From 73f807893607817b334fa5095388fbfce7cdd398 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Fri, 10 Mar 2023 15:25:16 +0100 Subject: [PATCH 01/45] define msg to submit misbehaviour to provider implement msg handling logic e2e test msg handling logic --- go.mod | 10 +- go.sum | 13 +- .../ccv/provider/v1/tx.proto | 11 +- tests/e2e/misbehaviour.go | 396 ++++++++++++++++++ testutil/integration/debug_test.go | 8 + testutil/keeper/mocks.go | 28 +- x/ccv/provider/keeper/misbehaviour.go | 52 +++ x/ccv/provider/types/tx.pb.go | 241 +++++++++-- x/ccv/types/expected_keepers.go | 2 + 9 files changed, 715 insertions(+), 46 deletions(-) create mode 100644 tests/e2e/misbehaviour.go create mode 100644 x/ccv/provider/keeper/misbehaviour.go diff --git a/go.mod b/go.mod index 58b8eaa667..2ac75fe25b 100644 --- a/go.mod +++ b/go.mod @@ -31,7 +31,10 @@ require ( gopkg.in/yaml.v2 v2.4.0 ) -require cosmossdk.io/errors v1.0.0-beta.7 +require ( + cosmossdk.io/errors v1.0.0-beta.7 + github.com/cosmos/interchain-security v1.2.1 +) require ( cosmossdk.io/api v0.2.6 // indirect @@ -85,7 +88,6 @@ require ( github.com/golang/glog v1.0.0 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/btree v1.1.2 // indirect - github.com/google/gofuzz v1.2.0 // indirect github.com/google/orderedcode v0.0.1 // indirect github.com/gorilla/handlers v1.5.1 // indirect github.com/gorilla/websocket v1.5.0 // indirect @@ -94,7 +96,6 @@ require ( github.com/gtank/merlin v0.1.1 // indirect github.com/gtank/ristretto255 v0.1.2 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect - github.com/hashicorp/go-uuid v1.0.2 // indirect github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 // indirect @@ -115,8 +116,7 @@ require ( github.com/minio/highwayhash v1.0.2 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mtibben/percent v0.2.1 // indirect - github.com/onsi/ginkgo v1.16.4 // indirect - github.com/onsi/gomega v1.20.0 // indirect + github.com/nxadm/tail v1.4.8 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.0.5 // indirect github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect diff --git a/go.sum b/go.sum index ed38bf1bab..6e8b054b07 100644 --- a/go.sum +++ b/go.sum @@ -242,6 +242,8 @@ github.com/cosmos/iavl v0.19.5 h1:rGA3hOrgNxgRM5wYcSCxgQBap7fW82WZgY78V9po/iY= github.com/cosmos/iavl v0.19.5/go.mod h1:X9PKD3J0iFxdmgNLa7b2LYWdsGd90ToV5cAONApkEPw= github.com/cosmos/ibc-go/v4 v4.4.2 h1:PG4Yy0/bw6Hvmha3RZbc53KYzaCwuB07Ot4GLyzcBvo= github.com/cosmos/ibc-go/v4 v4.4.2/go.mod h1:j/kD2JCIaV5ozvJvaEkWhLxM2zva7/KTM++EtKFYcB8= +github.com/cosmos/interchain-security v1.2.1 h1:8IawF74ep/eHRvY637Z1siw2gTBKPp4dU2khrL1P1U0= +github.com/cosmos/interchain-security v1.2.1/go.mod h1:yiJ9NBWvCW7M04BNDL5mWGEK1gsfK1w1IzKG49jnZm0= github.com/cosmos/ledger-cosmos-go v0.12.2 h1:/XYaBlE2BJxtvpkHiBm97gFGSGmYGKunKyF3nNqAXZA= github.com/cosmos/ledger-cosmos-go v0.12.2/go.mod h1:ZcqYgnfNJ6lAXe4HPtWgarNEY+B74i+2/8MhZw4ziiI= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= @@ -381,13 +383,12 @@ github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5Nq github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8= github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= -github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo= github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= +github.com/gobwas/ws v1.1.0 h1:7RFti/xnNkMJnrK7D1yQ/iCIB5OrrY/54/H930kIbHA= github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= @@ -464,7 +465,6 @@ github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSN github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -537,7 +537,6 @@ github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdv github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= -github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= @@ -777,14 +776,12 @@ github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= -github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.20.0 h1:8W0cWlwFkflGPLltQvLRB7ZVD5HuP6ng320w2IS245Q= -github.com/onsi/gomega v1.20.0/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034= @@ -1266,7 +1263,6 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1371,7 +1367,6 @@ golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= diff --git a/proto/interchain_security/ccv/provider/v1/tx.proto b/proto/interchain_security/ccv/provider/v1/tx.proto index 705e155317..7578ff84cc 100644 --- a/proto/interchain_security/ccv/provider/v1/tx.proto +++ b/proto/interchain_security/ccv/provider/v1/tx.proto @@ -7,6 +7,7 @@ import "google/api/annotations.proto"; import "gogoproto/gogo.proto"; import "cosmos_proto/cosmos.proto"; import "google/protobuf/any.proto"; +import "ibc/lightclients/tendermint/v1/tendermint.proto"; // Msg defines the Msg service. service Msg { @@ -42,4 +43,12 @@ message MsgRegisterConsumerRewardDenom { } // MsgRegisterConsumerRewardDenomResponse defines the Msg/RegisterConsumerRewardDenom response type. -message MsgRegisterConsumerRewardDenomResponse {} \ No newline at end of file +message MsgRegisterConsumerRewardDenomResponse {} +// MsgSubmitConsumerMisbehaviour reports a consumer chain Misbehaviour +message MsgSubmitConsumerMisbehaviour { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + // The Misbehaviour of the consumer chain wrapping + // two conflicting IBC headers + ibc.lightclients.tendermint.v1.Misbehaviour misbehaviour = 1; +} \ No newline at end of file diff --git a/tests/e2e/misbehaviour.go b/tests/e2e/misbehaviour.go new file mode 100644 index 0000000000..39e5db22f7 --- /dev/null +++ b/tests/e2e/misbehaviour.go @@ -0,0 +1,396 @@ +package e2e + +import ( + "time" + + ibcclientypes "github.com/cosmos/ibc-go/v4/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v4/modules/core/23-commitment/types" + ibctmtypes "github.com/cosmos/ibc-go/v4/modules/light-clients/07-tendermint/types" + ibctestingmock "github.com/cosmos/ibc-go/v4/testing/mock" + providertypes "github.com/cosmos/interchain-security/x/ccv/provider/types" + tmtypes "github.com/tendermint/tendermint/types" +) + +const ( + trustingPeriod time.Duration = time.Hour * 24 * 7 * 2 + ubdPeriod time.Duration = time.Hour * 24 * 7 * 3 + maxClockDrift time.Duration = time.Second * 10 +) + +// mostly based on TestCheckMisbehaviourAndUpdateState in ibc-go/modules/core/02-client/keeper/client_test.go +func (s *CCVTestSuite) TestCheckConsumerMisbehaviour() { + + s.SetupCCVChannel(s.path) + // required to have the consumer client revision height greater than 0 + s.SendEmptyVSCPacket() + + consumerConsState, _ := s.providerChain.GetConsensusState(s.path.EndpointA.ClientID, s.consumerChain.LastHeader.TrustedHeight) + + clientHeight := s.consumerChain.LastHeader.TrustedHeight + clientTMValset := tmtypes.NewValidatorSet(s.consumerChain.Vals.Validators) + clientSigners := s.consumerChain.Signers + + altPrivVal := ibctestingmock.NewPV() + altPubKey, err := altPrivVal.GetPubKey() + s.Require().NoError(err) + altVal := tmtypes.NewValidator(altPubKey, 4) + + altValset := tmtypes.NewValidatorSet([]*tmtypes.Validator{altVal}) + altSigners := make(map[string]tmtypes.PrivValidator, 1) + altSigners[altValset.Validators[0].Address.String()] = altPrivVal + + altTime := s.providerCtx().BlockTime().Add(time.Minute) + heightPlus5 := ibcclientypes.NewHeight(0, clientHeight.RevisionHeight+5) + + heightPlus3 := ibcclientypes.NewHeight(0, clientHeight.RevisionHeight+3) + + testCases := []struct { + name string + misbehaviour func() *ibctmtypes.Misbehaviour + malleate func() + expPass bool + }{ + { + "misbehaviour height is at same height as trusted height", + func() *ibctmtypes.Misbehaviour { + return &ibctmtypes.Misbehaviour{ + ClientId: s.path.EndpointA.ClientID, + Header1: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight), + clientHeight, + altTime, + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + Header2: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight), + clientHeight, + s.providerCtx().BlockTime(), + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + } + }, + func() {}, + false, + }, { + "invalid chain ID", + func() *ibctmtypes.Misbehaviour { + + mb := &ibctmtypes.Misbehaviour{ + ClientId: s.path.EndpointA.ClientID, + Header1: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime, + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + Header2: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime, + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + } + + mb.Header1.Header.ChainID = "wrongchainid" + return mb + + }, + func() {}, + false, + }, + { + "invalid client ID", + func() *ibctmtypes.Misbehaviour { + + mb := &ibctmtypes.Misbehaviour{ + ClientId: "wrongclientid", + Header1: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime, + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + Header2: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime, + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + } + + return mb + + }, + func() {}, + false, + }, { + "trusting period misbehavior should pass", + func() *ibctmtypes.Misbehaviour { + return &ibctmtypes.Misbehaviour{ + ClientId: s.path.EndpointA.ClientID, + Header1: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime, + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + Header2: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + s.providerCtx().BlockTime(), + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + } + }, + func() {}, + true, + }, + { + "time misbehavior should pass", + func() *ibctmtypes.Misbehaviour { + return &ibctmtypes.Misbehaviour{ + ClientId: s.path.EndpointA.ClientID, + Header1: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+5), + clientHeight, + s.providerCtx().BlockTime(), + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + Header2: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime, + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + } + }, + func() {}, + true, + }, + { + "both later height should pass", + func() *ibctmtypes.Misbehaviour { + return &ibctmtypes.Misbehaviour{ + ClientId: s.path.EndpointA.ClientID, + Header1: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(heightPlus5.RevisionHeight+1), + clientHeight, + s.providerCtx().BlockTime(), + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + Header2: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(heightPlus5.RevisionHeight+1), + heightPlus3, + altTime, + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + } + }, + func() { + + consumerConsState.(*ibctmtypes.ConsensusState).NextValidatorsHash = clientTMValset.Hash() + clientState := ibctmtypes.NewClientState(s.consumerChain.ChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), []string{"upgrade", "upgradedIBCState"}, false, false) + + // store trusted consensus state for Header2 + intermediateConsState := &ibctmtypes.ConsensusState{ + Timestamp: altTime, + NextValidatorsHash: clientTMValset.Hash(), + } + + s.providerApp.GetIBCKeeper().ClientKeeper.SetClientConsensusState(s.providerCtx(), s.path.EndpointA.ClientID, heightPlus3, intermediateConsState) + + clientState.LatestHeight = heightPlus3 + s.providerApp.GetIBCKeeper().ClientKeeper.SetClientState(s.providerCtx(), s.path.EndpointA.ClientID, clientState) + }, + true, + }, + { + "trusted ConsensusState1 not found", + func() *ibctmtypes.Misbehaviour { + return &ibctmtypes.Misbehaviour{ + ClientId: s.path.EndpointA.ClientID, + Header1: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight), + heightPlus3, + altTime, + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + Header2: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight), + clientHeight, + s.providerCtx().BlockTime(), + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + } + }, + func() {}, + false, + }, + { + "trusted ConsensusState2 not found", + func() *ibctmtypes.Misbehaviour { + return &ibctmtypes.Misbehaviour{ + ClientId: s.path.EndpointA.ClientID, + Header1: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight), + clientHeight, + altTime, + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + Header2: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight), + heightPlus3, + s.providerCtx().BlockTime(), + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + } + }, + func() {}, + false, + }, + { + "client state not found", + func() *ibctmtypes.Misbehaviour { + return &ibctmtypes.Misbehaviour{} + }, + func() {}, + false, + }, { + "client already is not active - client is frozen", + func() *ibctmtypes.Misbehaviour { + return &ibctmtypes.Misbehaviour{} + }, + func() { + consumerConsState.(*ibctmtypes.ConsensusState).NextValidatorsHash = clientTMValset.Hash() + clientState := ibctmtypes.NewClientState(s.consumerChain.ChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), []string{"upgrade", "upgradedIBCState"}, false, false) + + clientState.FrozenHeight = ibcclientypes.NewHeight(0, 1) + s.providerApp.GetIBCKeeper().ClientKeeper.SetClientState(s.providerCtx(), s.path.EndpointA.ClientID, clientState) + }, + false, + }, + { + "misbehaviour check failed", + func() *ibctmtypes.Misbehaviour { + return &ibctmtypes.Misbehaviour{ + ClientId: s.path.EndpointA.ClientID, + Header1: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + s.providerCtx().BlockTime(), + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + Header2: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime, + altValset, + altValset, + clientTMValset, + altSigners, + ), + } + }, + func() {}, + false, + }, + } + + for i, tc := range testCases { + + s.Run(tc.name, func() { + // run each test against fresh client states + cCtx, _ := s.providerCtx().CacheContext() + + tc.malleate() + + err = s.providerApp.GetProviderKeeper().CheckConsumerMisbehaviour( + cCtx, + providertypes.MsgSubmitConsumerMisbehaviour{ + Misbehaviour: tc.misbehaviour(), + }, + ) + + // Misbehaviour passed + if tc.expPass { + s.NoError(err, "valid test case %s failed with error %s", tc.name, err) + clientState, found := s.providerApp.GetIBCKeeper().ClientKeeper.GetClientState(cCtx, tc.misbehaviour().ClientId) + s.Require().True(found, "valid test case %d failed: %s", i, tc.name) + s.Require().True(!clientState.(*ibctmtypes.ClientState).FrozenHeight.IsZero(), "valid test case %d failed: %s", i, tc.name) + + } else { + // Misbehaviour rejected + s.Require().Error(err, "invalid test case %d passed: %s", i, tc.name) + } + }) + } +} diff --git a/testutil/integration/debug_test.go b/testutil/integration/debug_test.go index b6456663a9..7dd2b78777 100644 --- a/testutil/integration/debug_test.go +++ b/testutil/integration/debug_test.go @@ -256,3 +256,11 @@ func TestQueueAndSendVSCMaturedPackets(t *testing.T) { func TestRecycleTransferChannel(t *testing.T) { runCCVTestByName(t, "TestRecycleTransferChannel") } + +// +// Misbehaviour test +// + +func TestCheckConsumerMisbehaviour(t *testing.T) { + runCCVTestByName(t, "TestCheckConsumerMisbehaviour") +} diff --git a/testutil/keeper/mocks.go b/testutil/keeper/mocks.go index 4f931d8152..8dc42835b1 100644 --- a/testutil/keeper/mocks.go +++ b/testutil/keeper/mocks.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: x/ccv/types/expected_keepers.go +// Source: ./x/ccv/types/expected_keepers.go // Package keeper is a generated GoMock package. package keeper @@ -678,6 +678,20 @@ func (m *MockClientKeeper) EXPECT() *MockClientKeeperMockRecorder { return m.recorder } +// ClientStore mocks base method. +func (m *MockClientKeeper) ClientStore(ctx types.Context, clientID string) types.KVStore { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientStore", ctx, clientID) + ret0, _ := ret[0].(types.KVStore) + return ret0 +} + +// ClientStore indicates an expected call of ClientStore. +func (mr *MockClientKeeperMockRecorder) ClientStore(ctx, clientID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientStore", reflect.TypeOf((*MockClientKeeper)(nil).ClientStore), ctx, clientID) +} + // CreateClient mocks base method. func (m *MockClientKeeper) CreateClient(ctx types.Context, clientState exported.ClientState, consensusState exported.ConsensusState) (string, error) { m.ctrl.T.Helper() @@ -775,6 +789,18 @@ func (mr *MockDistributionKeeperMockRecorder) FundCommunityPool(ctx, amount, sen return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FundCommunityPool", reflect.TypeOf((*MockDistributionKeeper)(nil).FundCommunityPool), ctx, amount, sender) } +// SetClientState mocks base method. +func (m *MockClientKeeper) SetClientState(ctx types.Context, clientID string, clientState exported.ClientState) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetClientState", ctx, clientID, clientState) +} + +// SetClientState indicates an expected call of SetClientState. +func (mr *MockClientKeeperMockRecorder) SetClientState(ctx, clientID, clientState interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetClientState", reflect.TypeOf((*MockClientKeeper)(nil).SetClientState), ctx, clientID, clientState) +} + // MockConsumerHooks is a mock of ConsumerHooks interface. type MockConsumerHooks struct { ctrl *gomock.Controller diff --git a/x/ccv/provider/keeper/misbehaviour.go b/x/ccv/provider/keeper/misbehaviour.go new file mode 100644 index 0000000000..ec2609529e --- /dev/null +++ b/x/ccv/provider/keeper/misbehaviour.go @@ -0,0 +1,52 @@ +package keeper + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/ibc-go/v4/modules/core/exported" + "github.com/cosmos/interchain-security/x/ccv/provider/types" +) + +func (k Keeper) CheckConsumerMisbehaviour(ctx sdk.Context, msg types.MsgSubmitConsumerMisbehaviour) error { + + clientID := msg.Misbehaviour.GetClientID() + clientState, found := k.clientKeeper.GetClientState(ctx, clientID) + if !found { + return fmt.Errorf("types.ErrClientNotFound cannot check misbehaviour for client with ID %s", clientID) + } + + clientStore := k.clientKeeper.ClientStore(ctx, msg.Misbehaviour.GetClientID()) + + if status := clientState.Status(ctx, clientStore, k.cdc); status != exported.Active { + return fmt.Errorf("types.ErrClientNotActive cannot process misbehaviour for client (%s) with status %s", clientID, status) + } + + if err := msg.Misbehaviour.ValidateBasic(); err != nil { + return err + } + + clientState, err := clientState.CheckMisbehaviourAndUpdateState(ctx, k.cdc, clientStore, msg.Misbehaviour) + if err != nil { + return err + } + + k.clientKeeper.SetClientState(ctx, clientID, clientState) + k.Logger(ctx).Info("client frozen due to misbehaviour", "client-id", clientID) + + // TBD + // defer func() { + // telemetry.IncrCounterWithLabels( + // []string{"ibc", "client", "misbehaviour"}, + // 1, + // []metrics.Label{ + // telemetry.NewLabel(types.LabelClientType, misbehaviour.ClientType()), + // telemetry.NewLabel(types.LabelClientID, misbehaviour.GetClientID()), + // }, + // ) + // }() + + // EmitSubmitMisbehaviourEvent(ctx, clientID, clientState) + + return nil +} diff --git a/x/ccv/provider/types/tx.pb.go b/x/ccv/provider/types/tx.pb.go index 3603695359..71177c43d9 100644 --- a/x/ccv/provider/types/tx.pb.go +++ b/x/ccv/provider/types/tx.pb.go @@ -7,6 +7,7 @@ import ( context "context" fmt "fmt" _ "github.com/cosmos/cosmos-sdk/codec/types" + types "github.com/cosmos/ibc-go/v4/modules/light-clients/07-tendermint/types" _ "github.com/gogo/protobuf/gogoproto" grpc1 "github.com/gogo/protobuf/grpc" proto "github.com/gogo/protobuf/proto" @@ -191,11 +192,52 @@ func (m *MsgRegisterConsumerRewardDenomResponse) XXX_DiscardUnknown() { var xxx_messageInfo_MsgRegisterConsumerRewardDenomResponse proto.InternalMessageInfo +// MsgSubmitConsumerMisbehaviour reports a consumer chain Misbehaviour +type MsgSubmitConsumerMisbehaviour struct { + // The Misbehaviour of the consumer chain wrapping + // two conflicting IBC headers + Misbehaviour *types.Misbehaviour `protobuf:"bytes,1,opt,name=misbehaviour,proto3" json:"misbehaviour,omitempty"` +} + +func (m *MsgSubmitConsumerMisbehaviour) Reset() { *m = MsgSubmitConsumerMisbehaviour{} } +func (m *MsgSubmitConsumerMisbehaviour) String() string { return proto.CompactTextString(m) } +func (*MsgSubmitConsumerMisbehaviour) ProtoMessage() {} +func (*MsgSubmitConsumerMisbehaviour) Descriptor() ([]byte, []int) { + return fileDescriptor_43221a4391e9fbf4, []int{4} +} +func (m *MsgSubmitConsumerMisbehaviour) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgSubmitConsumerMisbehaviour) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgSubmitConsumerMisbehaviour.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgSubmitConsumerMisbehaviour) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgSubmitConsumerMisbehaviour.Merge(m, src) +} +func (m *MsgSubmitConsumerMisbehaviour) XXX_Size() int { + return m.Size() +} +func (m *MsgSubmitConsumerMisbehaviour) XXX_DiscardUnknown() { + xxx_messageInfo_MsgSubmitConsumerMisbehaviour.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgSubmitConsumerMisbehaviour proto.InternalMessageInfo + func init() { proto.RegisterType((*MsgAssignConsumerKey)(nil), "interchain_security.ccv.provider.v1.MsgAssignConsumerKey") proto.RegisterType((*MsgAssignConsumerKeyResponse)(nil), "interchain_security.ccv.provider.v1.MsgAssignConsumerKeyResponse") proto.RegisterType((*MsgRegisterConsumerRewardDenom)(nil), "interchain_security.ccv.provider.v1.MsgRegisterConsumerRewardDenom") proto.RegisterType((*MsgRegisterConsumerRewardDenomResponse)(nil), "interchain_security.ccv.provider.v1.MsgRegisterConsumerRewardDenomResponse") + proto.RegisterType((*MsgSubmitConsumerMisbehaviour)(nil), "interchain_security.ccv.provider.v1.MsgSubmitConsumerMisbehaviour") } func init() { @@ -203,36 +245,41 @@ func init() { } var fileDescriptor_43221a4391e9fbf4 = []byte{ - // 453 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x93, 0x3d, 0x6b, 0x14, 0x41, - 0x18, 0xc7, 0x77, 0x13, 0xd4, 0x64, 0x8c, 0x82, 0xc3, 0x15, 0x97, 0xf3, 0xd8, 0xd3, 0x15, 0x24, - 0x85, 0xee, 0x90, 0x58, 0x88, 0x01, 0x8b, 0x4b, 0x6c, 0x24, 0x5c, 0xb3, 0x8d, 0x60, 0xe1, 0xb1, - 0x37, 0x33, 0x4e, 0x06, 0xb3, 0xf3, 0x2c, 0xf3, 0xcc, 0xad, 0xd9, 0x6f, 0x60, 0xa9, 0x95, 0x6d, - 0xbe, 0x81, 0x5f, 0x43, 0xb0, 0x49, 0x69, 0x25, 0x72, 0xd7, 0x58, 0xfb, 0x09, 0x64, 0xdf, 0x3c, - 0xc5, 0xe3, 0x08, 0x92, 0xee, 0x79, 0xdb, 0xff, 0xff, 0xb7, 0x33, 0xf3, 0x90, 0x07, 0xda, 0x38, - 0x69, 0xf9, 0x71, 0xa2, 0xcd, 0x18, 0x25, 0x9f, 0x5a, 0xed, 0x0a, 0xc6, 0x79, 0xce, 0x32, 0x0b, - 0xb9, 0x16, 0xd2, 0xb2, 0x7c, 0x97, 0xb9, 0xd3, 0x28, 0xb3, 0xe0, 0x80, 0xde, 0x5b, 0x32, 0x1d, - 0x71, 0x9e, 0x47, 0xed, 0x74, 0x94, 0xef, 0xf6, 0xfa, 0x0a, 0x40, 0x9d, 0x48, 0x96, 0x64, 0x9a, - 0x25, 0xc6, 0x80, 0x4b, 0x9c, 0x06, 0x83, 0xb5, 0x44, 0xaf, 0xa3, 0x40, 0x41, 0x15, 0xb2, 0x32, - 0x6a, 0xaa, 0xdb, 0x1c, 0x30, 0x05, 0x1c, 0xd7, 0x8d, 0x3a, 0x69, 0x5b, 0x8d, 0x5c, 0x95, 0x4d, - 0xa6, 0xaf, 0x59, 0x62, 0x8a, 0xba, 0x15, 0x7e, 0xf4, 0x49, 0x67, 0x84, 0x6a, 0x88, 0xa8, 0x95, - 0x39, 0x04, 0x83, 0xd3, 0x54, 0xda, 0x23, 0x59, 0xd0, 0x6d, 0xb2, 0x51, 0x43, 0x6a, 0xd1, 0xf5, - 0xef, 0xf8, 0x3b, 0x9b, 0xf1, 0xb5, 0x2a, 0x7f, 0x2e, 0xe8, 0x63, 0x72, 0xa3, 0x85, 0x1d, 0x27, - 0x42, 0xd8, 0xee, 0x5a, 0xd9, 0x3f, 0xa0, 0x3f, 0xbf, 0x0d, 0x6e, 0x16, 0x49, 0x7a, 0xb2, 0x1f, - 0x96, 0x55, 0x89, 0x18, 0xc6, 0x5b, 0xed, 0xe0, 0x50, 0x08, 0x4b, 0xef, 0x92, 0x2d, 0xde, 0x58, - 0x8c, 0xdf, 0xc8, 0xa2, 0xbb, 0x5e, 0xe9, 0x5e, 0xe7, 0x0b, 0xdb, 0xfd, 0x8d, 0x77, 0x67, 0x03, - 0xef, 0xc7, 0xd9, 0xc0, 0x0b, 0x03, 0xd2, 0x5f, 0x06, 0x16, 0x4b, 0xcc, 0xc0, 0xa0, 0x0c, 0x5f, - 0x91, 0x60, 0x84, 0x2a, 0x96, 0x4a, 0xa3, 0x93, 0xb6, 0x9d, 0x88, 0xe5, 0xdb, 0xc4, 0x8a, 0x67, - 0xd2, 0x40, 0x4a, 0x3b, 0xe4, 0x8a, 0x28, 0x83, 0x86, 0xbf, 0x4e, 0x68, 0x9f, 0x6c, 0x0a, 0x99, - 0x01, 0x6a, 0x07, 0x0d, 0x79, 0xbc, 0x28, 0xfc, 0xe1, 0xbf, 0x43, 0xee, 0xaf, 0xd6, 0x6f, 0x49, - 0xf6, 0xbe, 0xac, 0x91, 0xf5, 0x11, 0x2a, 0xfa, 0xc1, 0x27, 0xb7, 0xfe, 0x3d, 0xc8, 0x27, 0xd1, - 0x05, 0x6e, 0x3c, 0x5a, 0xf6, 0xab, 0xbd, 0xe1, 0x7f, 0x7f, 0xda, 0xb2, 0xd1, 0x4f, 0x3e, 0xb9, - 0xbd, 0xea, 0x8c, 0x0e, 0x2f, 0x6a, 0xb1, 0x42, 0xa4, 0x77, 0x74, 0x09, 0x22, 0x2d, 0xf1, 0xc1, - 0x8b, 0xcf, 0xb3, 0xc0, 0x3f, 0x9f, 0x05, 0xfe, 0xf7, 0x59, 0xe0, 0xbf, 0x9f, 0x07, 0xde, 0xf9, - 0x3c, 0xf0, 0xbe, 0xce, 0x03, 0xef, 0xe5, 0x53, 0xa5, 0xdd, 0xf1, 0x74, 0x12, 0x71, 0x48, 0x9b, - 0xf7, 0xcd, 0x16, 0xbe, 0x0f, 0x7f, 0xaf, 0x5e, 0xbe, 0xc7, 0x4e, 0xff, 0xde, 0x3f, 0x57, 0x64, - 0x12, 0x27, 0x57, 0xab, 0x17, 0xff, 0xe8, 0x57, 0x00, 0x00, 0x00, 0xff, 0xff, 0x7a, 0x53, 0xb5, - 0xb8, 0xb0, 0x03, 0x00, 0x00, + // 530 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x54, 0x3f, 0x6f, 0x13, 0x31, + 0x14, 0xcf, 0xb5, 0x02, 0x5a, 0xb7, 0x20, 0x71, 0xca, 0x90, 0x86, 0x70, 0x81, 0x20, 0xa1, 0x0e, + 0xc5, 0x56, 0xc2, 0x80, 0xa8, 0xc4, 0x90, 0x96, 0x05, 0x55, 0x91, 0xd0, 0x31, 0x20, 0x31, 0x10, + 0xdd, 0xd9, 0xc6, 0xb1, 0xc8, 0xd9, 0x27, 0x3f, 0xdf, 0xd1, 0x13, 0x5f, 0x80, 0x11, 0x26, 0xd6, + 0x7e, 0x03, 0xbe, 0x06, 0x12, 0x4b, 0x47, 0x26, 0x84, 0x92, 0x85, 0x99, 0x4f, 0x80, 0x72, 0x7f, + 0x92, 0x54, 0x44, 0x51, 0x85, 0xd8, 0xde, 0xef, 0xbd, 0xe7, 0xdf, 0xef, 0x67, 0x3f, 0xdb, 0xe8, + 0x40, 0x2a, 0xcb, 0x0d, 0x1d, 0x05, 0x52, 0x0d, 0x81, 0xd3, 0xc4, 0x48, 0x9b, 0x11, 0x4a, 0x53, + 0x12, 0x1b, 0x9d, 0x4a, 0xc6, 0x0d, 0x49, 0xbb, 0xc4, 0x9e, 0xe2, 0xd8, 0x68, 0xab, 0xdd, 0x7b, + 0x2b, 0xba, 0x31, 0xa5, 0x29, 0xae, 0xba, 0x71, 0xda, 0x6d, 0xb6, 0x84, 0xd6, 0x62, 0xcc, 0x49, + 0x10, 0x4b, 0x12, 0x28, 0xa5, 0x6d, 0x60, 0xa5, 0x56, 0x50, 0x50, 0x34, 0xeb, 0x42, 0x0b, 0x9d, + 0x87, 0x64, 0x16, 0x95, 0xd9, 0x3d, 0xaa, 0x21, 0xd2, 0x30, 0x2c, 0x0a, 0x05, 0xa8, 0x4a, 0x25, + 0x5d, 0x8e, 0xc2, 0xe4, 0x0d, 0x09, 0x54, 0x56, 0x96, 0x88, 0x0c, 0x29, 0x19, 0x4b, 0x31, 0xb2, + 0x74, 0x2c, 0xb9, 0xb2, 0x40, 0x2c, 0x57, 0x8c, 0x9b, 0x48, 0x2a, 0x9b, 0xfb, 0x9e, 0xa3, 0x62, + 0x41, 0xe7, 0xb3, 0x83, 0xea, 0x03, 0x10, 0x7d, 0x00, 0x29, 0xd4, 0xb1, 0x56, 0x90, 0x44, 0xdc, + 0x9c, 0xf0, 0xcc, 0xdd, 0x43, 0x5b, 0xc5, 0xae, 0x24, 0x6b, 0x38, 0x77, 0x9c, 0xfd, 0x6d, 0xff, + 0x5a, 0x8e, 0x9f, 0x31, 0xf7, 0x11, 0xba, 0x5e, 0xed, 0x6e, 0x18, 0x30, 0x66, 0x1a, 0x1b, 0xb3, + 0xfa, 0x91, 0xfb, 0xfb, 0x47, 0xfb, 0x46, 0x16, 0x44, 0xe3, 0xc3, 0xce, 0x2c, 0xcb, 0x01, 0x3a, + 0xfe, 0x6e, 0xd5, 0xd8, 0x67, 0xcc, 0xb8, 0x77, 0xd1, 0x2e, 0x2d, 0x25, 0x86, 0x6f, 0x79, 0xd6, + 0xd8, 0xcc, 0x79, 0x77, 0xe8, 0x42, 0xf6, 0x70, 0xeb, 0xc3, 0x59, 0xbb, 0xf6, 0xeb, 0xac, 0x5d, + 0xeb, 0x78, 0xa8, 0xb5, 0xca, 0x98, 0xcf, 0x21, 0xd6, 0x0a, 0x78, 0xe7, 0x35, 0xf2, 0x06, 0x20, + 0x7c, 0x2e, 0x24, 0x58, 0x6e, 0xaa, 0x0e, 0x9f, 0xbf, 0x0b, 0x0c, 0x7b, 0xca, 0x95, 0x8e, 0xdc, + 0x3a, 0xba, 0xc2, 0x66, 0x41, 0xe9, 0xbf, 0x00, 0x6e, 0x0b, 0x6d, 0x33, 0x1e, 0x6b, 0x90, 0x56, + 0x97, 0xce, 0xfd, 0x45, 0x62, 0x49, 0x7f, 0x1f, 0xdd, 0x5f, 0xcf, 0x3f, 0x77, 0xf2, 0x1e, 0xdd, + 0x1e, 0x80, 0x78, 0x91, 0x84, 0x91, 0xb4, 0x55, 0xdf, 0x40, 0x42, 0xc8, 0x47, 0x41, 0x2a, 0x75, + 0x62, 0xdc, 0xe7, 0x68, 0x37, 0x5a, 0xc2, 0xb9, 0x9f, 0x9d, 0xde, 0x01, 0x96, 0x21, 0xc5, 0xcb, + 0xc3, 0xc2, 0x4b, 0xe3, 0x49, 0xbb, 0x78, 0x99, 0xc3, 0xbf, 0xc0, 0xb0, 0xb0, 0xd9, 0xfb, 0xb6, + 0x81, 0x36, 0x07, 0x20, 0xdc, 0x4f, 0x0e, 0xba, 0xf9, 0xf7, 0x14, 0x1f, 0xe3, 0x4b, 0xdc, 0x4f, + 0xbc, 0xea, 0x9c, 0x9b, 0xfd, 0x7f, 0x5e, 0x5a, 0x1d, 0x8c, 0xfb, 0xc5, 0x41, 0xb7, 0xd6, 0x0d, + 0xe8, 0xf8, 0xb2, 0x12, 0x6b, 0x48, 0x9a, 0x27, 0xff, 0x81, 0xa4, 0x72, 0x7c, 0xf4, 0xf2, 0xeb, + 0xc4, 0x73, 0xce, 0x27, 0x9e, 0xf3, 0x73, 0xe2, 0x39, 0x1f, 0xa7, 0x5e, 0xed, 0x7c, 0xea, 0xd5, + 0xbe, 0x4f, 0xbd, 0xda, 0xab, 0x27, 0x42, 0xda, 0x51, 0x12, 0x62, 0xaa, 0xa3, 0xf2, 0x35, 0x92, + 0x85, 0xee, 0x83, 0xf9, 0x47, 0x91, 0xf6, 0xc8, 0xe9, 0xc5, 0xdf, 0xc2, 0x66, 0x31, 0x87, 0xf0, + 0x6a, 0xfe, 0xdc, 0x1e, 0xfe, 0x09, 0x00, 0x00, 0xff, 0xff, 0xd8, 0x5c, 0x30, 0xf0, 0x5e, 0x04, + 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -478,6 +525,41 @@ func (m *MsgRegisterConsumerRewardDenomResponse) MarshalToSizedBuffer(dAtA []byt return len(dAtA) - i, nil } +func (m *MsgSubmitConsumerMisbehaviour) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgSubmitConsumerMisbehaviour) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgSubmitConsumerMisbehaviour) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Misbehaviour != nil { + { + size, err := m.Misbehaviour.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func encodeVarintTx(dAtA []byte, offset int, v uint64) int { offset -= sovTx(v) base := offset @@ -545,6 +627,19 @@ func (m *MsgRegisterConsumerRewardDenomResponse) Size() (n int) { return n } +func (m *MsgSubmitConsumerMisbehaviour) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Misbehaviour != nil { + l = m.Misbehaviour.Size() + n += 1 + l + sovTx(uint64(l)) + } + return n +} + func sovTx(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -911,6 +1006,92 @@ func (m *MsgRegisterConsumerRewardDenomResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *MsgSubmitConsumerMisbehaviour) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgSubmitConsumerMisbehaviour: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgSubmitConsumerMisbehaviour: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Misbehaviour", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Misbehaviour == nil { + m.Misbehaviour = &types.Misbehaviour{} + } + if err := m.Misbehaviour.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipTx(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/ccv/types/expected_keepers.go b/x/ccv/types/expected_keepers.go index 7f18324c8d..5280cb8b8d 100644 --- a/x/ccv/types/expected_keepers.go +++ b/x/ccv/types/expected_keepers.go @@ -86,6 +86,8 @@ type ClientKeeper interface { GetClientState(ctx sdk.Context, clientID string) (ibcexported.ClientState, bool) GetLatestClientConsensusState(ctx sdk.Context, clientID string) (ibcexported.ConsensusState, bool) GetSelfConsensusState(ctx sdk.Context, height ibcexported.Height) (ibcexported.ConsensusState, error) + ClientStore(ctx sdk.Context, clientID string) sdk.KVStore + SetClientState(ctx sdk.Context, clientID string, clientState ibcexported.ClientState) } // DistributionKeeper defines the expected interface of the distribution keeper From 0b6fd43c90323389a27d68a3294be1ef7caa3c4b Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Mon, 13 Mar 2023 08:53:35 +0100 Subject: [PATCH 02/45] wip: get byzantine validators in misbehavioiur handling --- tests/e2e/misbehaviour.go | 1 + x/ccv/provider/keeper/misbehaviour.go | 43 +++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/tests/e2e/misbehaviour.go b/tests/e2e/misbehaviour.go index 39e5db22f7..fccaa7e174 100644 --- a/tests/e2e/misbehaviour.go +++ b/tests/e2e/misbehaviour.go @@ -23,6 +23,7 @@ func (s *CCVTestSuite) TestCheckConsumerMisbehaviour() { s.SetupCCVChannel(s.path) // required to have the consumer client revision height greater than 0 s.SendEmptyVSCPacket() + tmtypes.LightClientAttackEvidence consumerConsState, _ := s.providerChain.GetConsensusState(s.path.EndpointA.ClientID, s.consumerChain.LastHeader.TrustedHeight) diff --git a/x/ccv/provider/keeper/misbehaviour.go b/x/ccv/provider/keeper/misbehaviour.go index ec2609529e..645eb721e0 100644 --- a/x/ccv/provider/keeper/misbehaviour.go +++ b/x/ccv/provider/keeper/misbehaviour.go @@ -6,6 +6,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/ibc-go/v4/modules/core/exported" "github.com/cosmos/interchain-security/x/ccv/provider/types" + tmtypes "github.com/tendermint/tendermint/types" ) func (k Keeper) CheckConsumerMisbehaviour(ctx sdk.Context, msg types.MsgSubmitConsumerMisbehaviour) error { @@ -34,6 +35,48 @@ func (k Keeper) CheckConsumerMisbehaviour(ctx sdk.Context, msg types.MsgSubmitCo k.clientKeeper.SetClientState(ctx, clientID, clientState) k.Logger(ctx).Info("client frozen due to misbehaviour", "client-id", clientID) + // get byzantine validators + sh, err := tmtypes.SignedHeaderFromProto(msg.Misbehaviour.Header1.SignedHeader) + if err != nil { + return err + } + + vs, err := tmtypes.ValidatorSetFromProto(msg.Misbehaviour.Header1.ValidatorSet) + if err != nil { + return err + } + ev := tmtypes.LightClientAttackEvidence{ + ConflictingBlock: &tmtypes.LightBlock{SignedHeader: sh, ValidatorSet: vs}, + } + + h2, err := tmtypes.HeaderFromProto(msg.Misbehaviour.Header2.Header) + if err != nil { + return err + } + + // WIP: return byzantine validators according to the light client commited + + // if this is an equivocation or amnesia attack, i.e. the validator sets are the same, then we + // return the height of the conflicting block else if it is a lunatic attack and the validator sets + // are not the same then we send the height of the common header. + if ev.ConflictingHeaderIsInvalid(&h2) { + ev.CommonHeight = msg.Misbehaviour.Header2.Header.Height + ev.Timestamp = msg.Misbehaviour.Header2.Header.Time + ev.TotalVotingPower = msg.Misbehaviour.Header2.ValidatorSet.TotalVotingPower + } else { + ev.CommonHeight = msg.Misbehaviour.Header1.Header.Height + ev.Timestamp = msg.Misbehaviour.Header1.Header.Time + ev.TotalVotingPower = msg.Misbehaviour.Header1.ValidatorSet.TotalVotingPower + } + ev.ByzantineValidators = ev.GetByzantineValidators(vs, sh) + + logger := ctx.Logger() + + logger.Info( + "confirmed equivocation", + "byzantine validators", ev.ByzantineValidators, + ) + // TBD // defer func() { // telemetry.IncrCounterWithLabels( From 16d940d9acc636cb9303b740604ec2e5c4731155 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Mon, 13 Mar 2023 10:06:30 +0100 Subject: [PATCH 03/45] add tx handler --- go.mod | 11 +- go.sum | 17 +- .../ccv/provider/v1/tx.proto | 7 +- tests/{e2e => integration}/misbehaviour.go | 8 +- x/ccv/provider/keeper/misbehaviour.go | 30 +-- x/ccv/provider/types/msg.go | 48 +++- x/ccv/provider/types/tx.pb.go | 236 +++++++++++++++--- x/ccv/types/events.go | 3 + 8 files changed, 291 insertions(+), 69 deletions(-) rename tests/{e2e => integration}/misbehaviour.go (98%) diff --git a/go.mod b/go.mod index 2ac75fe25b..7ef3e43ffb 100644 --- a/go.mod +++ b/go.mod @@ -31,10 +31,7 @@ require ( gopkg.in/yaml.v2 v2.4.0 ) -require ( - cosmossdk.io/errors v1.0.0-beta.7 - github.com/cosmos/interchain-security v1.2.1 -) +require cosmossdk.io/errors v1.0.0-beta.7 require ( cosmossdk.io/api v0.2.6 // indirect @@ -83,11 +80,13 @@ require ( github.com/go-kit/kit v0.12.0 // indirect github.com/go-kit/log v0.2.1 // indirect github.com/go-logfmt/logfmt v0.5.1 // indirect + github.com/gobwas/ws v1.1.0 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/gogo/gateway v1.1.0 // indirect github.com/golang/glog v1.0.0 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/btree v1.1.2 // indirect + github.com/google/gofuzz v1.2.0 // indirect github.com/google/orderedcode v0.0.1 // indirect github.com/gorilla/handlers v1.5.1 // indirect github.com/gorilla/websocket v1.5.0 // indirect @@ -96,6 +95,7 @@ require ( github.com/gtank/merlin v0.1.1 // indirect github.com/gtank/ristretto255 v0.1.2 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect + github.com/hashicorp/go-uuid v1.0.2 // indirect github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 // indirect @@ -116,7 +116,8 @@ require ( github.com/minio/highwayhash v1.0.2 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mtibben/percent v0.2.1 // indirect - github.com/nxadm/tail v1.4.8 // indirect + github.com/onsi/ginkgo v1.16.5 // indirect + github.com/onsi/gomega v1.20.0 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.0.5 // indirect github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect diff --git a/go.sum b/go.sum index 6e8b054b07..5afd840ee9 100644 --- a/go.sum +++ b/go.sum @@ -242,8 +242,6 @@ github.com/cosmos/iavl v0.19.5 h1:rGA3hOrgNxgRM5wYcSCxgQBap7fW82WZgY78V9po/iY= github.com/cosmos/iavl v0.19.5/go.mod h1:X9PKD3J0iFxdmgNLa7b2LYWdsGd90ToV5cAONApkEPw= github.com/cosmos/ibc-go/v4 v4.4.2 h1:PG4Yy0/bw6Hvmha3RZbc53KYzaCwuB07Ot4GLyzcBvo= github.com/cosmos/ibc-go/v4 v4.4.2/go.mod h1:j/kD2JCIaV5ozvJvaEkWhLxM2zva7/KTM++EtKFYcB8= -github.com/cosmos/interchain-security v1.2.1 h1:8IawF74ep/eHRvY637Z1siw2gTBKPp4dU2khrL1P1U0= -github.com/cosmos/interchain-security v1.2.1/go.mod h1:yiJ9NBWvCW7M04BNDL5mWGEK1gsfK1w1IzKG49jnZm0= github.com/cosmos/ledger-cosmos-go v0.12.2 h1:/XYaBlE2BJxtvpkHiBm97gFGSGmYGKunKyF3nNqAXZA= github.com/cosmos/ledger-cosmos-go v0.12.2/go.mod h1:ZcqYgnfNJ6lAXe4HPtWgarNEY+B74i+2/8MhZw4ziiI= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= @@ -383,12 +381,16 @@ github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5Nq github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= -github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8= +github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= +github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= +github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= github.com/gobwas/ws v1.1.0 h1:7RFti/xnNkMJnrK7D1yQ/iCIB5OrrY/54/H930kIbHA= +github.com/gobwas/ws v1.1.0/go.mod h1:nzvNcVha5eUziGrbxFCo6qFIojQHjJV5cLYIbezhfL0= github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= @@ -465,6 +467,7 @@ github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSN github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -537,6 +540,7 @@ github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdv github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= @@ -777,11 +781,13 @@ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108 github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.20.0 h1:8W0cWlwFkflGPLltQvLRB7ZVD5HuP6ng320w2IS245Q= +github.com/onsi/gomega v1.20.0/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034= @@ -1262,7 +1268,9 @@ golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1367,6 +1375,7 @@ golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= diff --git a/proto/interchain_security/ccv/provider/v1/tx.proto b/proto/interchain_security/ccv/provider/v1/tx.proto index 7578ff84cc..f6d06b4bb3 100644 --- a/proto/interchain_security/ccv/provider/v1/tx.proto +++ b/proto/interchain_security/ccv/provider/v1/tx.proto @@ -48,7 +48,10 @@ message MsgRegisterConsumerRewardDenomResponse {} message MsgSubmitConsumerMisbehaviour { option (gogoproto.equal) = false; option (gogoproto.goproto_getters) = false; + string submitter = 1; // The Misbehaviour of the consumer chain wrapping // two conflicting IBC headers - ibc.lightclients.tendermint.v1.Misbehaviour misbehaviour = 1; -} \ No newline at end of file + ibc.lightclients.tendermint.v1.Misbehaviour misbehaviour = 2; +} + +message MsgSubmitConsumerMisbehaviourResponse {} diff --git a/tests/e2e/misbehaviour.go b/tests/integration/misbehaviour.go similarity index 98% rename from tests/e2e/misbehaviour.go rename to tests/integration/misbehaviour.go index fccaa7e174..b04c865d41 100644 --- a/tests/e2e/misbehaviour.go +++ b/tests/integration/misbehaviour.go @@ -1,4 +1,4 @@ -package e2e +package integration import ( "time" @@ -7,7 +7,6 @@ import ( commitmenttypes "github.com/cosmos/ibc-go/v4/modules/core/23-commitment/types" ibctmtypes "github.com/cosmos/ibc-go/v4/modules/light-clients/07-tendermint/types" ibctestingmock "github.com/cosmos/ibc-go/v4/testing/mock" - providertypes "github.com/cosmos/interchain-security/x/ccv/provider/types" tmtypes "github.com/tendermint/tendermint/types" ) @@ -23,7 +22,6 @@ func (s *CCVTestSuite) TestCheckConsumerMisbehaviour() { s.SetupCCVChannel(s.path) // required to have the consumer client revision height greater than 0 s.SendEmptyVSCPacket() - tmtypes.LightClientAttackEvidence consumerConsState, _ := s.providerChain.GetConsensusState(s.path.EndpointA.ClientID, s.consumerChain.LastHeader.TrustedHeight) @@ -376,9 +374,7 @@ func (s *CCVTestSuite) TestCheckConsumerMisbehaviour() { err = s.providerApp.GetProviderKeeper().CheckConsumerMisbehaviour( cCtx, - providertypes.MsgSubmitConsumerMisbehaviour{ - Misbehaviour: tc.misbehaviour(), - }, + *tc.misbehaviour(), ) // Misbehaviour passed diff --git a/x/ccv/provider/keeper/misbehaviour.go b/x/ccv/provider/keeper/misbehaviour.go index 645eb721e0..737b272729 100644 --- a/x/ccv/provider/keeper/misbehaviour.go +++ b/x/ccv/provider/keeper/misbehaviour.go @@ -5,29 +5,29 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/ibc-go/v4/modules/core/exported" - "github.com/cosmos/interchain-security/x/ccv/provider/types" + ibctmtypes "github.com/cosmos/ibc-go/v4/modules/light-clients/07-tendermint/types" tmtypes "github.com/tendermint/tendermint/types" ) -func (k Keeper) CheckConsumerMisbehaviour(ctx sdk.Context, msg types.MsgSubmitConsumerMisbehaviour) error { +func (k Keeper) CheckConsumerMisbehaviour(ctx sdk.Context, misbeaviour ibctmtypes.Misbehaviour) error { - clientID := msg.Misbehaviour.GetClientID() + clientID := misbeaviour.GetClientID() clientState, found := k.clientKeeper.GetClientState(ctx, clientID) if !found { return fmt.Errorf("types.ErrClientNotFound cannot check misbehaviour for client with ID %s", clientID) } - clientStore := k.clientKeeper.ClientStore(ctx, msg.Misbehaviour.GetClientID()) + clientStore := k.clientKeeper.ClientStore(ctx, misbeaviour.GetClientID()) if status := clientState.Status(ctx, clientStore, k.cdc); status != exported.Active { return fmt.Errorf("types.ErrClientNotActive cannot process misbehaviour for client (%s) with status %s", clientID, status) } - if err := msg.Misbehaviour.ValidateBasic(); err != nil { + if err := misbeaviour.ValidateBasic(); err != nil { return err } - clientState, err := clientState.CheckMisbehaviourAndUpdateState(ctx, k.cdc, clientStore, msg.Misbehaviour) + clientState, err := clientState.CheckMisbehaviourAndUpdateState(ctx, k.cdc, clientStore, &misbeaviour) if err != nil { return err } @@ -36,12 +36,12 @@ func (k Keeper) CheckConsumerMisbehaviour(ctx sdk.Context, msg types.MsgSubmitCo k.Logger(ctx).Info("client frozen due to misbehaviour", "client-id", clientID) // get byzantine validators - sh, err := tmtypes.SignedHeaderFromProto(msg.Misbehaviour.Header1.SignedHeader) + sh, err := tmtypes.SignedHeaderFromProto(misbeaviour.Header1.SignedHeader) if err != nil { return err } - vs, err := tmtypes.ValidatorSetFromProto(msg.Misbehaviour.Header1.ValidatorSet) + vs, err := tmtypes.ValidatorSetFromProto(misbeaviour.Header1.ValidatorSet) if err != nil { return err } @@ -49,7 +49,7 @@ func (k Keeper) CheckConsumerMisbehaviour(ctx sdk.Context, msg types.MsgSubmitCo ConflictingBlock: &tmtypes.LightBlock{SignedHeader: sh, ValidatorSet: vs}, } - h2, err := tmtypes.HeaderFromProto(msg.Misbehaviour.Header2.Header) + h2, err := tmtypes.HeaderFromProto(misbeaviour.Header2.Header) if err != nil { return err } @@ -60,13 +60,13 @@ func (k Keeper) CheckConsumerMisbehaviour(ctx sdk.Context, msg types.MsgSubmitCo // return the height of the conflicting block else if it is a lunatic attack and the validator sets // are not the same then we send the height of the common header. if ev.ConflictingHeaderIsInvalid(&h2) { - ev.CommonHeight = msg.Misbehaviour.Header2.Header.Height - ev.Timestamp = msg.Misbehaviour.Header2.Header.Time - ev.TotalVotingPower = msg.Misbehaviour.Header2.ValidatorSet.TotalVotingPower + ev.CommonHeight = misbeaviour.Header2.Header.Height + ev.Timestamp = misbeaviour.Header2.Header.Time + ev.TotalVotingPower = misbeaviour.Header2.ValidatorSet.TotalVotingPower } else { - ev.CommonHeight = msg.Misbehaviour.Header1.Header.Height - ev.Timestamp = msg.Misbehaviour.Header1.Header.Time - ev.TotalVotingPower = msg.Misbehaviour.Header1.ValidatorSet.TotalVotingPower + ev.CommonHeight = misbeaviour.Header1.Header.Height + ev.Timestamp = misbeaviour.Header1.Header.Time + ev.TotalVotingPower = misbeaviour.Header1.ValidatorSet.TotalVotingPower } ev.ByzantineValidators = ev.GetByzantineValidators(vs, sh) diff --git a/x/ccv/provider/types/msg.go b/x/ccv/provider/types/msg.go index 67ad99d10c..9496122342 100644 --- a/x/ccv/provider/types/msg.go +++ b/x/ccv/provider/types/msg.go @@ -5,15 +5,22 @@ import ( "strings" sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + + ibctmtypes "github.com/cosmos/ibc-go/v4/modules/light-clients/07-tendermint/types" ) // provider message types const ( TypeMsgAssignConsumerKey = "assign_consumer_key" TypeMsgRegisterConsumerRewardDenom = "register_consumer_reward_denom" + TypeMsgSubmitConsumerMisbehaviour = "submit_consumer_misbehaviour" ) -var _ sdk.Msg = &MsgAssignConsumerKey{} +var ( + _ sdk.Msg = &MsgAssignConsumerKey{} + _ sdk.Msg = &MsgSubmitConsumerMisbehaviour{} +) // NewMsgAssignConsumerKey creates a new MsgAssignConsumerKey instance. // Delegator address and validator address are the same. @@ -139,3 +146,42 @@ func (msg MsgRegisterConsumerRewardDenom) ValidateBasic() error { return nil } + +func NewMsgSubmitConsumerMisbehaviour(submitter sdk.AccAddress, m *ibctmtypes.Misbehaviour) (*MsgSubmitConsumerMisbehaviour, error) { + return &MsgSubmitConsumerMisbehaviour{Submitter: submitter.String(), Misbehaviour: m}, nil +} + +// Route implements the sdk.Msg interface. +func (msg MsgSubmitConsumerMisbehaviour) Route() string { return RouterKey } + +// Type implements the sdk.Msg interface. +func (msg MsgSubmitConsumerMisbehaviour) Type() string { + return TypeMsgSubmitConsumerMisbehaviour +} + +// Type implements the sdk.Msg interface. +func (msg MsgSubmitConsumerMisbehaviour) ValidateBasic() error { + if msg.Submitter == "" { + return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, msg.Submitter) + } + if err := msg.Misbehaviour.ValidateBasic(); err != nil { + return err + } + return nil +} + +// Type implements the sdk.Msg interface. +func (msg MsgSubmitConsumerMisbehaviour) GetSignBytes() []byte { + bz := ModuleCdc.MustMarshalJSON(&msg) + return sdk.MustSortJSON(bz) +} + +// Type implements the sdk.Msg interface. +func (msg MsgSubmitConsumerMisbehaviour) GetSigners() []sdk.AccAddress { + addr, err := sdk.AccAddressFromBech32(msg.Submitter) + if err != nil { + // same behavior as in cosmos-sdk + panic(err) + } + return []sdk.AccAddress{addr} +} diff --git a/x/ccv/provider/types/tx.pb.go b/x/ccv/provider/types/tx.pb.go index 71177c43d9..4a82c5afc6 100644 --- a/x/ccv/provider/types/tx.pb.go +++ b/x/ccv/provider/types/tx.pb.go @@ -194,9 +194,10 @@ var xxx_messageInfo_MsgRegisterConsumerRewardDenomResponse proto.InternalMessage // MsgSubmitConsumerMisbehaviour reports a consumer chain Misbehaviour type MsgSubmitConsumerMisbehaviour struct { + Submitter string `protobuf:"bytes,1,opt,name=submitter,proto3" json:"submitter,omitempty"` // The Misbehaviour of the consumer chain wrapping // two conflicting IBC headers - Misbehaviour *types.Misbehaviour `protobuf:"bytes,1,opt,name=misbehaviour,proto3" json:"misbehaviour,omitempty"` + Misbehaviour *types.Misbehaviour `protobuf:"bytes,2,opt,name=misbehaviour,proto3" json:"misbehaviour,omitempty"` } func (m *MsgSubmitConsumerMisbehaviour) Reset() { *m = MsgSubmitConsumerMisbehaviour{} } @@ -232,12 +233,49 @@ func (m *MsgSubmitConsumerMisbehaviour) XXX_DiscardUnknown() { var xxx_messageInfo_MsgSubmitConsumerMisbehaviour proto.InternalMessageInfo +type MsgSubmitConsumerMisbehaviourResponse struct { +} + +func (m *MsgSubmitConsumerMisbehaviourResponse) Reset() { *m = MsgSubmitConsumerMisbehaviourResponse{} } +func (m *MsgSubmitConsumerMisbehaviourResponse) String() string { return proto.CompactTextString(m) } +func (*MsgSubmitConsumerMisbehaviourResponse) ProtoMessage() {} +func (*MsgSubmitConsumerMisbehaviourResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_43221a4391e9fbf4, []int{5} +} +func (m *MsgSubmitConsumerMisbehaviourResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgSubmitConsumerMisbehaviourResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgSubmitConsumerMisbehaviourResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgSubmitConsumerMisbehaviourResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgSubmitConsumerMisbehaviourResponse.Merge(m, src) +} +func (m *MsgSubmitConsumerMisbehaviourResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgSubmitConsumerMisbehaviourResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgSubmitConsumerMisbehaviourResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgSubmitConsumerMisbehaviourResponse proto.InternalMessageInfo + func init() { proto.RegisterType((*MsgAssignConsumerKey)(nil), "interchain_security.ccv.provider.v1.MsgAssignConsumerKey") proto.RegisterType((*MsgAssignConsumerKeyResponse)(nil), "interchain_security.ccv.provider.v1.MsgAssignConsumerKeyResponse") proto.RegisterType((*MsgRegisterConsumerRewardDenom)(nil), "interchain_security.ccv.provider.v1.MsgRegisterConsumerRewardDenom") proto.RegisterType((*MsgRegisterConsumerRewardDenomResponse)(nil), "interchain_security.ccv.provider.v1.MsgRegisterConsumerRewardDenomResponse") proto.RegisterType((*MsgSubmitConsumerMisbehaviour)(nil), "interchain_security.ccv.provider.v1.MsgSubmitConsumerMisbehaviour") + proto.RegisterType((*MsgSubmitConsumerMisbehaviourResponse)(nil), "interchain_security.ccv.provider.v1.MsgSubmitConsumerMisbehaviourResponse") } func init() { @@ -245,41 +283,42 @@ func init() { } var fileDescriptor_43221a4391e9fbf4 = []byte{ - // 530 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x54, 0x3f, 0x6f, 0x13, 0x31, - 0x14, 0xcf, 0xb5, 0x02, 0x5a, 0xb7, 0x20, 0x71, 0xca, 0x90, 0x86, 0x70, 0x81, 0x20, 0xa1, 0x0e, - 0xc5, 0x56, 0xc2, 0x80, 0xa8, 0xc4, 0x90, 0x96, 0x05, 0x55, 0x91, 0xd0, 0x31, 0x20, 0x31, 0x10, - 0xdd, 0xd9, 0xc6, 0xb1, 0xc8, 0xd9, 0x27, 0x3f, 0xdf, 0xd1, 0x13, 0x5f, 0x80, 0x11, 0x26, 0xd6, - 0x7e, 0x03, 0xbe, 0x06, 0x12, 0x4b, 0x47, 0x26, 0x84, 0x92, 0x85, 0x99, 0x4f, 0x80, 0x72, 0x7f, - 0x92, 0x54, 0x44, 0x51, 0x85, 0xd8, 0xde, 0xef, 0xbd, 0xe7, 0xdf, 0xef, 0x67, 0x3f, 0xdb, 0xe8, - 0x40, 0x2a, 0xcb, 0x0d, 0x1d, 0x05, 0x52, 0x0d, 0x81, 0xd3, 0xc4, 0x48, 0x9b, 0x11, 0x4a, 0x53, - 0x12, 0x1b, 0x9d, 0x4a, 0xc6, 0x0d, 0x49, 0xbb, 0xc4, 0x9e, 0xe2, 0xd8, 0x68, 0xab, 0xdd, 0x7b, - 0x2b, 0xba, 0x31, 0xa5, 0x29, 0xae, 0xba, 0x71, 0xda, 0x6d, 0xb6, 0x84, 0xd6, 0x62, 0xcc, 0x49, - 0x10, 0x4b, 0x12, 0x28, 0xa5, 0x6d, 0x60, 0xa5, 0x56, 0x50, 0x50, 0x34, 0xeb, 0x42, 0x0b, 0x9d, - 0x87, 0x64, 0x16, 0x95, 0xd9, 0x3d, 0xaa, 0x21, 0xd2, 0x30, 0x2c, 0x0a, 0x05, 0xa8, 0x4a, 0x25, - 0x5d, 0x8e, 0xc2, 0xe4, 0x0d, 0x09, 0x54, 0x56, 0x96, 0x88, 0x0c, 0x29, 0x19, 0x4b, 0x31, 0xb2, - 0x74, 0x2c, 0xb9, 0xb2, 0x40, 0x2c, 0x57, 0x8c, 0x9b, 0x48, 0x2a, 0x9b, 0xfb, 0x9e, 0xa3, 0x62, - 0x41, 0xe7, 0xb3, 0x83, 0xea, 0x03, 0x10, 0x7d, 0x00, 0x29, 0xd4, 0xb1, 0x56, 0x90, 0x44, 0xdc, - 0x9c, 0xf0, 0xcc, 0xdd, 0x43, 0x5b, 0xc5, 0xae, 0x24, 0x6b, 0x38, 0x77, 0x9c, 0xfd, 0x6d, 0xff, - 0x5a, 0x8e, 0x9f, 0x31, 0xf7, 0x11, 0xba, 0x5e, 0xed, 0x6e, 0x18, 0x30, 0x66, 0x1a, 0x1b, 0xb3, - 0xfa, 0x91, 0xfb, 0xfb, 0x47, 0xfb, 0x46, 0x16, 0x44, 0xe3, 0xc3, 0xce, 0x2c, 0xcb, 0x01, 0x3a, - 0xfe, 0x6e, 0xd5, 0xd8, 0x67, 0xcc, 0xb8, 0x77, 0xd1, 0x2e, 0x2d, 0x25, 0x86, 0x6f, 0x79, 0xd6, - 0xd8, 0xcc, 0x79, 0x77, 0xe8, 0x42, 0xf6, 0x70, 0xeb, 0xc3, 0x59, 0xbb, 0xf6, 0xeb, 0xac, 0x5d, - 0xeb, 0x78, 0xa8, 0xb5, 0xca, 0x98, 0xcf, 0x21, 0xd6, 0x0a, 0x78, 0xe7, 0x35, 0xf2, 0x06, 0x20, - 0x7c, 0x2e, 0x24, 0x58, 0x6e, 0xaa, 0x0e, 0x9f, 0xbf, 0x0b, 0x0c, 0x7b, 0xca, 0x95, 0x8e, 0xdc, - 0x3a, 0xba, 0xc2, 0x66, 0x41, 0xe9, 0xbf, 0x00, 0x6e, 0x0b, 0x6d, 0x33, 0x1e, 0x6b, 0x90, 0x56, - 0x97, 0xce, 0xfd, 0x45, 0x62, 0x49, 0x7f, 0x1f, 0xdd, 0x5f, 0xcf, 0x3f, 0x77, 0xf2, 0x1e, 0xdd, - 0x1e, 0x80, 0x78, 0x91, 0x84, 0x91, 0xb4, 0x55, 0xdf, 0x40, 0x42, 0xc8, 0x47, 0x41, 0x2a, 0x75, - 0x62, 0xdc, 0xe7, 0x68, 0x37, 0x5a, 0xc2, 0xb9, 0x9f, 0x9d, 0xde, 0x01, 0x96, 0x21, 0xc5, 0xcb, - 0xc3, 0xc2, 0x4b, 0xe3, 0x49, 0xbb, 0x78, 0x99, 0xc3, 0xbf, 0xc0, 0xb0, 0xb0, 0xd9, 0xfb, 0xb6, - 0x81, 0x36, 0x07, 0x20, 0xdc, 0x4f, 0x0e, 0xba, 0xf9, 0xf7, 0x14, 0x1f, 0xe3, 0x4b, 0xdc, 0x4f, - 0xbc, 0xea, 0x9c, 0x9b, 0xfd, 0x7f, 0x5e, 0x5a, 0x1d, 0x8c, 0xfb, 0xc5, 0x41, 0xb7, 0xd6, 0x0d, - 0xe8, 0xf8, 0xb2, 0x12, 0x6b, 0x48, 0x9a, 0x27, 0xff, 0x81, 0xa4, 0x72, 0x7c, 0xf4, 0xf2, 0xeb, - 0xc4, 0x73, 0xce, 0x27, 0x9e, 0xf3, 0x73, 0xe2, 0x39, 0x1f, 0xa7, 0x5e, 0xed, 0x7c, 0xea, 0xd5, - 0xbe, 0x4f, 0xbd, 0xda, 0xab, 0x27, 0x42, 0xda, 0x51, 0x12, 0x62, 0xaa, 0xa3, 0xf2, 0x35, 0x92, - 0x85, 0xee, 0x83, 0xf9, 0x47, 0x91, 0xf6, 0xc8, 0xe9, 0xc5, 0xdf, 0xc2, 0x66, 0x31, 0x87, 0xf0, - 0x6a, 0xfe, 0xdc, 0x1e, 0xfe, 0x09, 0x00, 0x00, 0xff, 0xff, 0xd8, 0x5c, 0x30, 0xf0, 0x5e, 0x04, - 0x00, 0x00, + // 547 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x54, 0x4d, 0x6b, 0x13, 0x41, + 0x18, 0xce, 0xb6, 0xa8, 0xed, 0x34, 0x0a, 0x2e, 0x39, 0xa4, 0x31, 0x6e, 0x34, 0xa2, 0xf6, 0x50, + 0x77, 0x48, 0x3c, 0x88, 0x05, 0x0f, 0x69, 0xbd, 0x48, 0x09, 0xc8, 0x7a, 0x10, 0x3c, 0x18, 0x76, + 0x67, 0xc6, 0xc9, 0x60, 0x76, 0x66, 0x99, 0x77, 0x76, 0xed, 0xfe, 0x03, 0x8f, 0x7a, 0x12, 0x6f, + 0xfd, 0x07, 0xfe, 0x0d, 0xc1, 0x4b, 0x8f, 0x9e, 0x44, 0x92, 0x8b, 0x67, 0x7f, 0x81, 0x64, 0x3f, + 0x92, 0x2d, 0x86, 0x50, 0xc4, 0xdb, 0xfb, 0xf1, 0xcc, 0xf3, 0x3c, 0x2f, 0xef, 0xcc, 0xa0, 0x7d, + 0x21, 0x0d, 0xd3, 0x64, 0xec, 0x0b, 0x39, 0x02, 0x46, 0x62, 0x2d, 0x4c, 0x8a, 0x09, 0x49, 0x70, + 0xa4, 0x55, 0x22, 0x28, 0xd3, 0x38, 0xe9, 0x61, 0x73, 0xe2, 0x46, 0x5a, 0x19, 0x65, 0xdf, 0x59, + 0x81, 0x76, 0x09, 0x49, 0xdc, 0x12, 0xed, 0x26, 0xbd, 0x56, 0x9b, 0x2b, 0xc5, 0x27, 0x0c, 0xfb, + 0x91, 0xc0, 0xbe, 0x94, 0xca, 0xf8, 0x46, 0x28, 0x09, 0x39, 0x45, 0xab, 0xc1, 0x15, 0x57, 0x59, + 0x88, 0xe7, 0x51, 0x51, 0xdd, 0x25, 0x0a, 0x42, 0x05, 0xa3, 0xbc, 0x91, 0x27, 0x65, 0xab, 0xa0, + 0xcb, 0xb2, 0x20, 0x7e, 0x83, 0x7d, 0x99, 0x16, 0x2d, 0x2c, 0x02, 0x82, 0x27, 0x82, 0x8f, 0x0d, + 0x99, 0x08, 0x26, 0x0d, 0x60, 0xc3, 0x24, 0x65, 0x3a, 0x14, 0xd2, 0x64, 0xbe, 0x17, 0x59, 0x7e, + 0xa0, 0xfb, 0xc9, 0x42, 0x8d, 0x21, 0xf0, 0x01, 0x80, 0xe0, 0xf2, 0x48, 0x49, 0x88, 0x43, 0xa6, + 0x8f, 0x59, 0x6a, 0xef, 0xa2, 0xad, 0x7c, 0x2a, 0x41, 0x9b, 0xd6, 0x2d, 0x6b, 0x6f, 0xdb, 0xbb, + 0x92, 0xe5, 0xcf, 0xa8, 0xfd, 0x08, 0x5d, 0x2d, 0xa7, 0x1b, 0xf9, 0x94, 0xea, 0xe6, 0xc6, 0xbc, + 0x7f, 0x68, 0xff, 0xfe, 0xd1, 0xb9, 0x96, 0xfa, 0xe1, 0xe4, 0xa0, 0x3b, 0xaf, 0x32, 0x80, 0xae, + 0x57, 0x2f, 0x81, 0x03, 0x4a, 0xb5, 0x7d, 0x1b, 0xd5, 0x49, 0x21, 0x31, 0x7a, 0xcb, 0xd2, 0xe6, + 0x66, 0xc6, 0xbb, 0x43, 0x96, 0xb2, 0x07, 0x5b, 0xef, 0x4f, 0x3b, 0xb5, 0x5f, 0xa7, 0x9d, 0x5a, + 0xd7, 0x41, 0xed, 0x55, 0xc6, 0x3c, 0x06, 0x91, 0x92, 0xc0, 0xba, 0xaf, 0x91, 0x33, 0x04, 0xee, + 0x31, 0x2e, 0xc0, 0x30, 0x5d, 0x22, 0x3c, 0xf6, 0xce, 0xd7, 0xf4, 0x29, 0x93, 0x2a, 0xb4, 0x1b, + 0xe8, 0x12, 0x9d, 0x07, 0x85, 0xff, 0x3c, 0xb1, 0xdb, 0x68, 0x9b, 0xb2, 0x48, 0x81, 0x30, 0xaa, + 0x70, 0xee, 0x2d, 0x0b, 0x15, 0xfd, 0x3d, 0x74, 0x6f, 0x3d, 0xff, 0xc2, 0xc9, 0x67, 0x0b, 0xdd, + 0x1c, 0x02, 0x7f, 0x11, 0x07, 0xa1, 0x30, 0x25, 0x70, 0x28, 0x20, 0x60, 0x63, 0x3f, 0x11, 0x2a, + 0xd6, 0x73, 0x4d, 0xc8, 0xba, 0x86, 0xe9, 0xc2, 0xcd, 0xb2, 0x60, 0x3f, 0x47, 0xf5, 0xb0, 0x82, + 0xce, 0x4c, 0xed, 0xf4, 0xf7, 0x5d, 0x11, 0x10, 0xb7, 0xba, 0x4b, 0xb7, 0xb2, 0xbd, 0xa4, 0xe7, + 0x56, 0x15, 0xbc, 0x73, 0x0c, 0x95, 0x29, 0xee, 0xa3, 0xbb, 0x6b, 0xad, 0x95, 0x43, 0xf4, 0xbf, + 0x6d, 0xa0, 0xcd, 0x21, 0x70, 0xfb, 0xa3, 0x85, 0xae, 0xff, 0x7d, 0x1b, 0x1e, 0xbb, 0x17, 0xb8, + 0xe7, 0xee, 0xaa, 0x7d, 0xb5, 0x06, 0xff, 0x7c, 0xb4, 0xf4, 0x66, 0x7f, 0xb1, 0xd0, 0x8d, 0x75, + 0x8b, 0x3e, 0xba, 0xa8, 0xc4, 0x1a, 0x92, 0xd6, 0xf1, 0x7f, 0x20, 0x29, 0x1d, 0x1f, 0xbe, 0xfc, + 0x3a, 0x75, 0xac, 0xb3, 0xa9, 0x63, 0xfd, 0x9c, 0x3a, 0xd6, 0x87, 0x99, 0x53, 0x3b, 0x9b, 0x39, + 0xb5, 0xef, 0x33, 0xa7, 0xf6, 0xea, 0x09, 0x17, 0x66, 0x1c, 0x07, 0x2e, 0x51, 0x61, 0xf1, 0xaa, + 0xf1, 0x52, 0xf7, 0xc1, 0xe2, 0xc3, 0x49, 0xfa, 0xf8, 0xe4, 0xfc, 0xaf, 0x63, 0xd2, 0x88, 0x41, + 0x70, 0x39, 0x7b, 0xb6, 0x0f, 0xff, 0x04, 0x00, 0x00, 0xff, 0xff, 0x80, 0xfb, 0xc7, 0x36, 0xa6, + 0x04, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -555,11 +594,41 @@ func (m *MsgSubmitConsumerMisbehaviour) MarshalToSizedBuffer(dAtA []byte) (int, i = encodeVarintTx(dAtA, i, uint64(size)) } i-- + dAtA[i] = 0x12 + } + if len(m.Submitter) > 0 { + i -= len(m.Submitter) + copy(dAtA[i:], m.Submitter) + i = encodeVarintTx(dAtA, i, uint64(len(m.Submitter))) + i-- dAtA[i] = 0xa } return len(dAtA) - i, nil } +func (m *MsgSubmitConsumerMisbehaviourResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgSubmitConsumerMisbehaviourResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgSubmitConsumerMisbehaviourResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + func encodeVarintTx(dAtA []byte, offset int, v uint64) int { offset -= sovTx(v) base := offset @@ -633,6 +702,10 @@ func (m *MsgSubmitConsumerMisbehaviour) Size() (n int) { } var l int _ = l + l = len(m.Submitter) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } if m.Misbehaviour != nil { l = m.Misbehaviour.Size() n += 1 + l + sovTx(uint64(l)) @@ -640,6 +713,15 @@ func (m *MsgSubmitConsumerMisbehaviour) Size() (n int) { return n } +func (m *MsgSubmitConsumerMisbehaviourResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + func sovTx(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -1036,6 +1118,38 @@ func (m *MsgSubmitConsumerMisbehaviour) Unmarshal(dAtA []byte) error { } switch fieldNum { case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Submitter", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Submitter = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Misbehaviour", wireType) } @@ -1092,6 +1206,56 @@ func (m *MsgSubmitConsumerMisbehaviour) Unmarshal(dAtA []byte) error { } return nil } +func (m *MsgSubmitConsumerMisbehaviourResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgSubmitConsumerMisbehaviourResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgSubmitConsumerMisbehaviourResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipTx(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/ccv/types/events.go b/x/ccv/types/events.go index ba71e063f3..0bfb19b548 100644 --- a/x/ccv/types/events.go +++ b/x/ccv/types/events.go @@ -9,6 +9,7 @@ const ( EventTypeConsumerClientCreated = "consumer_client_created" EventTypeAssignConsumerKey = "assign_consumer_key" EventTypeRegisterConsumerRewardDenom = "register_consumer_reward_denom" + EventTypeSubmitConsumerMisbehaviour = "submit_consumer_misbehaviour" EventTypeExecuteConsumerChainSlash = "execute_consumer_chain_slash" EventTypeFeeDistribution = "fee_distribution" @@ -33,6 +34,8 @@ const ( AttributeUnbondingPeriod = "unbonding_period" AttributeProviderValidatorAddress = "provider_validator_address" AttributeConsumerConsensusPubKey = "consumer_consensus_pub_key" + AttributeSubmitterAddress = "provider_validator_address" + AttributeConsumerMisbehaviour = "consumer_consensus_pub_key" AttributeDistributionCurrentHeight = "current_distribution_height" AttributeDistributionNextHeight = "next_distribution_height" From 4750f1f555964ba940eb6a7e65dd8e15b6899de2 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Wed, 15 Mar 2023 17:13:00 +0100 Subject: [PATCH 04/45] format HandleConsumerMisbehaviour --- tests/integration/misbehaviour.go | 31 +++++- testutil/keeper/mocks.go | 15 +++ x/ccv/provider/keeper/misbehaviour.go | 148 ++++++++++++++++++-------- x/ccv/types/expected_keepers.go | 1 + 4 files changed, 148 insertions(+), 47 deletions(-) diff --git a/tests/integration/misbehaviour.go b/tests/integration/misbehaviour.go index b04c865d41..5ea94ce5ae 100644 --- a/tests/integration/misbehaviour.go +++ b/tests/integration/misbehaviour.go @@ -146,6 +146,35 @@ func (s *CCVTestSuite) TestCheckConsumerMisbehaviour() { }, func() {}, false, + }, { + "different trusted height shouldn't pass", + func() *ibctmtypes.Misbehaviour { + return &ibctmtypes.Misbehaviour{ + ClientId: s.path.EndpointA.ClientID, + Header1: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime, + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + Header2: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + heightPlus3, + s.providerCtx().BlockTime(), + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + } + }, + func() {}, + false, }, { "trusting period misbehavior should pass", func() *ibctmtypes.Misbehaviour { @@ -224,7 +253,7 @@ func (s *CCVTestSuite) TestCheckConsumerMisbehaviour() { Header2: s.consumerChain.CreateTMClientHeader( s.consumerChain.ChainID, int64(heightPlus5.RevisionHeight+1), - heightPlus3, + clientHeight, altTime, clientTMValset, clientTMValset, diff --git a/testutil/keeper/mocks.go b/testutil/keeper/mocks.go index 8dc42835b1..2e45e5c58d 100644 --- a/testutil/keeper/mocks.go +++ b/testutil/keeper/mocks.go @@ -707,6 +707,21 @@ func (mr *MockClientKeeperMockRecorder) CreateClient(ctx, clientState, consensus return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateClient", reflect.TypeOf((*MockClientKeeper)(nil).CreateClient), ctx, clientState, consensusState) } +// GetClientConsensusState mocks base method. +func (m *MockClientKeeper) GetClientConsensusState(ctx types.Context, clientID string, height exported.Height) (exported.ConsensusState, bool) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetClientConsensusState", ctx, clientID, height) + ret0, _ := ret[0].(exported.ConsensusState) + ret1, _ := ret[1].(bool) + return ret0, ret1 +} + +// GetClientConsensusState indicates an expected call of GetClientConsensusState. +func (mr *MockClientKeeperMockRecorder) GetClientConsensusState(ctx, clientID, height interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetClientConsensusState", reflect.TypeOf((*MockClientKeeper)(nil).GetClientConsensusState), ctx, clientID, height) +} + // GetClientState mocks base method. func (m *MockClientKeeper) GetClientState(ctx types.Context, clientID string) (exported.ClientState, bool) { m.ctrl.T.Helper() diff --git a/x/ccv/provider/keeper/misbehaviour.go b/x/ccv/provider/keeper/misbehaviour.go index 737b272729..5f01be3d50 100644 --- a/x/ccv/provider/keeper/misbehaviour.go +++ b/x/ccv/provider/keeper/misbehaviour.go @@ -2,6 +2,7 @@ package keeper import ( "fmt" + "time" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/ibc-go/v4/modules/core/exported" @@ -9,25 +10,52 @@ import ( tmtypes "github.com/tendermint/tendermint/types" ) -func (k Keeper) CheckConsumerMisbehaviour(ctx sdk.Context, misbeaviour ibctmtypes.Misbehaviour) error { +func (k Keeper) HandleConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) error { + if err := k.CheckConsumerMisbehaviour(ctx, misbehaviour); err != nil { + return err + } + + byzantineValidators, err := k.GetByzantineValidators(ctx, misbehaviour) + if err != nil { + return err + } + + logger := ctx.Logger() + logger.Info( + "confirmed equivocation", + "byzantine validators", byzantineValidators, + ) + + return nil +} + +func (k Keeper) CheckConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) error { + + clientID := misbehaviour.GetClientID() - clientID := misbeaviour.GetClientID() clientState, found := k.clientKeeper.GetClientState(ctx, clientID) if !found { return fmt.Errorf("types.ErrClientNotFound cannot check misbehaviour for client with ID %s", clientID) } - clientStore := k.clientKeeper.ClientStore(ctx, misbeaviour.GetClientID()) + clientStore := k.clientKeeper.ClientStore(ctx, misbehaviour.GetClientID()) if status := clientState.Status(ctx, clientStore, k.cdc); status != exported.Active { return fmt.Errorf("types.ErrClientNotActive cannot process misbehaviour for client (%s) with status %s", clientID, status) } - if err := misbeaviour.ValidateBasic(); err != nil { + if err := misbehaviour.ValidateBasic(); err != nil { return err } - clientState, err := clientState.CheckMisbehaviourAndUpdateState(ctx, k.cdc, clientStore, &misbeaviour) + trusted, conflicted := misbehaviour.Header1, misbehaviour.Header2 + + // A common trusted height is required to get the byzantine validators who signed both headers + if !trusted.TrustedHeight.EQ(conflicted.TrustedHeight) { + return fmt.Errorf("misbehaviour headers have different trusted height %d != %d", trusted.TrustedHeight, conflicted.TrustedHeight) + } + + clientState, err := clientState.CheckMisbehaviourAndUpdateState(ctx, k.cdc, clientStore, &misbehaviour) if err != nil { return err } @@ -35,61 +63,89 @@ func (k Keeper) CheckConsumerMisbehaviour(ctx sdk.Context, misbeaviour ibctmtype k.clientKeeper.SetClientState(ctx, clientID, clientState) k.Logger(ctx).Info("client frozen due to misbehaviour", "client-id", clientID) - // get byzantine validators - sh, err := tmtypes.SignedHeaderFromProto(misbeaviour.Header1.SignedHeader) + // TBD + // defer func() { + // telemetry.IncrCounterWithLabels( + // []string{"ibc", "client", "misbehaviour"}, + // 1, + // []metrics.Label{ + // telemetry.NewLabel(types.LabelClientType, misbehaviour.ClientType()), + // telemetry.NewLabel(types.LabelClientID, misbehaviour.GetClientID()), + // }, + // ) + // }() + + // EmitSubmitMisbehaviourEvent(ctx, clientID, clientState) + return nil +} + +func (k Keeper) GetByzantineValidators(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) ([]*tmtypes.Validator, error) { + + // + trusted, err := HeaderToLightBlock(*misbehaviour.Header1) if err != nil { - return err + return nil, err } - - vs, err := tmtypes.ValidatorSetFromProto(misbeaviour.Header1.ValidatorSet) + conflicted, err := HeaderToLightBlock(*misbehaviour.Header2) if err != nil { - return err + return nil, err } + commonHeight, commonTs, commonValset, err := k.GetCommonFromMisbehaviour(ctx, misbehaviour) + if err != nil { + return nil, err + } + ev := tmtypes.LightClientAttackEvidence{ - ConflictingBlock: &tmtypes.LightBlock{SignedHeader: sh, ValidatorSet: vs}, + ConflictingBlock: conflicted, } - h2, err := tmtypes.HeaderFromProto(misbeaviour.Header2.Header) - if err != nil { - return err + if ev.ConflictingHeaderIsInvalid(trusted.Header) { + ev.CommonHeight = int64(commonHeight) + ev.Timestamp = commonTs + ev.TotalVotingPower = commonValset.TotalVotingPower() + } else { + ev.CommonHeight = trusted.Header.Height + ev.Timestamp = trusted.Header.Time + ev.TotalVotingPower = trusted.ValidatorSet.TotalVotingPower() } - // WIP: return byzantine validators according to the light client commited + return ev.GetByzantineValidators(commonValset, trusted.SignedHeader), nil +} - // if this is an equivocation or amnesia attack, i.e. the validator sets are the same, then we - // return the height of the conflicting block else if it is a lunatic attack and the validator sets - // are not the same then we send the height of the common header. - if ev.ConflictingHeaderIsInvalid(&h2) { - ev.CommonHeight = misbeaviour.Header2.Header.Height - ev.Timestamp = misbeaviour.Header2.Header.Time - ev.TotalVotingPower = misbeaviour.Header2.ValidatorSet.TotalVotingPower - } else { - ev.CommonHeight = misbeaviour.Header1.Header.Height - ev.Timestamp = misbeaviour.Header1.Header.Time - ev.TotalVotingPower = misbeaviour.Header1.ValidatorSet.TotalVotingPower +func (k Keeper) GetCommonFromMisbehaviour(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) (int64, time.Time, *tmtypes.ValidatorSet, error) { + + // A common trusted height is required + commonHeight := misbehaviour.Header1.TrustedHeight + if !commonHeight.EQ(misbehaviour.Header2.TrustedHeight) { + return 0, time.Time{}, nil, fmt.Errorf("misbehaviour headers have different trusted height: %v , %v", commonHeight, misbehaviour.Header2.TrustedHeight) } - ev.ByzantineValidators = ev.GetByzantineValidators(vs, sh) - logger := ctx.Logger() + cs, ok := k.clientKeeper.GetClientConsensusState(ctx, misbehaviour.GetClientID(), misbehaviour.Header1.TrustedHeight) + if !ok { + return 0, time.Time{}, nil, fmt.Errorf("cannot find consensus state at trusted height %d for client %s", commonHeight, misbehaviour.GetClientID()) + } - logger.Info( - "confirmed equivocation", - "byzantine validators", ev.ByzantineValidators, - ) + vs, err := tmtypes.ValidatorSetFromProto(misbehaviour.Header1.ValidatorSet) + if err != nil { + return 0, time.Time{}, nil, err + } - // TBD - // defer func() { - // telemetry.IncrCounterWithLabels( - // []string{"ibc", "client", "misbehaviour"}, - // 1, - // []metrics.Label{ - // telemetry.NewLabel(types.LabelClientType, misbehaviour.ClientType()), - // telemetry.NewLabel(types.LabelClientID, misbehaviour.GetClientID()), - // }, - // ) - // }() + return int64(commonHeight.RevisionHeight), time.Unix(0, int64(cs.GetTimestamp())), vs, nil +} - // EmitSubmitMisbehaviourEvent(ctx, clientID, clientState) +func HeaderToLightBlock(h ibctmtypes.Header) (*tmtypes.LightBlock, error) { + sh, err := tmtypes.SignedHeaderFromProto(h.SignedHeader) + if err != nil { + return nil, err + } - return nil + vs, err := tmtypes.ValidatorSetFromProto(h.ValidatorSet) + if err != nil { + return nil, err + } + + return &tmtypes.LightBlock{ + SignedHeader: sh, + ValidatorSet: vs, + }, nil } diff --git a/x/ccv/types/expected_keepers.go b/x/ccv/types/expected_keepers.go index 5280cb8b8d..d24400b114 100644 --- a/x/ccv/types/expected_keepers.go +++ b/x/ccv/types/expected_keepers.go @@ -88,6 +88,7 @@ type ClientKeeper interface { GetSelfConsensusState(ctx sdk.Context, height ibcexported.Height) (ibcexported.ConsensusState, error) ClientStore(ctx sdk.Context, clientID string) sdk.KVStore SetClientState(ctx sdk.Context, clientID string, clientState ibcexported.ClientState) + GetClientConsensusState(ctx sdk.Context, clientID string, height ibcexported.Height) (ibcexported.ConsensusState, bool) } // DistributionKeeper defines the expected interface of the distribution keeper From 2e5f8afcbb418384353489f54f44781a6095bb87 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Mon, 13 Mar 2023 10:06:30 +0100 Subject: [PATCH 05/45] add tx handler --- .../ccv/provider/v1/tx.proto | 4 + x/ccv/provider/client/cli/tx.go | 41 +++++++ x/ccv/provider/keeper/misbehaviour.go | 77 +++++++----- x/ccv/provider/keeper/msg_server.go | 17 +++ x/ccv/provider/types/tx.pb.go | 112 ++++++++++++------ x/ccv/types/events.go | 9 +- 6 files changed, 187 insertions(+), 73 deletions(-) diff --git a/proto/interchain_security/ccv/provider/v1/tx.proto b/proto/interchain_security/ccv/provider/v1/tx.proto index f6d06b4bb3..5e256d097a 100644 --- a/proto/interchain_security/ccv/provider/v1/tx.proto +++ b/proto/interchain_security/ccv/provider/v1/tx.proto @@ -13,6 +13,7 @@ import "ibc/lightclients/tendermint/v1/tendermint.proto"; service Msg { rpc AssignConsumerKey(MsgAssignConsumerKey) returns (MsgAssignConsumerKeyResponse); rpc RegisterConsumerRewardDenom(MsgRegisterConsumerRewardDenom) returns (MsgRegisterConsumerRewardDenomResponse); + rpc SubmitConsumerMisbehaviour(MsgSubmitConsumerMisbehaviour) returns (MsgSubmitConsumerMisbehaviourResponse); } message MsgAssignConsumerKey { @@ -45,6 +46,9 @@ message MsgRegisterConsumerRewardDenom { // MsgRegisterConsumerRewardDenomResponse defines the Msg/RegisterConsumerRewardDenom response type. message MsgRegisterConsumerRewardDenomResponse {} // MsgSubmitConsumerMisbehaviour reports a consumer chain Misbehaviour + +// MsgSubmitConsumerMisbehaviour defines a message that reports a misbehaviour +// observed on a consumer chain message MsgSubmitConsumerMisbehaviour { option (gogoproto.equal) = false; option (gogoproto.goproto_getters) = false; diff --git a/x/ccv/provider/client/cli/tx.go b/x/ccv/provider/client/cli/tx.go index 73b1df34c3..87b4179b0b 100644 --- a/x/ccv/provider/client/cli/tx.go +++ b/x/ccv/provider/client/cli/tx.go @@ -12,6 +12,7 @@ import ( "github.com/cosmos/cosmos-sdk/version" sdk "github.com/cosmos/cosmos-sdk/types" + ibctmtypes "github.com/cosmos/ibc-go/v4/modules/light-clients/07-tendermint/types" "github.com/cosmos/interchain-security/v2/x/ccv/provider/types" ) @@ -27,6 +28,7 @@ func GetTxCmd() *cobra.Command { cmd.AddCommand(NewAssignConsumerKeyCmd()) cmd.AddCommand(NewRegisterConsumerRewardDenomCmd()) + cmd.AddCommand(NewSubmitConsumerMisbehaviourCmd()) return cmd } @@ -99,3 +101,42 @@ $ %s tx provider register-consumer-reward-denom untrn --from mykey return cmd } + +func NewSubmitConsumerMisbehaviourCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "submit-consumer-misbehaviour [misbeaviour]", + Short: "submit a light client misbehaviour for a consumer chain", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + txf := tx.NewFactoryCLI(clientCtx, cmd.Flags()). + WithTxConfig(clientCtx.TxConfig).WithAccountRetriever(clientCtx.AccountRetriever) + + submitter := clientCtx.GetFromAddress() + var misbehavior ibctmtypes.Misbehaviour + if err := clientCtx.Codec.UnmarshalInterfaceJSON([]byte(args[1]), &misbehavior); err != nil { + return err + } + + msg, err := types.NewMsgSubmitConsumerMisbehaviour(submitter, &misbehavior) + if err != nil { + return err + } + if err := msg.ValidateBasic(); err != nil { + return err + } + + return tx.GenerateOrBroadcastTxWithFactory(clientCtx, txf, msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + + _ = cmd.MarkFlagRequired(flags.FlagFrom) + + return cmd +} diff --git a/x/ccv/provider/keeper/misbehaviour.go b/x/ccv/provider/keeper/misbehaviour.go index 5f01be3d50..fcdb122602 100644 --- a/x/ccv/provider/keeper/misbehaviour.go +++ b/x/ccv/provider/keeper/misbehaviour.go @@ -10,58 +10,73 @@ import ( tmtypes "github.com/tendermint/tendermint/types" ) -func (k Keeper) HandleConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) error { - if err := k.CheckConsumerMisbehaviour(ctx, misbehaviour); err != nil { - return err - } - - byzantineValidators, err := k.GetByzantineValidators(ctx, misbehaviour) - if err != nil { - return err - } - - logger := ctx.Logger() - logger.Info( - "confirmed equivocation", - "byzantine validators", byzantineValidators, - ) - - return nil -} - -func (k Keeper) CheckConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) error { - - clientID := misbehaviour.GetClientID() +func (k Keeper) CheckConsumerMisbehaviour(ctx sdk.Context, misbeaviour ibctmtypes.Misbehaviour) error { + clientID := misbeaviour.GetClientID() clientState, found := k.clientKeeper.GetClientState(ctx, clientID) if !found { return fmt.Errorf("types.ErrClientNotFound cannot check misbehaviour for client with ID %s", clientID) } - clientStore := k.clientKeeper.ClientStore(ctx, misbehaviour.GetClientID()) + clientStore := k.clientKeeper.ClientStore(ctx, misbeaviour.GetClientID()) if status := clientState.Status(ctx, clientStore, k.cdc); status != exported.Active { return fmt.Errorf("types.ErrClientNotActive cannot process misbehaviour for client (%s) with status %s", clientID, status) } - if err := misbehaviour.ValidateBasic(); err != nil { + if err := misbeaviour.ValidateBasic(); err != nil { return err } - trusted, conflicted := misbehaviour.Header1, misbehaviour.Header2 + clientState, err := clientState.CheckMisbehaviourAndUpdateState(ctx, k.cdc, clientStore, &misbeaviour) + if err != nil { + return err + } - // A common trusted height is required to get the byzantine validators who signed both headers - if !trusted.TrustedHeight.EQ(conflicted.TrustedHeight) { - return fmt.Errorf("misbehaviour headers have different trusted height %d != %d", trusted.TrustedHeight, conflicted.TrustedHeight) + k.clientKeeper.SetClientState(ctx, clientID, clientState) + k.Logger(ctx).Info("client frozen due to misbehaviour", "client-id", clientID) + + // get byzantine validators + sh, err := tmtypes.SignedHeaderFromProto(misbeaviour.Header1.SignedHeader) + if err != nil { + return err } - clientState, err := clientState.CheckMisbehaviourAndUpdateState(ctx, k.cdc, clientStore, &misbehaviour) + vs, err := tmtypes.ValidatorSetFromProto(misbeaviour.Header1.ValidatorSet) if err != nil { return err } + ev := tmtypes.LightClientAttackEvidence{ + ConflictingBlock: &tmtypes.LightBlock{SignedHeader: sh, ValidatorSet: vs}, + } - k.clientKeeper.SetClientState(ctx, clientID, clientState) - k.Logger(ctx).Info("client frozen due to misbehaviour", "client-id", clientID) + h2, err := tmtypes.HeaderFromProto(misbeaviour.Header2.Header) + if err != nil { + return err + } + + // WIP: return byzantine validators according to the light client commited + + // if this is an equivocation or amnesia attack, i.e. the validator sets are the same, then we + // return the height of the conflicting block else if it is a lunatic attack and the validator sets + // are not the same then we send the height of the common header. + if ev.ConflictingHeaderIsInvalid(&h2) { + ev.CommonHeight = misbeaviour.Header2.Header.Height + ev.Timestamp = misbeaviour.Header2.Header.Time + ev.TotalVotingPower = misbeaviour.Header2.ValidatorSet.TotalVotingPower + } else { + ev.CommonHeight = misbeaviour.Header1.Header.Height + ev.Timestamp = misbeaviour.Header1.Header.Time + ev.TotalVotingPower = misbeaviour.Header1.ValidatorSet.TotalVotingPower + } + ev.ByzantineValidators = ev.GetByzantineValidators(vs, sh) + + logger := ctx.Logger() + + logger.Info( + "confirmed equivocation", + "byzantine validators", ev.ByzantineValidators, + ) // TBD // defer func() { diff --git a/x/ccv/provider/keeper/msg_server.go b/x/ccv/provider/keeper/msg_server.go index ea5ff66220..f675423c69 100644 --- a/x/ccv/provider/keeper/msg_server.go +++ b/x/ccv/provider/keeper/msg_server.go @@ -126,3 +126,20 @@ func (k msgServer) RegisterConsumerRewardDenom(goCtx context.Context, msg *types return &types.MsgRegisterConsumerRewardDenomResponse{}, nil } + +func (k msgServer) SubmitConsumerMisbehaviour(goCtx context.Context, msg *types.MsgSubmitConsumerMisbehaviour) (*types.MsgSubmitConsumerMisbehaviourResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + if err := k.Keeper.CheckConsumerMisbehaviour(ctx, *msg.Misbehaviour); err != nil { + return &types.MsgSubmitConsumerMisbehaviourResponse{}, err + } + + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + ccvtypes.EventTypeSubmitConsumerMisbehaviour, + sdk.NewAttribute(ccvtypes.AttributeConsumerMisbehaviour, msg.Misbehaviour.String()), + sdk.NewAttribute(ccvtypes.AttributeSubmitterAddress, msg.Submitter), + ), + }) + + return &types.MsgSubmitConsumerMisbehaviourResponse{}, nil +} diff --git a/x/ccv/provider/types/tx.pb.go b/x/ccv/provider/types/tx.pb.go index 4a82c5afc6..61b59ecd8c 100644 --- a/x/ccv/provider/types/tx.pb.go +++ b/x/ccv/provider/types/tx.pb.go @@ -192,7 +192,8 @@ func (m *MsgRegisterConsumerRewardDenomResponse) XXX_DiscardUnknown() { var xxx_messageInfo_MsgRegisterConsumerRewardDenomResponse proto.InternalMessageInfo -// MsgSubmitConsumerMisbehaviour reports a consumer chain Misbehaviour +// MsgSubmitConsumerMisbehaviour defines a message that reports a misbehaviour +// observed on a consumer chain type MsgSubmitConsumerMisbehaviour struct { Submitter string `protobuf:"bytes,1,opt,name=submitter,proto3" json:"submitter,omitempty"` // The Misbehaviour of the consumer chain wrapping @@ -283,42 +284,43 @@ func init() { } var fileDescriptor_43221a4391e9fbf4 = []byte{ - // 547 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x54, 0x4d, 0x6b, 0x13, 0x41, - 0x18, 0xce, 0xb6, 0xa8, 0xed, 0x34, 0x0a, 0x2e, 0x39, 0xa4, 0x31, 0x6e, 0x34, 0xa2, 0xf6, 0x50, - 0x77, 0x48, 0x3c, 0x88, 0x05, 0x0f, 0x69, 0xbd, 0x48, 0x09, 0xc8, 0x7a, 0x10, 0x3c, 0x18, 0x76, - 0x67, 0xc6, 0xc9, 0x60, 0x76, 0x66, 0x99, 0x77, 0x76, 0xed, 0xfe, 0x03, 0x8f, 0x7a, 0x12, 0x6f, - 0xfd, 0x07, 0xfe, 0x0d, 0xc1, 0x4b, 0x8f, 0x9e, 0x44, 0x92, 0x8b, 0x67, 0x7f, 0x81, 0x64, 0x3f, - 0x92, 0x2d, 0x86, 0x50, 0xc4, 0xdb, 0xfb, 0xf1, 0xcc, 0xf3, 0x3c, 0x2f, 0xef, 0xcc, 0xa0, 0x7d, - 0x21, 0x0d, 0xd3, 0x64, 0xec, 0x0b, 0x39, 0x02, 0x46, 0x62, 0x2d, 0x4c, 0x8a, 0x09, 0x49, 0x70, - 0xa4, 0x55, 0x22, 0x28, 0xd3, 0x38, 0xe9, 0x61, 0x73, 0xe2, 0x46, 0x5a, 0x19, 0x65, 0xdf, 0x59, - 0x81, 0x76, 0x09, 0x49, 0xdc, 0x12, 0xed, 0x26, 0xbd, 0x56, 0x9b, 0x2b, 0xc5, 0x27, 0x0c, 0xfb, - 0x91, 0xc0, 0xbe, 0x94, 0xca, 0xf8, 0x46, 0x28, 0x09, 0x39, 0x45, 0xab, 0xc1, 0x15, 0x57, 0x59, - 0x88, 0xe7, 0x51, 0x51, 0xdd, 0x25, 0x0a, 0x42, 0x05, 0xa3, 0xbc, 0x91, 0x27, 0x65, 0xab, 0xa0, - 0xcb, 0xb2, 0x20, 0x7e, 0x83, 0x7d, 0x99, 0x16, 0x2d, 0x2c, 0x02, 0x82, 0x27, 0x82, 0x8f, 0x0d, - 0x99, 0x08, 0x26, 0x0d, 0x60, 0xc3, 0x24, 0x65, 0x3a, 0x14, 0xd2, 0x64, 0xbe, 0x17, 0x59, 0x7e, - 0xa0, 0xfb, 0xc9, 0x42, 0x8d, 0x21, 0xf0, 0x01, 0x80, 0xe0, 0xf2, 0x48, 0x49, 0x88, 0x43, 0xa6, - 0x8f, 0x59, 0x6a, 0xef, 0xa2, 0xad, 0x7c, 0x2a, 0x41, 0x9b, 0xd6, 0x2d, 0x6b, 0x6f, 0xdb, 0xbb, - 0x92, 0xe5, 0xcf, 0xa8, 0xfd, 0x08, 0x5d, 0x2d, 0xa7, 0x1b, 0xf9, 0x94, 0xea, 0xe6, 0xc6, 0xbc, - 0x7f, 0x68, 0xff, 0xfe, 0xd1, 0xb9, 0x96, 0xfa, 0xe1, 0xe4, 0xa0, 0x3b, 0xaf, 0x32, 0x80, 0xae, - 0x57, 0x2f, 0x81, 0x03, 0x4a, 0xb5, 0x7d, 0x1b, 0xd5, 0x49, 0x21, 0x31, 0x7a, 0xcb, 0xd2, 0xe6, - 0x66, 0xc6, 0xbb, 0x43, 0x96, 0xb2, 0x07, 0x5b, 0xef, 0x4f, 0x3b, 0xb5, 0x5f, 0xa7, 0x9d, 0x5a, - 0xd7, 0x41, 0xed, 0x55, 0xc6, 0x3c, 0x06, 0x91, 0x92, 0xc0, 0xba, 0xaf, 0x91, 0x33, 0x04, 0xee, - 0x31, 0x2e, 0xc0, 0x30, 0x5d, 0x22, 0x3c, 0xf6, 0xce, 0xd7, 0xf4, 0x29, 0x93, 0x2a, 0xb4, 0x1b, - 0xe8, 0x12, 0x9d, 0x07, 0x85, 0xff, 0x3c, 0xb1, 0xdb, 0x68, 0x9b, 0xb2, 0x48, 0x81, 0x30, 0xaa, - 0x70, 0xee, 0x2d, 0x0b, 0x15, 0xfd, 0x3d, 0x74, 0x6f, 0x3d, 0xff, 0xc2, 0xc9, 0x67, 0x0b, 0xdd, - 0x1c, 0x02, 0x7f, 0x11, 0x07, 0xa1, 0x30, 0x25, 0x70, 0x28, 0x20, 0x60, 0x63, 0x3f, 0x11, 0x2a, - 0xd6, 0x73, 0x4d, 0xc8, 0xba, 0x86, 0xe9, 0xc2, 0xcd, 0xb2, 0x60, 0x3f, 0x47, 0xf5, 0xb0, 0x82, - 0xce, 0x4c, 0xed, 0xf4, 0xf7, 0x5d, 0x11, 0x10, 0xb7, 0xba, 0x4b, 0xb7, 0xb2, 0xbd, 0xa4, 0xe7, - 0x56, 0x15, 0xbc, 0x73, 0x0c, 0x95, 0x29, 0xee, 0xa3, 0xbb, 0x6b, 0xad, 0x95, 0x43, 0xf4, 0xbf, - 0x6d, 0xa0, 0xcd, 0x21, 0x70, 0xfb, 0xa3, 0x85, 0xae, 0xff, 0x7d, 0x1b, 0x1e, 0xbb, 0x17, 0xb8, - 0xe7, 0xee, 0xaa, 0x7d, 0xb5, 0x06, 0xff, 0x7c, 0xb4, 0xf4, 0x66, 0x7f, 0xb1, 0xd0, 0x8d, 0x75, - 0x8b, 0x3e, 0xba, 0xa8, 0xc4, 0x1a, 0x92, 0xd6, 0xf1, 0x7f, 0x20, 0x29, 0x1d, 0x1f, 0xbe, 0xfc, - 0x3a, 0x75, 0xac, 0xb3, 0xa9, 0x63, 0xfd, 0x9c, 0x3a, 0xd6, 0x87, 0x99, 0x53, 0x3b, 0x9b, 0x39, - 0xb5, 0xef, 0x33, 0xa7, 0xf6, 0xea, 0x09, 0x17, 0x66, 0x1c, 0x07, 0x2e, 0x51, 0x61, 0xf1, 0xaa, - 0xf1, 0x52, 0xf7, 0xc1, 0xe2, 0xc3, 0x49, 0xfa, 0xf8, 0xe4, 0xfc, 0xaf, 0x63, 0xd2, 0x88, 0x41, - 0x70, 0x39, 0x7b, 0xb6, 0x0f, 0xff, 0x04, 0x00, 0x00, 0xff, 0xff, 0x80, 0xfb, 0xc7, 0x36, 0xa6, - 0x04, 0x00, 0x00, + // 568 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x54, 0x3f, 0x6f, 0x13, 0x4f, + 0x10, 0xf5, 0xfd, 0xa2, 0x1f, 0x24, 0x9b, 0x80, 0xc4, 0xc9, 0x85, 0x73, 0x98, 0x33, 0x18, 0x01, + 0x29, 0xc2, 0xae, 0x6c, 0x0a, 0x44, 0x24, 0x0a, 0x3b, 0x34, 0x10, 0x59, 0x42, 0x47, 0x81, 0x44, + 0x81, 0x75, 0xb7, 0xbb, 0xac, 0x57, 0xf8, 0x76, 0x4f, 0xbb, 0x7b, 0x47, 0xee, 0x1b, 0x50, 0x42, + 0x85, 0xe8, 0xf2, 0x01, 0x90, 0xf8, 0x1a, 0x94, 0x29, 0xa9, 0x10, 0xb2, 0x1b, 0x6a, 0x4a, 0x2a, + 0xe4, 0xfb, 0x63, 0x5f, 0x84, 0xb1, 0x2c, 0xa0, 0xdb, 0x99, 0x79, 0xfb, 0xde, 0x1b, 0xcd, 0x68, + 0xc0, 0x3e, 0x17, 0x86, 0x2a, 0x3c, 0xf2, 0xb9, 0x18, 0x6a, 0x8a, 0x63, 0xc5, 0x4d, 0x8a, 0x30, + 0x4e, 0x50, 0xa4, 0x64, 0xc2, 0x09, 0x55, 0x28, 0xe9, 0x20, 0x73, 0x0c, 0x23, 0x25, 0x8d, 0xb4, + 0xaf, 0x2f, 0x41, 0x43, 0x8c, 0x13, 0x58, 0xa2, 0x61, 0xd2, 0x71, 0x9a, 0x4c, 0x4a, 0x36, 0xa6, + 0xc8, 0x8f, 0x38, 0xf2, 0x85, 0x90, 0xc6, 0x37, 0x5c, 0x0a, 0x9d, 0x53, 0x38, 0x75, 0x26, 0x99, + 0xcc, 0x9e, 0x68, 0xf6, 0x2a, 0xb2, 0xbb, 0x58, 0xea, 0x50, 0xea, 0x61, 0x5e, 0xc8, 0x83, 0xb2, + 0x54, 0xd0, 0x65, 0x51, 0x10, 0xbf, 0x40, 0xbe, 0x48, 0x8b, 0x12, 0xe2, 0x01, 0x46, 0x63, 0xce, + 0x46, 0x06, 0x8f, 0x39, 0x15, 0x46, 0x23, 0x43, 0x05, 0xa1, 0x2a, 0xe4, 0xc2, 0x64, 0xbe, 0xe7, + 0x51, 0xfe, 0xa1, 0xfd, 0xce, 0x02, 0xf5, 0x81, 0x66, 0x3d, 0xad, 0x39, 0x13, 0x87, 0x52, 0xe8, + 0x38, 0xa4, 0xea, 0x88, 0xa6, 0xf6, 0x2e, 0xd8, 0xcc, 0xbb, 0xe2, 0xa4, 0x61, 0x5d, 0xb5, 0xf6, + 0xb6, 0xbc, 0xf3, 0x59, 0xfc, 0x90, 0xd8, 0x77, 0xc1, 0x85, 0xb2, 0xbb, 0xa1, 0x4f, 0x88, 0x6a, + 0xfc, 0x37, 0xab, 0xf7, 0xed, 0xef, 0x5f, 0x5a, 0x17, 0x53, 0x3f, 0x1c, 0x1f, 0xb4, 0x67, 0x59, + 0xaa, 0x75, 0xdb, 0xdb, 0x29, 0x81, 0x3d, 0x42, 0x94, 0x7d, 0x0d, 0xec, 0xe0, 0x42, 0x62, 0xf8, + 0x92, 0xa6, 0x8d, 0x8d, 0x8c, 0x77, 0x1b, 0x2f, 0x64, 0x0f, 0x36, 0x5f, 0x9f, 0xb4, 0x6a, 0xdf, + 0x4e, 0x5a, 0xb5, 0xb6, 0x0b, 0x9a, 0xcb, 0x8c, 0x79, 0x54, 0x47, 0x52, 0x68, 0xda, 0x7e, 0x0e, + 0xdc, 0x81, 0x66, 0x1e, 0x65, 0x5c, 0x1b, 0xaa, 0x4a, 0x84, 0x47, 0x5f, 0xf9, 0x8a, 0x3c, 0xa0, + 0x42, 0x86, 0x76, 0x1d, 0xfc, 0x4f, 0x66, 0x8f, 0xc2, 0x7f, 0x1e, 0xd8, 0x4d, 0xb0, 0x45, 0x68, + 0x24, 0x35, 0x37, 0xb2, 0x70, 0xee, 0x2d, 0x12, 0x15, 0xfd, 0x3d, 0x70, 0x73, 0x35, 0xff, 0xdc, + 0xc9, 0x7b, 0x0b, 0x5c, 0x19, 0x68, 0xf6, 0x24, 0x0e, 0x42, 0x6e, 0x4a, 0xe0, 0x80, 0xeb, 0x80, + 0x8e, 0xfc, 0x84, 0xcb, 0x58, 0xcd, 0x34, 0x75, 0x56, 0x35, 0x54, 0x15, 0x6e, 0x16, 0x09, 0xfb, + 0x31, 0xd8, 0x09, 0x2b, 0xe8, 0xcc, 0xd4, 0x76, 0x77, 0x1f, 0xf2, 0x00, 0xc3, 0xea, 0x2c, 0x61, + 0x65, 0x7a, 0x49, 0x07, 0x56, 0x15, 0xbc, 0x33, 0x0c, 0x95, 0x2e, 0x6e, 0x81, 0x1b, 0x2b, 0xad, + 0x95, 0x4d, 0x74, 0x7f, 0x6c, 0x80, 0x8d, 0x81, 0x66, 0xf6, 0x5b, 0x0b, 0x5c, 0xfa, 0x75, 0x1b, + 0xee, 0xc1, 0x35, 0xf6, 0x1c, 0x2e, 0x9b, 0x97, 0xd3, 0xfb, 0xe3, 0xaf, 0xa5, 0x37, 0xfb, 0xa3, + 0x05, 0x2e, 0xaf, 0x1a, 0xf4, 0xe1, 0xba, 0x12, 0x2b, 0x48, 0x9c, 0xa3, 0x7f, 0x40, 0x32, 0x77, + 0xfc, 0xc1, 0x02, 0xce, 0x8a, 0x7d, 0xe8, 0xaf, 0xab, 0xf5, 0x7b, 0x0e, 0xe7, 0xd1, 0xdf, 0x73, + 0x94, 0x76, 0xfb, 0x4f, 0x3f, 0x4d, 0x5c, 0xeb, 0x74, 0xe2, 0x5a, 0x5f, 0x27, 0xae, 0xf5, 0x66, + 0xea, 0xd6, 0x4e, 0xa7, 0x6e, 0xed, 0xf3, 0xd4, 0xad, 0x3d, 0xbb, 0xcf, 0xb8, 0x19, 0xc5, 0x01, + 0xc4, 0x32, 0x2c, 0x8e, 0x10, 0x5a, 0xc8, 0xde, 0x9e, 0xdf, 0xc7, 0xa4, 0x8b, 0x8e, 0xcf, 0x1e, + 0x49, 0x93, 0x46, 0x54, 0x07, 0xe7, 0xb2, 0x2b, 0x73, 0xe7, 0x67, 0x00, 0x00, 0x00, 0xff, 0xff, + 0xe3, 0x4f, 0x57, 0x26, 0x55, 0x05, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -335,6 +337,7 @@ const _ = grpc.SupportPackageIsVersion4 type MsgClient interface { AssignConsumerKey(ctx context.Context, in *MsgAssignConsumerKey, opts ...grpc.CallOption) (*MsgAssignConsumerKeyResponse, error) RegisterConsumerRewardDenom(ctx context.Context, in *MsgRegisterConsumerRewardDenom, opts ...grpc.CallOption) (*MsgRegisterConsumerRewardDenomResponse, error) + SubmitConsumerMisbehaviour(ctx context.Context, in *MsgSubmitConsumerMisbehaviour, opts ...grpc.CallOption) (*MsgSubmitConsumerMisbehaviourResponse, error) } type msgClient struct { @@ -363,10 +366,20 @@ func (c *msgClient) RegisterConsumerRewardDenom(ctx context.Context, in *MsgRegi return out, nil } +func (c *msgClient) SubmitConsumerMisbehaviour(ctx context.Context, in *MsgSubmitConsumerMisbehaviour, opts ...grpc.CallOption) (*MsgSubmitConsumerMisbehaviourResponse, error) { + out := new(MsgSubmitConsumerMisbehaviourResponse) + err := c.cc.Invoke(ctx, "/interchain_security.ccv.provider.v1.Msg/SubmitConsumerMisbehaviour", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // MsgServer is the server API for Msg service. type MsgServer interface { AssignConsumerKey(context.Context, *MsgAssignConsumerKey) (*MsgAssignConsumerKeyResponse, error) RegisterConsumerRewardDenom(context.Context, *MsgRegisterConsumerRewardDenom) (*MsgRegisterConsumerRewardDenomResponse, error) + SubmitConsumerMisbehaviour(context.Context, *MsgSubmitConsumerMisbehaviour) (*MsgSubmitConsumerMisbehaviourResponse, error) } // UnimplementedMsgServer can be embedded to have forward compatible implementations. @@ -379,6 +392,9 @@ func (*UnimplementedMsgServer) AssignConsumerKey(ctx context.Context, req *MsgAs func (*UnimplementedMsgServer) RegisterConsumerRewardDenom(ctx context.Context, req *MsgRegisterConsumerRewardDenom) (*MsgRegisterConsumerRewardDenomResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method RegisterConsumerRewardDenom not implemented") } +func (*UnimplementedMsgServer) SubmitConsumerMisbehaviour(ctx context.Context, req *MsgSubmitConsumerMisbehaviour) (*MsgSubmitConsumerMisbehaviourResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SubmitConsumerMisbehaviour not implemented") +} func RegisterMsgServer(s grpc1.Server, srv MsgServer) { s.RegisterService(&_Msg_serviceDesc, srv) @@ -420,6 +436,24 @@ func _Msg_RegisterConsumerRewardDenom_Handler(srv interface{}, ctx context.Conte return interceptor(ctx, in, info, handler) } +func _Msg_SubmitConsumerMisbehaviour_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgSubmitConsumerMisbehaviour) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).SubmitConsumerMisbehaviour(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/interchain_security.ccv.provider.v1.Msg/SubmitConsumerMisbehaviour", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).SubmitConsumerMisbehaviour(ctx, req.(*MsgSubmitConsumerMisbehaviour)) + } + return interceptor(ctx, in, info, handler) +} + var _Msg_serviceDesc = grpc.ServiceDesc{ ServiceName: "interchain_security.ccv.provider.v1.Msg", HandlerType: (*MsgServer)(nil), @@ -432,6 +466,10 @@ var _Msg_serviceDesc = grpc.ServiceDesc{ MethodName: "RegisterConsumerRewardDenom", Handler: _Msg_RegisterConsumerRewardDenom_Handler, }, + { + MethodName: "SubmitConsumerMisbehaviour", + Handler: _Msg_SubmitConsumerMisbehaviour_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "interchain_security/ccv/provider/v1/tx.proto", diff --git a/x/ccv/types/events.go b/x/ccv/types/events.go index 0bfb19b548..99b1e05d15 100644 --- a/x/ccv/types/events.go +++ b/x/ccv/types/events.go @@ -10,11 +10,10 @@ const ( EventTypeAssignConsumerKey = "assign_consumer_key" EventTypeRegisterConsumerRewardDenom = "register_consumer_reward_denom" EventTypeSubmitConsumerMisbehaviour = "submit_consumer_misbehaviour" - - EventTypeExecuteConsumerChainSlash = "execute_consumer_chain_slash" - EventTypeFeeDistribution = "fee_distribution" - EventTypeConsumerSlashRequest = "consumer_slash_request" - EventTypeVSCMatured = "vsc_matured" + EventTypeExecuteConsumerChainSlash = "execute_consumer_chain_slash" + EventTypeFeeDistribution = "fee_distribution" + EventTypeConsumerSlashRequest = "consumer_slash_request" + EventTypeVSCMatured = "vsc_matured" AttributeKeyAckSuccess = "success" AttributeKeyAck = "acknowledgement" From b076fd4c6055377e17f02832733e7d4c333881a0 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Wed, 22 Mar 2023 13:33:56 +0100 Subject: [PATCH 06/45] add debugging stuff --- tests/integration/misbehaviour.go | 42 ++++++++++++++++++++++++++++++ testutil/integration/debug_test.go | 4 +++ 2 files changed, 46 insertions(+) diff --git a/tests/integration/misbehaviour.go b/tests/integration/misbehaviour.go index 5ea94ce5ae..a442001cc7 100644 --- a/tests/integration/misbehaviour.go +++ b/tests/integration/misbehaviour.go @@ -1,8 +1,10 @@ package integration import ( + "fmt" "time" + clienttypes "github.com/cosmos/ibc-go/v4/modules/core/02-client/types" ibcclientypes "github.com/cosmos/ibc-go/v4/modules/core/02-client/types" commitmenttypes "github.com/cosmos/ibc-go/v4/modules/core/23-commitment/types" ibctmtypes "github.com/cosmos/ibc-go/v4/modules/light-clients/07-tendermint/types" @@ -420,3 +422,43 @@ func (s *CCVTestSuite) TestCheckConsumerMisbehaviour() { }) } } + +func (s *CCVTestSuite) TestGetByzantineValidators() { + + s.SetupCCVChannel(s.path) + // required to have the consumer client revision height greater than 0 + s.SendEmptyVSCPacket() + s.consumerCtx().BlockHeight() + // get consumer client state + // consumerClientState := s.providerChain.GetClientState(s.path.EndpointA.ClientID) + + // create two conflicting headers and forge them + // commit new block on consumer + + s.coordinator.CommitBlock(s.consumerChain) + + // get trusted height from client state + trustedHeight := s.providerChain.GetClientState(s.path.EndpointA.ClientID).GetLatestHeight().(clienttypes.Height) + tmTrustedVals := s.consumerChain.Vals + // get last consumer header + header := s.consumerChain.LastHeader + + header.TrustedHeight = trustedHeight + trustedVals, err := tmTrustedVals.ToProto() + s.NoError(err) + header.TrustedValidators = trustedVals + + msg, err := clienttypes.NewMsgUpdateClient( + s.path.EndpointB.ClientID, header, + s.path.EndpointB.Chain.SenderAccount.GetAddress().String(), + ) + s.NoError(err) + + header2 := *header + header2.SignedHeader.Commit.BlockID.Hash = []byte("forge_hash") + + _, err = s.providerChain.SendMsgs(msg) + s.NoError(err) + + fmt.Printf("%+v\n", msg.Header) +} diff --git a/testutil/integration/debug_test.go b/testutil/integration/debug_test.go index 7dd2b78777..149915282b 100644 --- a/testutil/integration/debug_test.go +++ b/testutil/integration/debug_test.go @@ -264,3 +264,7 @@ func TestRecycleTransferChannel(t *testing.T) { func TestCheckConsumerMisbehaviour(t *testing.T) { runCCVTestByName(t, "TestCheckConsumerMisbehaviour") } + +func TestGetByzantineValidators(t *testing.T) { + runCCVTestByName(t, "TestGetByzantineValidators") +} From 457fe205c692abf4f36ea1245185b31bd08a9004 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Thu, 4 May 2023 09:36:36 +0200 Subject: [PATCH 07/45] Add misbehaviour handler --- x/ccv/provider/handler.go | 3 +++ x/ccv/provider/keeper/misbehaviour.go | 8 ++------ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/x/ccv/provider/handler.go b/x/ccv/provider/handler.go index dc0c8cbc4f..6fa38b5ddf 100644 --- a/x/ccv/provider/handler.go +++ b/x/ccv/provider/handler.go @@ -21,6 +21,9 @@ func NewHandler(k *keeper.Keeper) sdk.Handler { case *types.MsgRegisterConsumerRewardDenom: res, err := msgServer.RegisterConsumerRewardDenom(sdk.WrapSDKContext(ctx), msg) return sdk.WrapServiceResult(ctx, res, err) + case *types.MsgSubmitConsumerMisbehaviour: + res, err := msgServer.SubmitConsumerMisbehaviour(sdk.WrapSDKContext(ctx), msg) + return sdk.WrapServiceResult(ctx, res, err) default: return nil, errorsmod.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized %s message type: %T", types.ModuleName, msg) } diff --git a/x/ccv/provider/keeper/misbehaviour.go b/x/ccv/provider/keeper/misbehaviour.go index fcdb122602..879670063e 100644 --- a/x/ccv/provider/keeper/misbehaviour.go +++ b/x/ccv/provider/keeper/misbehaviour.go @@ -11,7 +11,6 @@ import ( ) func (k Keeper) CheckConsumerMisbehaviour(ctx sdk.Context, misbeaviour ibctmtypes.Misbehaviour) error { - clientID := misbeaviour.GetClientID() clientState, found := k.clientKeeper.GetClientState(ctx, clientID) if !found { @@ -55,7 +54,7 @@ func (k Keeper) CheckConsumerMisbehaviour(ctx sdk.Context, misbeaviour ibctmtype return err } - // WIP: return byzantine validators according to the light client commited + // WIP: return byzantine validators according to the light client committed // if this is an equivocation or amnesia attack, i.e. the validator sets are the same, then we // return the height of the conflicting block else if it is a lunatic attack and the validator sets @@ -95,8 +94,6 @@ func (k Keeper) CheckConsumerMisbehaviour(ctx sdk.Context, misbeaviour ibctmtype } func (k Keeper) GetByzantineValidators(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) ([]*tmtypes.Validator, error) { - - // trusted, err := HeaderToLightBlock(*misbehaviour.Header1) if err != nil { return nil, err @@ -115,7 +112,7 @@ func (k Keeper) GetByzantineValidators(ctx sdk.Context, misbehaviour ibctmtypes. } if ev.ConflictingHeaderIsInvalid(trusted.Header) { - ev.CommonHeight = int64(commonHeight) + ev.CommonHeight = commonHeight ev.Timestamp = commonTs ev.TotalVotingPower = commonValset.TotalVotingPower() } else { @@ -128,7 +125,6 @@ func (k Keeper) GetByzantineValidators(ctx sdk.Context, misbehaviour ibctmtypes. } func (k Keeper) GetCommonFromMisbehaviour(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) (int64, time.Time, *tmtypes.ValidatorSet, error) { - // A common trusted height is required commonHeight := misbehaviour.Header1.TrustedHeight if !commonHeight.EQ(misbehaviour.Header2.TrustedHeight) { From ae522e125350275a2813c8626835159685686e0c Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Wed, 3 May 2023 11:12:00 +0200 Subject: [PATCH 08/45] create message for consumer double voting evidence --- .../ccv/provider/v1/tx.proto | 1 + testutil/keeper/mocks.go | 2 +- .../proto/tendermint/types/evidence.proto | 38 ++++++++++ x/ccv/provider/keeper/misbehaviour.go | 67 ++++++++++++---- x/ccv/provider/types/tx.pb.go | 76 ++++++++++--------- 5 files changed, 132 insertions(+), 52 deletions(-) create mode 100644 third_party/proto/tendermint/types/evidence.proto diff --git a/proto/interchain_security/ccv/provider/v1/tx.proto b/proto/interchain_security/ccv/provider/v1/tx.proto index 5e256d097a..1bcfad7cbc 100644 --- a/proto/interchain_security/ccv/provider/v1/tx.proto +++ b/proto/interchain_security/ccv/provider/v1/tx.proto @@ -8,6 +8,7 @@ import "gogoproto/gogo.proto"; import "cosmos_proto/cosmos.proto"; import "google/protobuf/any.proto"; import "ibc/lightclients/tendermint/v1/tendermint.proto"; +import "tendermint/types/evidence.proto"; // Msg defines the Msg service. service Msg { diff --git a/testutil/keeper/mocks.go b/testutil/keeper/mocks.go index 2e45e5c58d..dc01a7f094 100644 --- a/testutil/keeper/mocks.go +++ b/testutil/keeper/mocks.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: ./x/ccv/types/expected_keepers.go +// Source: x/ccv/types/expected_keepers.go // Package keeper is a generated GoMock package. package keeper diff --git a/third_party/proto/tendermint/types/evidence.proto b/third_party/proto/tendermint/types/evidence.proto new file mode 100644 index 0000000000..451b8dca3c --- /dev/null +++ b/third_party/proto/tendermint/types/evidence.proto @@ -0,0 +1,38 @@ +syntax = "proto3"; +package tendermint.types; + +option go_package = "github.com/tendermint/tendermint/proto/tendermint/types"; + +import "gogoproto/gogo.proto"; +import "google/protobuf/timestamp.proto"; +import "tendermint/types/types.proto"; +import "tendermint/types/validator.proto"; + +message Evidence { + oneof sum { + DuplicateVoteEvidence duplicate_vote_evidence = 1; + LightClientAttackEvidence light_client_attack_evidence = 2; + } +} + +// DuplicateVoteEvidence contains evidence of a validator signed two conflicting votes. +message DuplicateVoteEvidence { + tendermint.types.Vote vote_a = 1; + tendermint.types.Vote vote_b = 2; + int64 total_voting_power = 3; + int64 validator_power = 4; + google.protobuf.Timestamp timestamp = 5 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; +} + +// LightClientAttackEvidence contains evidence of a set of validators attempting to mislead a light client. +message LightClientAttackEvidence { + tendermint.types.LightBlock conflicting_block = 1; + int64 common_height = 2; + repeated tendermint.types.Validator byzantine_validators = 3; + int64 total_voting_power = 4; + google.protobuf.Timestamp timestamp = 5 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; +} + +message EvidenceList { + repeated Evidence evidence = 1 [(gogoproto.nullable) = false]; +} diff --git a/x/ccv/provider/keeper/misbehaviour.go b/x/ccv/provider/keeper/misbehaviour.go index 879670063e..1f843fc517 100644 --- a/x/ccv/provider/keeper/misbehaviour.go +++ b/x/ccv/provider/keeper/misbehaviour.go @@ -5,29 +5,68 @@ import ( "time" sdk "github.com/cosmos/cosmos-sdk/types" + evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" "github.com/cosmos/ibc-go/v4/modules/core/exported" ibctmtypes "github.com/cosmos/ibc-go/v4/modules/light-clients/07-tendermint/types" tmtypes "github.com/tendermint/tendermint/types" ) -func (k Keeper) CheckConsumerMisbehaviour(ctx sdk.Context, misbeaviour ibctmtypes.Misbehaviour) error { - clientID := misbeaviour.GetClientID() +func (k Keeper) HandleConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) error { + if err := k.CheckConsumerMisbehaviour(ctx, misbehaviour); err != nil { + return err + } + + byzantineValidators, err := k.GetByzantineValidators(ctx, misbehaviour) + if err != nil { + return err + } + + // Since the misbehaviour packet was received within the trusting period + // w.r.t to the last trusted consensus it entails that the infraction age + // isn't too old. see ibc-go/modules/light-clients/07-tendermint/types/misbehaviour_handle.go + for _, v := range byzantineValidators { + // convert address to key assigned + consAddr := sdk.ConsAddress(v.Address.Bytes()) + validator := k.stakingKeeper.ValidatorByConsAddr(ctx, consAddr) + if validator == nil || validator.IsUnbonded() { + // Defensive: Simulation doesn't take unbonding periods into account, and + // Tendermint might break this assumption at some point. + k.Logger(ctx).Error("validator not found or is unbonded", consAddr) + } + k.slashingKeeper.JailUntil(ctx, consAddr, evidencetypes.DoubleSignJailEndTime) + k.slashingKeeper.Tombstone(ctx, consAddr) + // store misbehaviour? + } + + logger := ctx.Logger() + logger.Info( + "confirmed equivocation", + "byzantine validators", byzantineValidators, + ) + + return nil +} + +func (k Keeper) CheckConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) error { + + clientID := misbehaviour.GetClientID() + clientState, found := k.clientKeeper.GetClientState(ctx, clientID) if !found { return fmt.Errorf("types.ErrClientNotFound cannot check misbehaviour for client with ID %s", clientID) } - clientStore := k.clientKeeper.ClientStore(ctx, misbeaviour.GetClientID()) + clientStore := k.clientKeeper.ClientStore(ctx, misbehaviour.GetClientID()) if status := clientState.Status(ctx, clientStore, k.cdc); status != exported.Active { return fmt.Errorf("types.ErrClientNotActive cannot process misbehaviour for client (%s) with status %s", clientID, status) } - if err := misbeaviour.ValidateBasic(); err != nil { + if err := misbehaviour.ValidateBasic(); err != nil { return err } - clientState, err := clientState.CheckMisbehaviourAndUpdateState(ctx, k.cdc, clientStore, &misbeaviour) + clientState, err := clientState.CheckMisbehaviourAndUpdateState(ctx, k.cdc, clientStore, &misbehaviour) if err != nil { return err } @@ -36,12 +75,12 @@ func (k Keeper) CheckConsumerMisbehaviour(ctx sdk.Context, misbeaviour ibctmtype k.Logger(ctx).Info("client frozen due to misbehaviour", "client-id", clientID) // get byzantine validators - sh, err := tmtypes.SignedHeaderFromProto(misbeaviour.Header1.SignedHeader) + sh, err := tmtypes.SignedHeaderFromProto(misbehaviour.Header1.SignedHeader) if err != nil { return err } - vs, err := tmtypes.ValidatorSetFromProto(misbeaviour.Header1.ValidatorSet) + vs, err := tmtypes.ValidatorSetFromProto(misbehaviour.Header1.ValidatorSet) if err != nil { return err } @@ -49,7 +88,7 @@ func (k Keeper) CheckConsumerMisbehaviour(ctx sdk.Context, misbeaviour ibctmtype ConflictingBlock: &tmtypes.LightBlock{SignedHeader: sh, ValidatorSet: vs}, } - h2, err := tmtypes.HeaderFromProto(misbeaviour.Header2.Header) + h2, err := tmtypes.HeaderFromProto(misbehaviour.Header2.Header) if err != nil { return err } @@ -60,13 +99,13 @@ func (k Keeper) CheckConsumerMisbehaviour(ctx sdk.Context, misbeaviour ibctmtype // return the height of the conflicting block else if it is a lunatic attack and the validator sets // are not the same then we send the height of the common header. if ev.ConflictingHeaderIsInvalid(&h2) { - ev.CommonHeight = misbeaviour.Header2.Header.Height - ev.Timestamp = misbeaviour.Header2.Header.Time - ev.TotalVotingPower = misbeaviour.Header2.ValidatorSet.TotalVotingPower + ev.CommonHeight = misbehaviour.Header2.Header.Height + ev.Timestamp = misbehaviour.Header2.Header.Time + ev.TotalVotingPower = misbehaviour.Header2.ValidatorSet.TotalVotingPower } else { - ev.CommonHeight = misbeaviour.Header1.Header.Height - ev.Timestamp = misbeaviour.Header1.Header.Time - ev.TotalVotingPower = misbeaviour.Header1.ValidatorSet.TotalVotingPower + ev.CommonHeight = misbehaviour.Header1.Header.Height + ev.Timestamp = misbehaviour.Header1.Header.Time + ev.TotalVotingPower = misbehaviour.Header1.ValidatorSet.TotalVotingPower } ev.ByzantineValidators = ev.GetByzantineValidators(vs, sh) diff --git a/x/ccv/provider/types/tx.pb.go b/x/ccv/provider/types/tx.pb.go index 61b59ecd8c..27757abd0c 100644 --- a/x/ccv/provider/types/tx.pb.go +++ b/x/ccv/provider/types/tx.pb.go @@ -12,6 +12,7 @@ import ( grpc1 "github.com/gogo/protobuf/grpc" proto "github.com/gogo/protobuf/proto" _ "github.com/regen-network/cosmos-proto" + _ "github.com/tendermint/tendermint/proto/tendermint/types" _ "google.golang.org/genproto/googleapis/api/annotations" grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" @@ -284,43 +285,44 @@ func init() { } var fileDescriptor_43221a4391e9fbf4 = []byte{ - // 568 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x54, 0x3f, 0x6f, 0x13, 0x4f, - 0x10, 0xf5, 0xfd, 0xa2, 0x1f, 0x24, 0x9b, 0x80, 0xc4, 0xc9, 0x85, 0x73, 0x98, 0x33, 0x18, 0x01, - 0x29, 0xc2, 0xae, 0x6c, 0x0a, 0x44, 0x24, 0x0a, 0x3b, 0x34, 0x10, 0x59, 0x42, 0x47, 0x81, 0x44, - 0x81, 0x75, 0xb7, 0xbb, 0xac, 0x57, 0xf8, 0x76, 0x4f, 0xbb, 0x7b, 0x47, 0xee, 0x1b, 0x50, 0x42, - 0x85, 0xe8, 0xf2, 0x01, 0x90, 0xf8, 0x1a, 0x94, 0x29, 0xa9, 0x10, 0xb2, 0x1b, 0x6a, 0x4a, 0x2a, - 0xe4, 0xfb, 0x63, 0x5f, 0x84, 0xb1, 0x2c, 0xa0, 0xdb, 0x99, 0x79, 0xfb, 0xde, 0x1b, 0xcd, 0x68, - 0xc0, 0x3e, 0x17, 0x86, 0x2a, 0x3c, 0xf2, 0xb9, 0x18, 0x6a, 0x8a, 0x63, 0xc5, 0x4d, 0x8a, 0x30, - 0x4e, 0x50, 0xa4, 0x64, 0xc2, 0x09, 0x55, 0x28, 0xe9, 0x20, 0x73, 0x0c, 0x23, 0x25, 0x8d, 0xb4, - 0xaf, 0x2f, 0x41, 0x43, 0x8c, 0x13, 0x58, 0xa2, 0x61, 0xd2, 0x71, 0x9a, 0x4c, 0x4a, 0x36, 0xa6, - 0xc8, 0x8f, 0x38, 0xf2, 0x85, 0x90, 0xc6, 0x37, 0x5c, 0x0a, 0x9d, 0x53, 0x38, 0x75, 0x26, 0x99, - 0xcc, 0x9e, 0x68, 0xf6, 0x2a, 0xb2, 0xbb, 0x58, 0xea, 0x50, 0xea, 0x61, 0x5e, 0xc8, 0x83, 0xb2, - 0x54, 0xd0, 0x65, 0x51, 0x10, 0xbf, 0x40, 0xbe, 0x48, 0x8b, 0x12, 0xe2, 0x01, 0x46, 0x63, 0xce, - 0x46, 0x06, 0x8f, 0x39, 0x15, 0x46, 0x23, 0x43, 0x05, 0xa1, 0x2a, 0xe4, 0xc2, 0x64, 0xbe, 0xe7, - 0x51, 0xfe, 0xa1, 0xfd, 0xce, 0x02, 0xf5, 0x81, 0x66, 0x3d, 0xad, 0x39, 0x13, 0x87, 0x52, 0xe8, - 0x38, 0xa4, 0xea, 0x88, 0xa6, 0xf6, 0x2e, 0xd8, 0xcc, 0xbb, 0xe2, 0xa4, 0x61, 0x5d, 0xb5, 0xf6, - 0xb6, 0xbc, 0xf3, 0x59, 0xfc, 0x90, 0xd8, 0x77, 0xc1, 0x85, 0xb2, 0xbb, 0xa1, 0x4f, 0x88, 0x6a, - 0xfc, 0x37, 0xab, 0xf7, 0xed, 0xef, 0x5f, 0x5a, 0x17, 0x53, 0x3f, 0x1c, 0x1f, 0xb4, 0x67, 0x59, - 0xaa, 0x75, 0xdb, 0xdb, 0x29, 0x81, 0x3d, 0x42, 0x94, 0x7d, 0x0d, 0xec, 0xe0, 0x42, 0x62, 0xf8, - 0x92, 0xa6, 0x8d, 0x8d, 0x8c, 0x77, 0x1b, 0x2f, 0x64, 0x0f, 0x36, 0x5f, 0x9f, 0xb4, 0x6a, 0xdf, - 0x4e, 0x5a, 0xb5, 0xb6, 0x0b, 0x9a, 0xcb, 0x8c, 0x79, 0x54, 0x47, 0x52, 0x68, 0xda, 0x7e, 0x0e, - 0xdc, 0x81, 0x66, 0x1e, 0x65, 0x5c, 0x1b, 0xaa, 0x4a, 0x84, 0x47, 0x5f, 0xf9, 0x8a, 0x3c, 0xa0, - 0x42, 0x86, 0x76, 0x1d, 0xfc, 0x4f, 0x66, 0x8f, 0xc2, 0x7f, 0x1e, 0xd8, 0x4d, 0xb0, 0x45, 0x68, - 0x24, 0x35, 0x37, 0xb2, 0x70, 0xee, 0x2d, 0x12, 0x15, 0xfd, 0x3d, 0x70, 0x73, 0x35, 0xff, 0xdc, - 0xc9, 0x7b, 0x0b, 0x5c, 0x19, 0x68, 0xf6, 0x24, 0x0e, 0x42, 0x6e, 0x4a, 0xe0, 0x80, 0xeb, 0x80, - 0x8e, 0xfc, 0x84, 0xcb, 0x58, 0xcd, 0x34, 0x75, 0x56, 0x35, 0x54, 0x15, 0x6e, 0x16, 0x09, 0xfb, - 0x31, 0xd8, 0x09, 0x2b, 0xe8, 0xcc, 0xd4, 0x76, 0x77, 0x1f, 0xf2, 0x00, 0xc3, 0xea, 0x2c, 0x61, - 0x65, 0x7a, 0x49, 0x07, 0x56, 0x15, 0xbc, 0x33, 0x0c, 0x95, 0x2e, 0x6e, 0x81, 0x1b, 0x2b, 0xad, - 0x95, 0x4d, 0x74, 0x7f, 0x6c, 0x80, 0x8d, 0x81, 0x66, 0xf6, 0x5b, 0x0b, 0x5c, 0xfa, 0x75, 0x1b, - 0xee, 0xc1, 0x35, 0xf6, 0x1c, 0x2e, 0x9b, 0x97, 0xd3, 0xfb, 0xe3, 0xaf, 0xa5, 0x37, 0xfb, 0xa3, - 0x05, 0x2e, 0xaf, 0x1a, 0xf4, 0xe1, 0xba, 0x12, 0x2b, 0x48, 0x9c, 0xa3, 0x7f, 0x40, 0x32, 0x77, - 0xfc, 0xc1, 0x02, 0xce, 0x8a, 0x7d, 0xe8, 0xaf, 0xab, 0xf5, 0x7b, 0x0e, 0xe7, 0xd1, 0xdf, 0x73, - 0x94, 0x76, 0xfb, 0x4f, 0x3f, 0x4d, 0x5c, 0xeb, 0x74, 0xe2, 0x5a, 0x5f, 0x27, 0xae, 0xf5, 0x66, - 0xea, 0xd6, 0x4e, 0xa7, 0x6e, 0xed, 0xf3, 0xd4, 0xad, 0x3d, 0xbb, 0xcf, 0xb8, 0x19, 0xc5, 0x01, - 0xc4, 0x32, 0x2c, 0x8e, 0x10, 0x5a, 0xc8, 0xde, 0x9e, 0xdf, 0xc7, 0xa4, 0x8b, 0x8e, 0xcf, 0x1e, - 0x49, 0x93, 0x46, 0x54, 0x07, 0xe7, 0xb2, 0x2b, 0x73, 0xe7, 0x67, 0x00, 0x00, 0x00, 0xff, 0xff, - 0xe3, 0x4f, 0x57, 0x26, 0x55, 0x05, 0x00, 0x00, + // 578 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x54, 0x3f, 0x6f, 0xd3, 0x4e, + 0x18, 0x8e, 0x7f, 0xd5, 0x0f, 0xda, 0x6b, 0x41, 0xc2, 0xea, 0xd0, 0x9a, 0xe0, 0x40, 0x10, 0xd0, + 0xa1, 0xf8, 0x94, 0x30, 0x20, 0x2a, 0x31, 0x24, 0x65, 0x81, 0x2a, 0x12, 0x32, 0x03, 0x12, 0x03, + 0x91, 0x7d, 0x7e, 0xb9, 0x9c, 0x88, 0xef, 0xac, 0xbb, 0xb3, 0xa9, 0xbf, 0x01, 0x23, 0x4c, 0x88, + 0xad, 0x1f, 0x00, 0x89, 0xaf, 0xc1, 0xd8, 0x91, 0x09, 0xa1, 0x64, 0x61, 0x66, 0x64, 0x42, 0xfe, + 0x97, 0xb8, 0x22, 0x44, 0x11, 0xb0, 0xbd, 0x7f, 0x1e, 0x3f, 0xcf, 0xf3, 0xea, 0x3d, 0xbf, 0x68, + 0x9f, 0x71, 0x0d, 0x92, 0x8c, 0x3c, 0xc6, 0x87, 0x0a, 0x48, 0x2c, 0x99, 0x4e, 0x31, 0x21, 0x09, + 0x8e, 0xa4, 0x48, 0x58, 0x00, 0x12, 0x27, 0x1d, 0xac, 0x8f, 0x9d, 0x48, 0x0a, 0x2d, 0xcc, 0xeb, + 0x0b, 0xd0, 0x0e, 0x21, 0x89, 0x53, 0xa1, 0x9d, 0xa4, 0x63, 0x35, 0xa9, 0x10, 0x74, 0x0c, 0xd8, + 0x8b, 0x18, 0xf6, 0x38, 0x17, 0xda, 0xd3, 0x4c, 0x70, 0x55, 0x50, 0x58, 0xdb, 0x54, 0x50, 0x91, + 0x87, 0x38, 0x8b, 0xca, 0xea, 0x2e, 0x11, 0x2a, 0x14, 0x6a, 0x58, 0x34, 0x8a, 0xa4, 0x6a, 0x95, + 0x74, 0x79, 0xe6, 0xc7, 0x2f, 0xb0, 0xc7, 0xd3, 0xb2, 0x85, 0x99, 0x4f, 0xf0, 0x98, 0xd1, 0x91, + 0x26, 0x63, 0x06, 0x5c, 0x2b, 0xac, 0x81, 0x07, 0x20, 0x43, 0xc6, 0x75, 0xee, 0x7b, 0x96, 0x95, + 0x1f, 0xb4, 0x6a, 0x7d, 0x9d, 0x46, 0xa0, 0x30, 0x64, 0xb6, 0x39, 0x81, 0x02, 0xd0, 0x7e, 0x67, + 0xa0, 0xed, 0x81, 0xa2, 0x3d, 0xa5, 0x18, 0xe5, 0x87, 0x82, 0xab, 0x38, 0x04, 0x79, 0x04, 0xa9, + 0xb9, 0x8b, 0xd6, 0x8b, 0xb1, 0x59, 0xb0, 0x63, 0x5c, 0x35, 0xf6, 0x36, 0xdc, 0xf3, 0x79, 0xfe, + 0x30, 0x30, 0xef, 0xa2, 0x0b, 0xd5, 0xf8, 0x43, 0x2f, 0x08, 0xe4, 0xce, 0x7f, 0x59, 0xbf, 0x6f, + 0x7e, 0xff, 0xd2, 0xba, 0x98, 0x7a, 0xe1, 0xf8, 0xa0, 0x9d, 0x55, 0x41, 0xa9, 0xb6, 0xbb, 0x55, + 0x01, 0x7b, 0x41, 0x20, 0xcd, 0x6b, 0x68, 0x8b, 0x94, 0x12, 0xc3, 0x97, 0x90, 0xee, 0xac, 0xe5, + 0xbc, 0x9b, 0x64, 0x2e, 0x7b, 0xb0, 0xfe, 0xfa, 0xa4, 0xd5, 0xf8, 0x76, 0xd2, 0x6a, 0xb4, 0x6d, + 0xd4, 0x5c, 0x64, 0xcc, 0x05, 0x15, 0x09, 0xae, 0xa0, 0xfd, 0x1c, 0xd9, 0x03, 0x45, 0x5d, 0xa0, + 0x4c, 0x69, 0x90, 0x15, 0xc2, 0x85, 0x57, 0x9e, 0x0c, 0x1e, 0x00, 0x17, 0xa1, 0xb9, 0x8d, 0xfe, + 0x0f, 0xb2, 0xa0, 0xf4, 0x5f, 0x24, 0x66, 0x13, 0x6d, 0x04, 0x10, 0x09, 0xc5, 0xb4, 0x28, 0x9d, + 0xbb, 0xf3, 0x42, 0x4d, 0x7f, 0x0f, 0xdd, 0x5c, 0xce, 0x3f, 0x73, 0xf2, 0xde, 0x40, 0x57, 0x06, + 0x8a, 0x3e, 0x89, 0xfd, 0x90, 0xe9, 0x0a, 0x38, 0x60, 0xca, 0x87, 0x91, 0x97, 0x30, 0x11, 0xcb, + 0x4c, 0x53, 0xe5, 0x5d, 0x0d, 0xb2, 0x74, 0x33, 0x2f, 0x98, 0x8f, 0xd1, 0x56, 0x58, 0x43, 0xe7, + 0xa6, 0x36, 0xbb, 0xfb, 0x0e, 0xf3, 0x89, 0x53, 0x5f, 0xb6, 0x53, 0x5b, 0x6f, 0xd2, 0x71, 0xea, + 0x0a, 0xee, 0x19, 0x86, 0xda, 0x14, 0xb7, 0xd0, 0x8d, 0xa5, 0xd6, 0xaa, 0x21, 0xba, 0x3f, 0xd6, + 0xd0, 0xda, 0x40, 0x51, 0xf3, 0xad, 0x81, 0x2e, 0xfd, 0xfa, 0x1a, 0xee, 0x39, 0x2b, 0xfc, 0x08, + 0xce, 0xa2, 0x7d, 0x59, 0xbd, 0x3f, 0xfe, 0xb4, 0xf2, 0x66, 0x7e, 0x34, 0xd0, 0xe5, 0x65, 0x8b, + 0x3e, 0x5c, 0x55, 0x62, 0x09, 0x89, 0x75, 0xf4, 0x0f, 0x48, 0x66, 0x8e, 0x3f, 0x18, 0xc8, 0x5a, + 0xf2, 0x1e, 0xfa, 0xab, 0x6a, 0xfd, 0x9e, 0xc3, 0x7a, 0xf4, 0xf7, 0x1c, 0x95, 0xdd, 0xfe, 0xd3, + 0x4f, 0x13, 0xdb, 0x38, 0x9d, 0xd8, 0xc6, 0xd7, 0x89, 0x6d, 0xbc, 0x99, 0xda, 0x8d, 0xd3, 0xa9, + 0xdd, 0xf8, 0x3c, 0xb5, 0x1b, 0xcf, 0xee, 0x53, 0xa6, 0x47, 0xb1, 0xef, 0x10, 0x11, 0x96, 0x57, + 0x0a, 0xcf, 0x65, 0x6f, 0xcf, 0x0e, 0x68, 0xd2, 0xc5, 0xc7, 0x67, 0xaf, 0x68, 0x7e, 0x6d, 0xfc, + 0x73, 0xf9, 0x95, 0xb9, 0xf3, 0x33, 0x00, 0x00, 0xff, 0xff, 0x7c, 0xb5, 0x5a, 0x59, 0x76, 0x05, + 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. From b52daff4d5e3d3fa396a34fdd0c88bc8831a8ce4 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Wed, 3 May 2023 12:03:21 +0200 Subject: [PATCH 09/45] add DRAFT double vote handler --- x/ccv/provider/keeper/double_vote.go | 39 ++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 x/ccv/provider/keeper/double_vote.go diff --git a/x/ccv/provider/keeper/double_vote.go b/x/ccv/provider/keeper/double_vote.go new file mode 100644 index 0000000000..7885890596 --- /dev/null +++ b/x/ccv/provider/keeper/double_vote.go @@ -0,0 +1,39 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + ibctmtypes "github.com/cosmos/ibc-go/v4/modules/light-clients/07-tendermint/types" + tmev "github.com/tendermint/tendermint/evidence" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + tmtypes "github.com/tendermint/tendermint/types" +) + +func (k Keeper) HandleConsumerDoubleVoting(ctx sdk.Context, evidence *tmproto.DuplicateVoteEvidence, header *ibctmtypes.Header) error { + + // TODO check header against consumer chain client + + ev, err := tmtypes.DuplicateVoteEvidenceFromProto(evidence) + if err != nil { + return err + } + valset, err := tmtypes.ValidatorSetFromProto(header.TrustedValidators) + if err != nil { + return err + } + + // TODO: figure out if the evidence age must also be checked + if err := tmev.VerifyDuplicateVote(ev, header.Header.ChainID, valset); err != nil { + return err + } + + // TODO convert misbehaving validator consumer pubkey + //TODO call k.evidenceKeeper.HandleEquivocationEvidence() using correct infraction height + + logger := ctx.Logger() + logger.Info( + "confirmed equivocation", + "byzantine validator", sdk.ConsAddress(evidence.VoteA.GetValidatorAddress()), + ) + + return nil +} From 8eea67715d559a8daa9e6198c60bc1495e47db87 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Thu, 4 May 2023 09:34:14 +0200 Subject: [PATCH 10/45] Add cli cmd for submit consumer double voting --- x/ccv/provider/keeper/double_vote.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/x/ccv/provider/keeper/double_vote.go b/x/ccv/provider/keeper/double_vote.go index 7885890596..fc7875009f 100644 --- a/x/ccv/provider/keeper/double_vote.go +++ b/x/ccv/provider/keeper/double_vote.go @@ -10,7 +10,7 @@ import ( func (k Keeper) HandleConsumerDoubleVoting(ctx sdk.Context, evidence *tmproto.DuplicateVoteEvidence, header *ibctmtypes.Header) error { - // TODO check header against consumer chain client + // TODO: check header against consumer chain client ev, err := tmtypes.DuplicateVoteEvidenceFromProto(evidence) if err != nil { @@ -26,8 +26,8 @@ func (k Keeper) HandleConsumerDoubleVoting(ctx sdk.Context, evidence *tmproto.Du return err } - // TODO convert misbehaving validator consumer pubkey - //TODO call k.evidenceKeeper.HandleEquivocationEvidence() using correct infraction height + // TODO: convert misbehaving validator consumer pubkey + // TODO: call k.evidenceKeeper.HandleEquivocationEvidence() using correct infraction height logger := ctx.Logger() logger.Info( From 81dd6ba1beb7f535332abad090a2eede59880f73 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Thu, 4 May 2023 09:39:05 +0200 Subject: [PATCH 11/45] Add double-vote handler --- x/ccv/provider/handler.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/x/ccv/provider/handler.go b/x/ccv/provider/handler.go index 6fa38b5ddf..8dca550732 100644 --- a/x/ccv/provider/handler.go +++ b/x/ccv/provider/handler.go @@ -24,6 +24,9 @@ func NewHandler(k *keeper.Keeper) sdk.Handler { case *types.MsgSubmitConsumerMisbehaviour: res, err := msgServer.SubmitConsumerMisbehaviour(sdk.WrapSDKContext(ctx), msg) return sdk.WrapServiceResult(ctx, res, err) + case *types.MsgSubmitConsumerDoubleVoting: + res, err := msgServer.SubmitConsumerDoubleVoting(sdk.WrapSDKContext(ctx), msg) + return sdk.WrapServiceResult(ctx, res, err) default: return nil, errorsmod.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized %s message type: %T", types.ModuleName, msg) } From 0cb86c9d73b358c38a12d74f6799ca8b32d6dac9 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Thu, 15 Jun 2023 17:13:56 +0200 Subject: [PATCH 12/45] add last update --- x/ccv/provider/handler.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/x/ccv/provider/handler.go b/x/ccv/provider/handler.go index 8dca550732..6fa38b5ddf 100644 --- a/x/ccv/provider/handler.go +++ b/x/ccv/provider/handler.go @@ -24,9 +24,6 @@ func NewHandler(k *keeper.Keeper) sdk.Handler { case *types.MsgSubmitConsumerMisbehaviour: res, err := msgServer.SubmitConsumerMisbehaviour(sdk.WrapSDKContext(ctx), msg) return sdk.WrapServiceResult(ctx, res, err) - case *types.MsgSubmitConsumerDoubleVoting: - res, err := msgServer.SubmitConsumerDoubleVoting(sdk.WrapSDKContext(ctx), msg) - return sdk.WrapServiceResult(ctx, res, err) default: return nil, errorsmod.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized %s message type: %T", types.ModuleName, msg) } From b6cf9381635c866eabcfea4a7cd43eb21e8e8670 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Fri, 16 Jun 2023 14:34:26 +0200 Subject: [PATCH 13/45] fix jailing --- tests/integration/misbehaviour.go | 817 +++++++++++++++----------- testutil/integration/debug_test.go | 4 + x/ccv/provider/keeper/misbehaviour.go | 15 +- 3 files changed, 480 insertions(+), 356 deletions(-) diff --git a/tests/integration/misbehaviour.go b/tests/integration/misbehaviour.go index a442001cc7..a9d20a1f88 100644 --- a/tests/integration/misbehaviour.go +++ b/tests/integration/misbehaviour.go @@ -4,11 +4,9 @@ import ( "fmt" "time" - clienttypes "github.com/cosmos/ibc-go/v4/modules/core/02-client/types" - ibcclientypes "github.com/cosmos/ibc-go/v4/modules/core/02-client/types" - commitmenttypes "github.com/cosmos/ibc-go/v4/modules/core/23-commitment/types" + sdk "github.com/cosmos/cosmos-sdk/types" + ibctmtypes "github.com/cosmos/ibc-go/v4/modules/light-clients/07-tendermint/types" - ibctestingmock "github.com/cosmos/ibc-go/v4/testing/mock" tmtypes "github.com/tendermint/tendermint/types" ) @@ -18,6 +16,64 @@ const ( maxClockDrift time.Duration = time.Second * 10 ) +func (s *CCVTestSuite) TestHandleConsumerMisbehaviour() { + s.SetupCCVChannel(s.path) + // required to have the consumer client revision height greater than 0 + s.SendEmptyVSCPacket() + + for _, v := range s.providerChain.Vals.Validators { + s.setDefaultValSigningInfo(*v) + } + + // consumerConsState, _ := s.providerChain.GetConsensusState(s.path.EndpointA.ClientID, s.consumerChain.LastHeader.TrustedHeight) + altTime := s.providerCtx().BlockTime().Add(time.Minute) + + clientHeight := s.consumerChain.LastHeader.TrustedHeight + clientTMValset := tmtypes.NewValidatorSet(s.consumerChain.Vals.Validators) + clientSigners := s.consumerChain.Signers + + altValset := tmtypes.NewValidatorSet(s.consumerChain.Vals.Validators[0:2]) + altSigners := make(map[string]tmtypes.PrivValidator, 1) + // altSigners[altValset.Validators[0].Address.String()] = altPrivVal + altSigners[clientTMValset.Validators[0].Address.String()] = clientSigners[clientTMValset.Validators[0].Address.String()] + altSigners[clientTMValset.Validators[1].Address.String()] = clientSigners[clientTMValset.Validators[1].Address.String()] + + misb := &ibctmtypes.Misbehaviour{ + ClientId: s.path.EndpointA.ClientID, + Header1: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime, + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + Header2: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime, + altValset, + altValset, + clientTMValset, + altSigners, + ), + } + + err := s.providerApp.GetProviderKeeper().HandleConsumerMisbehaviour(s.providerCtx(), *misb) + s.NoError(err) + + for _, v := range altValset.Validators { + consuAddr := sdk.ConsAddress(v.Address.Bytes()) + provAddr := s.providerApp.GetProviderKeeper().GetProviderAddrFromConsumerAddr(s.providerCtx(), s.consumerChain.ChainID, consuAddr) + s.providerApp.GetE2eStakingKeeper().ValidatorByConsAddr(s.providerCtx(), consuAddr) + val, ok := s.providerApp.GetProviderKeeper().stakingKeeper.GetValidatorByConsAddr(s.providerCtx(), provAddr) + } + +} + // mostly based on TestCheckMisbehaviourAndUpdateState in ibc-go/modules/core/02-client/keeper/client_test.go func (s *CCVTestSuite) TestCheckConsumerMisbehaviour() { @@ -25,25 +81,28 @@ func (s *CCVTestSuite) TestCheckConsumerMisbehaviour() { // required to have the consumer client revision height greater than 0 s.SendEmptyVSCPacket() - consumerConsState, _ := s.providerChain.GetConsensusState(s.path.EndpointA.ClientID, s.consumerChain.LastHeader.TrustedHeight) + // consumerConsState, _ := s.providerChain.GetConsensusState(s.path.EndpointA.ClientID, s.consumerChain.LastHeader.TrustedHeight) clientHeight := s.consumerChain.LastHeader.TrustedHeight clientTMValset := tmtypes.NewValidatorSet(s.consumerChain.Vals.Validators) clientSigners := s.consumerChain.Signers - altPrivVal := ibctestingmock.NewPV() - altPubKey, err := altPrivVal.GetPubKey() - s.Require().NoError(err) - altVal := tmtypes.NewValidator(altPubKey, 4) + // altPrivVal := ibctestingmock.NewPV() + // altPubKey, err := altPrivVal.GetPubKey() + // s.Require().NoError(err) + // altVal := tmtypes.NewValidator(altPubKey, 4) - altValset := tmtypes.NewValidatorSet([]*tmtypes.Validator{altVal}) + // altValset := tmtypes.NewValidatorSet([]*tmtypes.Validator{altVal}) + altValset := tmtypes.NewValidatorSet(s.consumerChain.Vals.Validators[0:2]) altSigners := make(map[string]tmtypes.PrivValidator, 1) - altSigners[altValset.Validators[0].Address.String()] = altPrivVal + // altSigners[altValset.Validators[0].Address.String()] = altPrivVal + altSigners[clientTMValset.Validators[0].Address.String()] = clientSigners[clientTMValset.Validators[0].Address.String()] + altSigners[clientTMValset.Validators[1].Address.String()] = clientSigners[clientTMValset.Validators[1].Address.String()] altTime := s.providerCtx().BlockTime().Add(time.Minute) - heightPlus5 := ibcclientypes.NewHeight(0, clientHeight.RevisionHeight+5) + // heightPlus5 := ibcclientypes.NewHeight(0, clientHeight.RevisionHeight+5) - heightPlus3 := ibcclientypes.NewHeight(0, clientHeight.RevisionHeight+3) + // heightPlus3 := ibcclientypes.NewHeight(0, clientHeight.RevisionHeight+3) testCases := []struct { name string @@ -51,318 +110,318 @@ func (s *CCVTestSuite) TestCheckConsumerMisbehaviour() { malleate func() expPass bool }{ - { - "misbehaviour height is at same height as trusted height", - func() *ibctmtypes.Misbehaviour { - return &ibctmtypes.Misbehaviour{ - ClientId: s.path.EndpointA.ClientID, - Header1: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight), - clientHeight, - altTime, - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - Header2: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight), - clientHeight, - s.providerCtx().BlockTime(), - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - } - }, - func() {}, - false, - }, { - "invalid chain ID", - func() *ibctmtypes.Misbehaviour { - - mb := &ibctmtypes.Misbehaviour{ - ClientId: s.path.EndpointA.ClientID, - Header1: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight+1), - clientHeight, - altTime, - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - Header2: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight+1), - clientHeight, - altTime, - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - } - - mb.Header1.Header.ChainID = "wrongchainid" - return mb - - }, - func() {}, - false, - }, - { - "invalid client ID", - func() *ibctmtypes.Misbehaviour { - - mb := &ibctmtypes.Misbehaviour{ - ClientId: "wrongclientid", - Header1: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight+1), - clientHeight, - altTime, - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - Header2: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight+1), - clientHeight, - altTime, - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - } - - return mb - - }, - func() {}, - false, - }, { - "different trusted height shouldn't pass", - func() *ibctmtypes.Misbehaviour { - return &ibctmtypes.Misbehaviour{ - ClientId: s.path.EndpointA.ClientID, - Header1: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight+1), - clientHeight, - altTime, - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - Header2: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight+1), - heightPlus3, - s.providerCtx().BlockTime(), - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - } - }, - func() {}, - false, - }, { - "trusting period misbehavior should pass", - func() *ibctmtypes.Misbehaviour { - return &ibctmtypes.Misbehaviour{ - ClientId: s.path.EndpointA.ClientID, - Header1: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight+1), - clientHeight, - altTime, - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - Header2: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight+1), - clientHeight, - s.providerCtx().BlockTime(), - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - } - }, - func() {}, - true, - }, - { - "time misbehavior should pass", - func() *ibctmtypes.Misbehaviour { - return &ibctmtypes.Misbehaviour{ - ClientId: s.path.EndpointA.ClientID, - Header1: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight+5), - clientHeight, - s.providerCtx().BlockTime(), - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - Header2: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight+1), - clientHeight, - altTime, - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - } - }, - func() {}, - true, - }, - { - "both later height should pass", - func() *ibctmtypes.Misbehaviour { - return &ibctmtypes.Misbehaviour{ - ClientId: s.path.EndpointA.ClientID, - Header1: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(heightPlus5.RevisionHeight+1), - clientHeight, - s.providerCtx().BlockTime(), - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - Header2: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(heightPlus5.RevisionHeight+1), - clientHeight, - altTime, - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - } - }, - func() { - - consumerConsState.(*ibctmtypes.ConsensusState).NextValidatorsHash = clientTMValset.Hash() - clientState := ibctmtypes.NewClientState(s.consumerChain.ChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), []string{"upgrade", "upgradedIBCState"}, false, false) - - // store trusted consensus state for Header2 - intermediateConsState := &ibctmtypes.ConsensusState{ - Timestamp: altTime, - NextValidatorsHash: clientTMValset.Hash(), - } - - s.providerApp.GetIBCKeeper().ClientKeeper.SetClientConsensusState(s.providerCtx(), s.path.EndpointA.ClientID, heightPlus3, intermediateConsState) - - clientState.LatestHeight = heightPlus3 - s.providerApp.GetIBCKeeper().ClientKeeper.SetClientState(s.providerCtx(), s.path.EndpointA.ClientID, clientState) - }, - true, - }, - { - "trusted ConsensusState1 not found", - func() *ibctmtypes.Misbehaviour { - return &ibctmtypes.Misbehaviour{ - ClientId: s.path.EndpointA.ClientID, - Header1: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight), - heightPlus3, - altTime, - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - Header2: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight), - clientHeight, - s.providerCtx().BlockTime(), - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - } - }, - func() {}, - false, - }, - { - "trusted ConsensusState2 not found", - func() *ibctmtypes.Misbehaviour { - return &ibctmtypes.Misbehaviour{ - ClientId: s.path.EndpointA.ClientID, - Header1: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight), - clientHeight, - altTime, - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - Header2: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight), - heightPlus3, - s.providerCtx().BlockTime(), - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - } - }, - func() {}, - false, - }, - { - "client state not found", - func() *ibctmtypes.Misbehaviour { - return &ibctmtypes.Misbehaviour{} - }, - func() {}, - false, - }, { - "client already is not active - client is frozen", - func() *ibctmtypes.Misbehaviour { - return &ibctmtypes.Misbehaviour{} - }, - func() { - consumerConsState.(*ibctmtypes.ConsensusState).NextValidatorsHash = clientTMValset.Hash() - clientState := ibctmtypes.NewClientState(s.consumerChain.ChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), []string{"upgrade", "upgradedIBCState"}, false, false) - - clientState.FrozenHeight = ibcclientypes.NewHeight(0, 1) - s.providerApp.GetIBCKeeper().ClientKeeper.SetClientState(s.providerCtx(), s.path.EndpointA.ClientID, clientState) - }, - false, - }, + // { + // "misbehaviour height is at same height as trusted height", + // func() *ibctmtypes.Misbehaviour { + // return &ibctmtypes.Misbehaviour{ + // ClientId: s.path.EndpointA.ClientID, + // Header1: s.consumerChain.CreateTMClientHeader( + // s.consumerChain.ChainID, + // int64(clientHeight.RevisionHeight), + // clientHeight, + // altTime, + // clientTMValset, + // clientTMValset, + // clientTMValset, + // clientSigners, + // ), + // Header2: s.consumerChain.CreateTMClientHeader( + // s.consumerChain.ChainID, + // int64(clientHeight.RevisionHeight), + // clientHeight, + // s.providerCtx().BlockTime(), + // clientTMValset, + // clientTMValset, + // clientTMValset, + // clientSigners, + // ), + // } + // }, + // func() {}, + // false, + // }, { + // "invalid chain ID", + // func() *ibctmtypes.Misbehaviour { + + // mb := &ibctmtypes.Misbehaviour{ + // ClientId: s.path.EndpointA.ClientID, + // Header1: s.consumerChain.CreateTMClientHeader( + // s.consumerChain.ChainID, + // int64(clientHeight.RevisionHeight+1), + // clientHeight, + // altTime, + // clientTMValset, + // clientTMValset, + // clientTMValset, + // clientSigners, + // ), + // Header2: s.consumerChain.CreateTMClientHeader( + // s.consumerChain.ChainID, + // int64(clientHeight.RevisionHeight+1), + // clientHeight, + // altTime, + // clientTMValset, + // clientTMValset, + // clientTMValset, + // clientSigners, + // ), + // } + + // mb.Header1.Header.ChainID = "wrongchainid" + // return mb + + // }, + // func() {}, + // false, + // }, + // { + // "invalid client ID", + // func() *ibctmtypes.Misbehaviour { + + // mb := &ibctmtypes.Misbehaviour{ + // ClientId: "wrongclientid", + // Header1: s.consumerChain.CreateTMClientHeader( + // s.consumerChain.ChainID, + // int64(clientHeight.RevisionHeight+1), + // clientHeight, + // altTime, + // clientTMValset, + // clientTMValset, + // clientTMValset, + // clientSigners, + // ), + // Header2: s.consumerChain.CreateTMClientHeader( + // s.consumerChain.ChainID, + // int64(clientHeight.RevisionHeight+1), + // clientHeight, + // altTime, + // clientTMValset, + // clientTMValset, + // clientTMValset, + // clientSigners, + // ), + // } + + // return mb + + // }, + // func() {}, + // false, + // }, { + // "different trusted height shouldn't pass", + // func() *ibctmtypes.Misbehaviour { + // return &ibctmtypes.Misbehaviour{ + // ClientId: s.path.EndpointA.ClientID, + // Header1: s.consumerChain.CreateTMClientHeader( + // s.consumerChain.ChainID, + // int64(clientHeight.RevisionHeight+1), + // clientHeight, + // altTime, + // clientTMValset, + // clientTMValset, + // clientTMValset, + // clientSigners, + // ), + // Header2: s.consumerChain.CreateTMClientHeader( + // s.consumerChain.ChainID, + // int64(clientHeight.RevisionHeight+1), + // heightPlus3, + // s.providerCtx().BlockTime(), + // clientTMValset, + // clientTMValset, + // clientTMValset, + // clientSigners, + // ), + // } + // }, + // func() {}, + // false, + // }, { + // "trusting period misbehavior should pass", + // func() *ibctmtypes.Misbehaviour { + // return &ibctmtypes.Misbehaviour{ + // ClientId: s.path.EndpointA.ClientID, + // Header1: s.consumerChain.CreateTMClientHeader( + // s.consumerChain.ChainID, + // int64(clientHeight.RevisionHeight+1), + // clientHeight, + // altTime, + // clientTMValset, + // clientTMValset, + // clientTMValset, + // clientSigners, + // ), + // Header2: s.consumerChain.CreateTMClientHeader( + // s.consumerChain.ChainID, + // int64(clientHeight.RevisionHeight+1), + // clientHeight, + // s.providerCtx().BlockTime(), + // clientTMValset, + // clientTMValset, + // clientTMValset, + // clientSigners, + // ), + // } + // }, + // func() {}, + // true, + // }, + // { + // "time misbehavior should pass", + // func() *ibctmtypes.Misbehaviour { + // return &ibctmtypes.Misbehaviour{ + // ClientId: s.path.EndpointA.ClientID, + // Header1: s.consumerChain.CreateTMClientHeader( + // s.consumerChain.ChainID, + // int64(clientHeight.RevisionHeight+5), + // clientHeight, + // s.providerCtx().BlockTime(), + // clientTMValset, + // clientTMValset, + // clientTMValset, + // clientSigners, + // ), + // Header2: s.consumerChain.CreateTMClientHeader( + // s.consumerChain.ChainID, + // int64(clientHeight.RevisionHeight+1), + // clientHeight, + // altTime, + // clientTMValset, + // clientTMValset, + // clientTMValset, + // clientSigners, + // ), + // } + // }, + // func() {}, + // true, + // }, + // { + // "both later height should pass", + // func() *ibctmtypes.Misbehaviour { + // return &ibctmtypes.Misbehaviour{ + // ClientId: s.path.EndpointA.ClientID, + // Header1: s.consumerChain.CreateTMClientHeader( + // s.consumerChain.ChainID, + // int64(heightPlus5.RevisionHeight+1), + // clientHeight, + // s.providerCtx().BlockTime(), + // clientTMValset, + // clientTMValset, + // clientTMValset, + // clientSigners, + // ), + // Header2: s.consumerChain.CreateTMClientHeader( + // s.consumerChain.ChainID, + // int64(heightPlus5.RevisionHeight+1), + // clientHeight, + // altTime, + // clientTMValset, + // clientTMValset, + // clientTMValset, + // clientSigners, + // ), + // } + // }, + // func() { + + // consumerConsState.(*ibctmtypes.ConsensusState).NextValidatorsHash = clientTMValset.Hash() + // clientState := ibctmtypes.NewClientState(s.consumerChain.ChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), []string{"upgrade", "upgradedIBCState"}, false, false) + + // // store trusted consensus state for Header2 + // intermediateConsState := &ibctmtypes.ConsensusState{ + // Timestamp: altTime, + // NextValidatorsHash: clientTMValset.Hash(), + // } + + // s.providerApp.GetIBCKeeper().ClientKeeper.SetClientConsensusState(s.providerCtx(), s.path.EndpointA.ClientID, heightPlus3, intermediateConsState) + + // clientState.LatestHeight = heightPlus3 + // s.providerApp.GetIBCKeeper().ClientKeeper.SetClientState(s.providerCtx(), s.path.EndpointA.ClientID, clientState) + // }, + // true, + // }, + // { + // "trusted ConsensusState1 not found", + // func() *ibctmtypes.Misbehaviour { + // return &ibctmtypes.Misbehaviour{ + // ClientId: s.path.EndpointA.ClientID, + // Header1: s.consumerChain.CreateTMClientHeader( + // s.consumerChain.ChainID, + // int64(clientHeight.RevisionHeight), + // heightPlus3, + // altTime, + // clientTMValset, + // clientTMValset, + // clientTMValset, + // clientSigners, + // ), + // Header2: s.consumerChain.CreateTMClientHeader( + // s.consumerChain.ChainID, + // int64(clientHeight.RevisionHeight), + // clientHeight, + // s.providerCtx().BlockTime(), + // clientTMValset, + // clientTMValset, + // clientTMValset, + // clientSigners, + // ), + // } + // }, + // func() {}, + // false, + // }, + // { + // "trusted ConsensusState2 not found", + // func() *ibctmtypes.Misbehaviour { + // return &ibctmtypes.Misbehaviour{ + // ClientId: s.path.EndpointA.ClientID, + // Header1: s.consumerChain.CreateTMClientHeader( + // s.consumerChain.ChainID, + // int64(clientHeight.RevisionHeight), + // clientHeight, + // altTime, + // clientTMValset, + // clientTMValset, + // clientTMValset, + // clientSigners, + // ), + // Header2: s.consumerChain.CreateTMClientHeader( + // s.consumerChain.ChainID, + // int64(clientHeight.RevisionHeight), + // heightPlus3, + // s.providerCtx().BlockTime(), + // clientTMValset, + // clientTMValset, + // clientTMValset, + // clientSigners, + // ), + // } + // }, + // func() {}, + // false, + // }, + // { + // "client state not found", + // func() *ibctmtypes.Misbehaviour { + // return &ibctmtypes.Misbehaviour{} + // }, + // func() {}, + // false, + // }, { + // "client already is not active - client is frozen", + // func() *ibctmtypes.Misbehaviour { + // return &ibctmtypes.Misbehaviour{} + // }, + // func() { + // consumerConsState.(*ibctmtypes.ConsensusState).NextValidatorsHash = clientTMValset.Hash() + // clientState := ibctmtypes.NewClientState(s.consumerChain.ChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), []string{"upgrade", "upgradedIBCState"}, false, false) + + // clientState.FrozenHeight = ibcclientypes.NewHeight(0, 1) + // s.providerApp.GetIBCKeeper().ClientKeeper.SetClientState(s.providerCtx(), s.path.EndpointA.ClientID, clientState) + // }, + // false, + // }, { "misbehaviour check failed", func() *ibctmtypes.Misbehaviour { @@ -391,7 +450,7 @@ func (s *CCVTestSuite) TestCheckConsumerMisbehaviour() { } }, func() {}, - false, + true, }, } @@ -403,7 +462,7 @@ func (s *CCVTestSuite) TestCheckConsumerMisbehaviour() { tc.malleate() - err = s.providerApp.GetProviderKeeper().CheckConsumerMisbehaviour( + err := s.providerApp.GetProviderKeeper().CheckConsumerMisbehaviour( cCtx, *tc.misbehaviour(), ) @@ -428,37 +487,95 @@ func (s *CCVTestSuite) TestGetByzantineValidators() { s.SetupCCVChannel(s.path) // required to have the consumer client revision height greater than 0 s.SendEmptyVSCPacket() - s.consumerCtx().BlockHeight() - // get consumer client state - // consumerClientState := s.providerChain.GetClientState(s.path.EndpointA.ClientID) - - // create two conflicting headers and forge them - // commit new block on consumer - s.coordinator.CommitBlock(s.consumerChain) + // consumerConsState, _ := s.providerChain.GetConsensusState(s.path.EndpointA.ClientID, s.consumerChain.LastHeader.TrustedHeight) + altTime := s.providerCtx().BlockTime().Add(time.Minute) - // get trusted height from client state - trustedHeight := s.providerChain.GetClientState(s.path.EndpointA.ClientID).GetLatestHeight().(clienttypes.Height) - tmTrustedVals := s.consumerChain.Vals - // get last consumer header - header := s.consumerChain.LastHeader + clientHeight := s.consumerChain.LastHeader.TrustedHeight + clientTMValset := tmtypes.NewValidatorSet(s.consumerChain.Vals.Validators) + clientSigners := s.consumerChain.Signers - header.TrustedHeight = trustedHeight - trustedVals, err := tmTrustedVals.ToProto() - s.NoError(err) - header.TrustedValidators = trustedVals + altValset := tmtypes.NewValidatorSet(s.consumerChain.Vals.Validators[0:2]) + altSigners := make(map[string]tmtypes.PrivValidator, 1) + // altSigners[altValset.Validators[0].Address.String()] = altPrivVal + altSigners[clientTMValset.Validators[0].Address.String()] = clientSigners[clientTMValset.Validators[0].Address.String()] + altSigners[clientTMValset.Validators[1].Address.String()] = clientSigners[clientTMValset.Validators[1].Address.String()] + + misb := &ibctmtypes.Misbehaviour{ + ClientId: s.path.EndpointA.ClientID, + Header1: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime, + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + Header2: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime, + altValset, + altValset, + clientTMValset, + altSigners, + ), + } - msg, err := clienttypes.NewMsgUpdateClient( - s.path.EndpointB.ClientID, header, - s.path.EndpointB.Chain.SenderAccount.GetAddress().String(), + err := s.providerApp.GetProviderKeeper().CheckConsumerMisbehaviour( + s.providerCtx(), + *misb, ) + s.NoError(err) - header2 := *header - header2.SignedHeader.Commit.BlockID.Hash = []byte("forge_hash") + val, err := s.providerApp.GetProviderKeeper().GetByzantineValidators( + s.providerCtx(), + *misb, + ) - _, err = s.providerChain.SendMsgs(msg) s.NoError(err) - fmt.Printf("%+v\n", msg.Header) + fmt.Println(len(val)) + fmt.Println(len(s.consumerChain.Vals.Validators)) } + +// s.SetupCCVChannel(s.path) +// // required to have the consumer client revision height greater than 0 +// s.SendEmptyVSCPacket() +// s.consumerCtx().BlockHeight() +// // get consumer client state +// // consumerClientState := s.providerChain.GetClientState(s.path.EndpointA.ClientID) + +// // create two conflicting headers and forge them +// // commit new block on consumer + +// s.coordinator.CommitBlock(s.consumerChain) + +// // get trusted height from client state +// trustedHeight := s.providerChain.GetClientState(s.path.EndpointA.ClientID).GetLatestHeight().(clienttypes.Height) +// tmTrustedVals := s.consumerChain.Vals +// // get last consumer header +// header := s.consumerChain.LastHeader + +// header.TrustedHeight = trustedHeight +// trustedVals, err := tmTrustedVals.ToProto() +// s.NoError(err) +// header.TrustedValidators = trustedVals + +// msg, err := clienttypes.NewMsgUpdateClient( +// s.path.EndpointB.ClientID, header, +// s.path.EndpointB.Chain.SenderAccount.GetAddress().String(), +// ) +// s.NoError(err) + +// header2 := *header +// header2.SignedHeader.Commit.BlockID.Hash = []byte("forge_hash") + +// _, err = s.providerChain.SendMsgs(msg) +// s.NoError(err) + +// fmt.Printf("%+v\n", msg.Header) diff --git a/testutil/integration/debug_test.go b/testutil/integration/debug_test.go index 149915282b..0b87bdf92a 100644 --- a/testutil/integration/debug_test.go +++ b/testutil/integration/debug_test.go @@ -261,6 +261,10 @@ func TestRecycleTransferChannel(t *testing.T) { // Misbehaviour test // +func TestHandleConsumerMisbehaviour(t *testing.T) { + runCCVTestByName(t, "TestHandleConsumerMisbehaviour") +} + func TestCheckConsumerMisbehaviour(t *testing.T) { runCCVTestByName(t, "TestCheckConsumerMisbehaviour") } diff --git a/x/ccv/provider/keeper/misbehaviour.go b/x/ccv/provider/keeper/misbehaviour.go index 1f843fc517..a3a4e3edef 100644 --- a/x/ccv/provider/keeper/misbehaviour.go +++ b/x/ccv/provider/keeper/misbehaviour.go @@ -26,15 +26,18 @@ func (k Keeper) HandleConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmty // isn't too old. see ibc-go/modules/light-clients/07-tendermint/types/misbehaviour_handle.go for _, v := range byzantineValidators { // convert address to key assigned - consAddr := sdk.ConsAddress(v.Address.Bytes()) - validator := k.stakingKeeper.ValidatorByConsAddr(ctx, consAddr) - if validator == nil || validator.IsUnbonded() { + consuAddr := sdk.ConsAddress(v.Address.Bytes()) + provAddr := k.GetProviderAddrFromConsumerAddr(ctx, misbehaviour.Header1.Header.ChainID, consuAddr) + k.stakingKeeper.ValidatorByConsAddr(ctx, consuAddr) + val, ok := k.stakingKeeper.GetValidatorByConsAddr(ctx, provAddr) + if !ok || val.IsUnbonded() { // Defensive: Simulation doesn't take unbonding periods into account, and // Tendermint might break this assumption at some point. - k.Logger(ctx).Error("validator not found or is unbonded", consAddr) + k.Logger(ctx).Error("validator not found or is unbonded", provAddr.String()) + continue } - k.slashingKeeper.JailUntil(ctx, consAddr, evidencetypes.DoubleSignJailEndTime) - k.slashingKeeper.Tombstone(ctx, consAddr) + k.slashingKeeper.JailUntil(ctx, provAddr, evidencetypes.DoubleSignJailEndTime) + k.slashingKeeper.Tombstone(ctx, provAddr) // store misbehaviour? } From 1444690d9c2b7298e2ee5c2748e4b2c1744a1860 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Fri, 16 Jun 2023 16:35:08 +0200 Subject: [PATCH 14/45] pass first jailing integration test --- tests/integration/misbehaviour.go | 6 ++++-- x/ccv/provider/keeper/misbehaviour.go | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/integration/misbehaviour.go b/tests/integration/misbehaviour.go index a9d20a1f88..a3eb894fec 100644 --- a/tests/integration/misbehaviour.go +++ b/tests/integration/misbehaviour.go @@ -68,8 +68,10 @@ func (s *CCVTestSuite) TestHandleConsumerMisbehaviour() { for _, v := range altValset.Validators { consuAddr := sdk.ConsAddress(v.Address.Bytes()) provAddr := s.providerApp.GetProviderKeeper().GetProviderAddrFromConsumerAddr(s.providerCtx(), s.consumerChain.ChainID, consuAddr) - s.providerApp.GetE2eStakingKeeper().ValidatorByConsAddr(s.providerCtx(), consuAddr) - val, ok := s.providerApp.GetProviderKeeper().stakingKeeper.GetValidatorByConsAddr(s.providerCtx(), provAddr) + val, ok := s.providerApp.GetE2eStakingKeeper().GetValidatorByConsAddr(s.providerCtx(), provAddr) + s.Require().True(ok) + s.Require().True(val.Jailed) + s.Require().True(s.providerApp.GetE2eSlashingKeeper().IsTombstoned(s.providerCtx(), provAddr)) } } diff --git a/x/ccv/provider/keeper/misbehaviour.go b/x/ccv/provider/keeper/misbehaviour.go index a3a4e3edef..a57add7564 100644 --- a/x/ccv/provider/keeper/misbehaviour.go +++ b/x/ccv/provider/keeper/misbehaviour.go @@ -36,6 +36,8 @@ func (k Keeper) HandleConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmty k.Logger(ctx).Error("validator not found or is unbonded", provAddr.String()) continue } + // TODO: continue if validator is already tombstoned/jailed + log + k.stakingKeeper.Jail(ctx, provAddr) k.slashingKeeper.JailUntil(ctx, provAddr, evidencetypes.DoubleSignJailEndTime) k.slashingKeeper.Tombstone(ctx, provAddr) // store misbehaviour? From 9c31932aafa91a078eba36a0b6deba4fddc97087 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Fri, 16 Jun 2023 17:45:48 +0200 Subject: [PATCH 15/45] format tests --- tests/integration/misbehaviour.go | 699 ++++++++++++++---------------- 1 file changed, 328 insertions(+), 371 deletions(-) diff --git a/tests/integration/misbehaviour.go b/tests/integration/misbehaviour.go index a3eb894fec..046d03fd16 100644 --- a/tests/integration/misbehaviour.go +++ b/tests/integration/misbehaviour.go @@ -1,12 +1,14 @@ package integration import ( - "fmt" "time" sdk "github.com/cosmos/cosmos-sdk/types" + ibcclientypes "github.com/cosmos/ibc-go/v4/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v4/modules/core/23-commitment/types" ibctmtypes "github.com/cosmos/ibc-go/v4/modules/light-clients/07-tendermint/types" + ibctestingmock "github.com/cosmos/ibc-go/v4/testing/mock" tmtypes "github.com/tendermint/tendermint/types" ) @@ -25,7 +27,6 @@ func (s *CCVTestSuite) TestHandleConsumerMisbehaviour() { s.setDefaultValSigningInfo(*v) } - // consumerConsState, _ := s.providerChain.GetConsensusState(s.path.EndpointA.ClientID, s.consumerChain.LastHeader.TrustedHeight) altTime := s.providerCtx().BlockTime().Add(time.Minute) clientHeight := s.consumerChain.LastHeader.TrustedHeight @@ -34,7 +35,6 @@ func (s *CCVTestSuite) TestHandleConsumerMisbehaviour() { altValset := tmtypes.NewValidatorSet(s.consumerChain.Vals.Validators[0:2]) altSigners := make(map[string]tmtypes.PrivValidator, 1) - // altSigners[altValset.Validators[0].Address.String()] = altPrivVal altSigners[clientTMValset.Validators[0].Address.String()] = clientSigners[clientTMValset.Validators[0].Address.String()] altSigners[clientTMValset.Validators[1].Address.String()] = clientSigners[clientTMValset.Validators[1].Address.String()] @@ -83,28 +83,25 @@ func (s *CCVTestSuite) TestCheckConsumerMisbehaviour() { // required to have the consumer client revision height greater than 0 s.SendEmptyVSCPacket() - // consumerConsState, _ := s.providerChain.GetConsensusState(s.path.EndpointA.ClientID, s.consumerChain.LastHeader.TrustedHeight) + consumerConsState, ok := s.providerChain.GetConsensusState(s.path.EndpointA.ClientID, s.consumerChain.LastHeader.TrustedHeight) + s.Require().True(ok) clientHeight := s.consumerChain.LastHeader.TrustedHeight clientTMValset := tmtypes.NewValidatorSet(s.consumerChain.Vals.Validators) clientSigners := s.consumerChain.Signers - // altPrivVal := ibctestingmock.NewPV() - // altPubKey, err := altPrivVal.GetPubKey() - // s.Require().NoError(err) - // altVal := tmtypes.NewValidator(altPubKey, 4) + altPrivVal := ibctestingmock.NewPV() + altPubKey, err := altPrivVal.GetPubKey() + s.Require().NoError(err) + altVal := tmtypes.NewValidator(altPubKey, 4) - // altValset := tmtypes.NewValidatorSet([]*tmtypes.Validator{altVal}) - altValset := tmtypes.NewValidatorSet(s.consumerChain.Vals.Validators[0:2]) + altValset := tmtypes.NewValidatorSet([]*tmtypes.Validator{altVal}) altSigners := make(map[string]tmtypes.PrivValidator, 1) - // altSigners[altValset.Validators[0].Address.String()] = altPrivVal - altSigners[clientTMValset.Validators[0].Address.String()] = clientSigners[clientTMValset.Validators[0].Address.String()] - altSigners[clientTMValset.Validators[1].Address.String()] = clientSigners[clientTMValset.Validators[1].Address.String()] + altSigners[altValset.Validators[0].Address.String()] = altPrivVal altTime := s.providerCtx().BlockTime().Add(time.Minute) - // heightPlus5 := ibcclientypes.NewHeight(0, clientHeight.RevisionHeight+5) - - // heightPlus3 := ibcclientypes.NewHeight(0, clientHeight.RevisionHeight+3) + heightPlus5 := ibcclientypes.NewHeight(0, clientHeight.RevisionHeight+5) + heightPlus3 := ibcclientypes.NewHeight(0, clientHeight.RevisionHeight+3) testCases := []struct { name string @@ -112,318 +109,318 @@ func (s *CCVTestSuite) TestCheckConsumerMisbehaviour() { malleate func() expPass bool }{ - // { - // "misbehaviour height is at same height as trusted height", - // func() *ibctmtypes.Misbehaviour { - // return &ibctmtypes.Misbehaviour{ - // ClientId: s.path.EndpointA.ClientID, - // Header1: s.consumerChain.CreateTMClientHeader( - // s.consumerChain.ChainID, - // int64(clientHeight.RevisionHeight), - // clientHeight, - // altTime, - // clientTMValset, - // clientTMValset, - // clientTMValset, - // clientSigners, - // ), - // Header2: s.consumerChain.CreateTMClientHeader( - // s.consumerChain.ChainID, - // int64(clientHeight.RevisionHeight), - // clientHeight, - // s.providerCtx().BlockTime(), - // clientTMValset, - // clientTMValset, - // clientTMValset, - // clientSigners, - // ), - // } - // }, - // func() {}, - // false, - // }, { - // "invalid chain ID", - // func() *ibctmtypes.Misbehaviour { - - // mb := &ibctmtypes.Misbehaviour{ - // ClientId: s.path.EndpointA.ClientID, - // Header1: s.consumerChain.CreateTMClientHeader( - // s.consumerChain.ChainID, - // int64(clientHeight.RevisionHeight+1), - // clientHeight, - // altTime, - // clientTMValset, - // clientTMValset, - // clientTMValset, - // clientSigners, - // ), - // Header2: s.consumerChain.CreateTMClientHeader( - // s.consumerChain.ChainID, - // int64(clientHeight.RevisionHeight+1), - // clientHeight, - // altTime, - // clientTMValset, - // clientTMValset, - // clientTMValset, - // clientSigners, - // ), - // } - - // mb.Header1.Header.ChainID = "wrongchainid" - // return mb - - // }, - // func() {}, - // false, - // }, - // { - // "invalid client ID", - // func() *ibctmtypes.Misbehaviour { - - // mb := &ibctmtypes.Misbehaviour{ - // ClientId: "wrongclientid", - // Header1: s.consumerChain.CreateTMClientHeader( - // s.consumerChain.ChainID, - // int64(clientHeight.RevisionHeight+1), - // clientHeight, - // altTime, - // clientTMValset, - // clientTMValset, - // clientTMValset, - // clientSigners, - // ), - // Header2: s.consumerChain.CreateTMClientHeader( - // s.consumerChain.ChainID, - // int64(clientHeight.RevisionHeight+1), - // clientHeight, - // altTime, - // clientTMValset, - // clientTMValset, - // clientTMValset, - // clientSigners, - // ), - // } - - // return mb - - // }, - // func() {}, - // false, - // }, { - // "different trusted height shouldn't pass", - // func() *ibctmtypes.Misbehaviour { - // return &ibctmtypes.Misbehaviour{ - // ClientId: s.path.EndpointA.ClientID, - // Header1: s.consumerChain.CreateTMClientHeader( - // s.consumerChain.ChainID, - // int64(clientHeight.RevisionHeight+1), - // clientHeight, - // altTime, - // clientTMValset, - // clientTMValset, - // clientTMValset, - // clientSigners, - // ), - // Header2: s.consumerChain.CreateTMClientHeader( - // s.consumerChain.ChainID, - // int64(clientHeight.RevisionHeight+1), - // heightPlus3, - // s.providerCtx().BlockTime(), - // clientTMValset, - // clientTMValset, - // clientTMValset, - // clientSigners, - // ), - // } - // }, - // func() {}, - // false, - // }, { - // "trusting period misbehavior should pass", - // func() *ibctmtypes.Misbehaviour { - // return &ibctmtypes.Misbehaviour{ - // ClientId: s.path.EndpointA.ClientID, - // Header1: s.consumerChain.CreateTMClientHeader( - // s.consumerChain.ChainID, - // int64(clientHeight.RevisionHeight+1), - // clientHeight, - // altTime, - // clientTMValset, - // clientTMValset, - // clientTMValset, - // clientSigners, - // ), - // Header2: s.consumerChain.CreateTMClientHeader( - // s.consumerChain.ChainID, - // int64(clientHeight.RevisionHeight+1), - // clientHeight, - // s.providerCtx().BlockTime(), - // clientTMValset, - // clientTMValset, - // clientTMValset, - // clientSigners, - // ), - // } - // }, - // func() {}, - // true, - // }, - // { - // "time misbehavior should pass", - // func() *ibctmtypes.Misbehaviour { - // return &ibctmtypes.Misbehaviour{ - // ClientId: s.path.EndpointA.ClientID, - // Header1: s.consumerChain.CreateTMClientHeader( - // s.consumerChain.ChainID, - // int64(clientHeight.RevisionHeight+5), - // clientHeight, - // s.providerCtx().BlockTime(), - // clientTMValset, - // clientTMValset, - // clientTMValset, - // clientSigners, - // ), - // Header2: s.consumerChain.CreateTMClientHeader( - // s.consumerChain.ChainID, - // int64(clientHeight.RevisionHeight+1), - // clientHeight, - // altTime, - // clientTMValset, - // clientTMValset, - // clientTMValset, - // clientSigners, - // ), - // } - // }, - // func() {}, - // true, - // }, - // { - // "both later height should pass", - // func() *ibctmtypes.Misbehaviour { - // return &ibctmtypes.Misbehaviour{ - // ClientId: s.path.EndpointA.ClientID, - // Header1: s.consumerChain.CreateTMClientHeader( - // s.consumerChain.ChainID, - // int64(heightPlus5.RevisionHeight+1), - // clientHeight, - // s.providerCtx().BlockTime(), - // clientTMValset, - // clientTMValset, - // clientTMValset, - // clientSigners, - // ), - // Header2: s.consumerChain.CreateTMClientHeader( - // s.consumerChain.ChainID, - // int64(heightPlus5.RevisionHeight+1), - // clientHeight, - // altTime, - // clientTMValset, - // clientTMValset, - // clientTMValset, - // clientSigners, - // ), - // } - // }, - // func() { - - // consumerConsState.(*ibctmtypes.ConsensusState).NextValidatorsHash = clientTMValset.Hash() - // clientState := ibctmtypes.NewClientState(s.consumerChain.ChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), []string{"upgrade", "upgradedIBCState"}, false, false) - - // // store trusted consensus state for Header2 - // intermediateConsState := &ibctmtypes.ConsensusState{ - // Timestamp: altTime, - // NextValidatorsHash: clientTMValset.Hash(), - // } - - // s.providerApp.GetIBCKeeper().ClientKeeper.SetClientConsensusState(s.providerCtx(), s.path.EndpointA.ClientID, heightPlus3, intermediateConsState) - - // clientState.LatestHeight = heightPlus3 - // s.providerApp.GetIBCKeeper().ClientKeeper.SetClientState(s.providerCtx(), s.path.EndpointA.ClientID, clientState) - // }, - // true, - // }, - // { - // "trusted ConsensusState1 not found", - // func() *ibctmtypes.Misbehaviour { - // return &ibctmtypes.Misbehaviour{ - // ClientId: s.path.EndpointA.ClientID, - // Header1: s.consumerChain.CreateTMClientHeader( - // s.consumerChain.ChainID, - // int64(clientHeight.RevisionHeight), - // heightPlus3, - // altTime, - // clientTMValset, - // clientTMValset, - // clientTMValset, - // clientSigners, - // ), - // Header2: s.consumerChain.CreateTMClientHeader( - // s.consumerChain.ChainID, - // int64(clientHeight.RevisionHeight), - // clientHeight, - // s.providerCtx().BlockTime(), - // clientTMValset, - // clientTMValset, - // clientTMValset, - // clientSigners, - // ), - // } - // }, - // func() {}, - // false, - // }, - // { - // "trusted ConsensusState2 not found", - // func() *ibctmtypes.Misbehaviour { - // return &ibctmtypes.Misbehaviour{ - // ClientId: s.path.EndpointA.ClientID, - // Header1: s.consumerChain.CreateTMClientHeader( - // s.consumerChain.ChainID, - // int64(clientHeight.RevisionHeight), - // clientHeight, - // altTime, - // clientTMValset, - // clientTMValset, - // clientTMValset, - // clientSigners, - // ), - // Header2: s.consumerChain.CreateTMClientHeader( - // s.consumerChain.ChainID, - // int64(clientHeight.RevisionHeight), - // heightPlus3, - // s.providerCtx().BlockTime(), - // clientTMValset, - // clientTMValset, - // clientTMValset, - // clientSigners, - // ), - // } - // }, - // func() {}, - // false, - // }, - // { - // "client state not found", - // func() *ibctmtypes.Misbehaviour { - // return &ibctmtypes.Misbehaviour{} - // }, - // func() {}, - // false, - // }, { - // "client already is not active - client is frozen", - // func() *ibctmtypes.Misbehaviour { - // return &ibctmtypes.Misbehaviour{} - // }, - // func() { - // consumerConsState.(*ibctmtypes.ConsensusState).NextValidatorsHash = clientTMValset.Hash() - // clientState := ibctmtypes.NewClientState(s.consumerChain.ChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), []string{"upgrade", "upgradedIBCState"}, false, false) - - // clientState.FrozenHeight = ibcclientypes.NewHeight(0, 1) - // s.providerApp.GetIBCKeeper().ClientKeeper.SetClientState(s.providerCtx(), s.path.EndpointA.ClientID, clientState) - // }, - // false, - // }, + { + "misbehaviour height is at same height as trusted height", + func() *ibctmtypes.Misbehaviour { + return &ibctmtypes.Misbehaviour{ + ClientId: s.path.EndpointA.ClientID, + Header1: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight), + clientHeight, + altTime, + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + Header2: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight), + clientHeight, + s.providerCtx().BlockTime(), + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + } + }, + func() {}, + false, + }, { + "invalid chain ID", + func() *ibctmtypes.Misbehaviour { + + mb := &ibctmtypes.Misbehaviour{ + ClientId: s.path.EndpointA.ClientID, + Header1: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime, + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + Header2: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime, + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + } + + mb.Header1.Header.ChainID = "wrongchainid" + return mb + + }, + func() {}, + false, + }, + { + "invalid client ID", + func() *ibctmtypes.Misbehaviour { + + mb := &ibctmtypes.Misbehaviour{ + ClientId: "wrongclientid", + Header1: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime, + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + Header2: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime, + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + } + + return mb + + }, + func() {}, + false, + }, { + "different trusted height shouldn't pass", + func() *ibctmtypes.Misbehaviour { + return &ibctmtypes.Misbehaviour{ + ClientId: s.path.EndpointA.ClientID, + Header1: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime, + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + Header2: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + heightPlus3, + s.providerCtx().BlockTime(), + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + } + }, + func() {}, + false, + }, { + "trusting period misbehavior should pass", + func() *ibctmtypes.Misbehaviour { + return &ibctmtypes.Misbehaviour{ + ClientId: s.path.EndpointA.ClientID, + Header1: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime, + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + Header2: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + s.providerCtx().BlockTime(), + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + } + }, + func() {}, + true, + }, + { + "time misbehavior should pass", + func() *ibctmtypes.Misbehaviour { + return &ibctmtypes.Misbehaviour{ + ClientId: s.path.EndpointA.ClientID, + Header1: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+5), + clientHeight, + s.providerCtx().BlockTime(), + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + Header2: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime, + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + } + }, + func() {}, + true, + }, + { + "both later height should pass", + func() *ibctmtypes.Misbehaviour { + return &ibctmtypes.Misbehaviour{ + ClientId: s.path.EndpointA.ClientID, + Header1: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(heightPlus5.RevisionHeight+1), + clientHeight, + s.providerCtx().BlockTime(), + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + Header2: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(heightPlus5.RevisionHeight+1), + clientHeight, + altTime, + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + } + }, + func() { + + consumerConsState.(*ibctmtypes.ConsensusState).NextValidatorsHash = clientTMValset.Hash() + clientState := ibctmtypes.NewClientState(s.consumerChain.ChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), []string{"upgrade", "upgradedIBCState"}, false, false) + + // store trusted consensus state for Header2 + intermediateConsState := &ibctmtypes.ConsensusState{ + Timestamp: altTime, + NextValidatorsHash: clientTMValset.Hash(), + } + + s.providerApp.GetIBCKeeper().ClientKeeper.SetClientConsensusState(s.providerCtx(), s.path.EndpointA.ClientID, heightPlus3, intermediateConsState) + + clientState.LatestHeight = heightPlus3 + s.providerApp.GetIBCKeeper().ClientKeeper.SetClientState(s.providerCtx(), s.path.EndpointA.ClientID, clientState) + }, + true, + }, + { + "trusted ConsensusState1 not found", + func() *ibctmtypes.Misbehaviour { + return &ibctmtypes.Misbehaviour{ + ClientId: s.path.EndpointA.ClientID, + Header1: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight), + heightPlus3, + altTime, + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + Header2: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight), + clientHeight, + s.providerCtx().BlockTime(), + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + } + }, + func() {}, + false, + }, + { + "trusted ConsensusState2 not found", + func() *ibctmtypes.Misbehaviour { + return &ibctmtypes.Misbehaviour{ + ClientId: s.path.EndpointA.ClientID, + Header1: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight), + clientHeight, + altTime, + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + Header2: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight), + heightPlus3, + s.providerCtx().BlockTime(), + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + } + }, + func() {}, + false, + }, + { + "client state not found", + func() *ibctmtypes.Misbehaviour { + return &ibctmtypes.Misbehaviour{} + }, + func() {}, + false, + }, { + "client already is not active - client is frozen", + func() *ibctmtypes.Misbehaviour { + return &ibctmtypes.Misbehaviour{} + }, + func() { + consumerConsState.(*ibctmtypes.ConsensusState).NextValidatorsHash = clientTMValset.Hash() + clientState := ibctmtypes.NewClientState(s.consumerChain.ChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), []string{"upgrade", "upgradedIBCState"}, false, false) + + clientState.FrozenHeight = ibcclientypes.NewHeight(0, 1) + s.providerApp.GetIBCKeeper().ClientKeeper.SetClientState(s.providerCtx(), s.path.EndpointA.ClientID, clientState) + }, + false, + }, { "misbehaviour check failed", func() *ibctmtypes.Misbehaviour { @@ -490,7 +487,6 @@ func (s *CCVTestSuite) TestGetByzantineValidators() { // required to have the consumer client revision height greater than 0 s.SendEmptyVSCPacket() - // consumerConsState, _ := s.providerChain.GetConsensusState(s.path.EndpointA.ClientID, s.consumerChain.LastHeader.TrustedHeight) altTime := s.providerCtx().BlockTime().Add(time.Minute) clientHeight := s.consumerChain.LastHeader.TrustedHeight @@ -499,7 +495,6 @@ func (s *CCVTestSuite) TestGetByzantineValidators() { altValset := tmtypes.NewValidatorSet(s.consumerChain.Vals.Validators[0:2]) altSigners := make(map[string]tmtypes.PrivValidator, 1) - // altSigners[altValset.Validators[0].Address.String()] = altPrivVal altSigners[clientTMValset.Validators[0].Address.String()] = clientSigners[clientTMValset.Validators[0].Address.String()] altSigners[clientTMValset.Validators[1].Address.String()] = clientSigners[clientTMValset.Validators[1].Address.String()] @@ -534,50 +529,12 @@ func (s *CCVTestSuite) TestGetByzantineValidators() { s.NoError(err) - val, err := s.providerApp.GetProviderKeeper().GetByzantineValidators( + byzVals, err := s.providerApp.GetProviderKeeper().GetByzantineValidators( s.providerCtx(), *misb, ) - s.NoError(err) + s.Require().Equal(len(altValset.Validators), len(byzVals)) - fmt.Println(len(val)) - fmt.Println(len(s.consumerChain.Vals.Validators)) + // TODO: check that the byzantine validators == altValset.Validators } - -// s.SetupCCVChannel(s.path) -// // required to have the consumer client revision height greater than 0 -// s.SendEmptyVSCPacket() -// s.consumerCtx().BlockHeight() -// // get consumer client state -// // consumerClientState := s.providerChain.GetClientState(s.path.EndpointA.ClientID) - -// // create two conflicting headers and forge them -// // commit new block on consumer - -// s.coordinator.CommitBlock(s.consumerChain) - -// // get trusted height from client state -// trustedHeight := s.providerChain.GetClientState(s.path.EndpointA.ClientID).GetLatestHeight().(clienttypes.Height) -// tmTrustedVals := s.consumerChain.Vals -// // get last consumer header -// header := s.consumerChain.LastHeader - -// header.TrustedHeight = trustedHeight -// trustedVals, err := tmTrustedVals.ToProto() -// s.NoError(err) -// header.TrustedValidators = trustedVals - -// msg, err := clienttypes.NewMsgUpdateClient( -// s.path.EndpointB.ClientID, header, -// s.path.EndpointB.Chain.SenderAccount.GetAddress().String(), -// ) -// s.NoError(err) - -// header2 := *header -// header2.SignedHeader.Commit.BlockID.Hash = []byte("forge_hash") - -// _, err = s.providerChain.SendMsgs(msg) -// s.NoError(err) - -// fmt.Printf("%+v\n", msg.Header) From 42204e18dfabbec6309d8889a97df8792fb9a7b4 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Fri, 16 Jun 2023 17:47:24 +0200 Subject: [PATCH 16/45] doc --- tests/integration/misbehaviour.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/misbehaviour.go b/tests/integration/misbehaviour.go index 046d03fd16..079f0d996c 100644 --- a/tests/integration/misbehaviour.go +++ b/tests/integration/misbehaviour.go @@ -422,7 +422,7 @@ func (s *CCVTestSuite) TestCheckConsumerMisbehaviour() { false, }, { - "misbehaviour check failed", + "misbehaviour check failed", //TODO: verify as client is already frozen func() *ibctmtypes.Misbehaviour { return &ibctmtypes.Misbehaviour{ ClientId: s.path.EndpointA.ClientID, @@ -449,7 +449,7 @@ func (s *CCVTestSuite) TestCheckConsumerMisbehaviour() { } }, func() {}, - true, + false, }, } From 806a005c7e62d9b070ed634f004d11a79a874ddd Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Wed, 21 Jun 2023 09:30:59 +0200 Subject: [PATCH 17/45] save --- tests/integration/misbehaviour.go | 579 +++++++------------------- testutil/integration/debug_test.go | 8 +- testutil/keeper/mocks.go | 14 + x/ccv/provider/keeper/misbehaviour.go | 162 +++---- x/ccv/provider/keeper/msg_server.go | 2 +- x/ccv/types/expected_keepers.go | 1 + 6 files changed, 228 insertions(+), 538 deletions(-) diff --git a/tests/integration/misbehaviour.go b/tests/integration/misbehaviour.go index 079f0d996c..8ee0a71cde 100644 --- a/tests/integration/misbehaviour.go +++ b/tests/integration/misbehaviour.go @@ -1,14 +1,13 @@ package integration import ( + "fmt" "time" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/interchain-security/v2/x/ccv/provider/types" - ibcclientypes "github.com/cosmos/ibc-go/v4/modules/core/02-client/types" - commitmenttypes "github.com/cosmos/ibc-go/v4/modules/core/23-commitment/types" ibctmtypes "github.com/cosmos/ibc-go/v4/modules/light-clients/07-tendermint/types" - ibctestingmock "github.com/cosmos/ibc-go/v4/testing/mock" tmtypes "github.com/tendermint/tendermint/types" ) @@ -67,421 +66,23 @@ func (s *CCVTestSuite) TestHandleConsumerMisbehaviour() { for _, v := range altValset.Validators { consuAddr := sdk.ConsAddress(v.Address.Bytes()) - provAddr := s.providerApp.GetProviderKeeper().GetProviderAddrFromConsumerAddr(s.providerCtx(), s.consumerChain.ChainID, consuAddr) - val, ok := s.providerApp.GetE2eStakingKeeper().GetValidatorByConsAddr(s.providerCtx(), provAddr) + provAddr := s.providerApp.GetProviderKeeper().GetProviderAddrFromConsumerAddr(s.providerCtx(), s.consumerChain.ChainID, types.ConsumerConsAddress{consuAddr}) + val, ok := s.providerApp.GetTestStakingKeeper().GetValidatorByConsAddr(s.providerCtx(), provAddr.Address) s.Require().True(ok) s.Require().True(val.Jailed) - s.Require().True(s.providerApp.GetE2eSlashingKeeper().IsTombstoned(s.providerCtx(), provAddr)) + s.Require().True(s.providerApp.GetTestSlashingKeeper().IsTombstoned(s.providerCtx(), provAddr.Address)) } } -// mostly based on TestCheckMisbehaviourAndUpdateState in ibc-go/modules/core/02-client/keeper/client_test.go -func (s *CCVTestSuite) TestCheckConsumerMisbehaviour() { +func (s *CCVTestSuite) TestConstructLigthClientEvidence() { - s.SetupCCVChannel(s.path) - // required to have the consumer client revision height greater than 0 - s.SendEmptyVSCPacket() - - consumerConsState, ok := s.providerChain.GetConsensusState(s.path.EndpointA.ClientID, s.consumerChain.LastHeader.TrustedHeight) - s.Require().True(ok) - - clientHeight := s.consumerChain.LastHeader.TrustedHeight - clientTMValset := tmtypes.NewValidatorSet(s.consumerChain.Vals.Validators) - clientSigners := s.consumerChain.Signers - - altPrivVal := ibctestingmock.NewPV() - altPubKey, err := altPrivVal.GetPubKey() - s.Require().NoError(err) - altVal := tmtypes.NewValidator(altPubKey, 4) - - altValset := tmtypes.NewValidatorSet([]*tmtypes.Validator{altVal}) - altSigners := make(map[string]tmtypes.PrivValidator, 1) - altSigners[altValset.Validators[0].Address.String()] = altPrivVal - - altTime := s.providerCtx().BlockTime().Add(time.Minute) - heightPlus5 := ibcclientypes.NewHeight(0, clientHeight.RevisionHeight+5) - heightPlus3 := ibcclientypes.NewHeight(0, clientHeight.RevisionHeight+3) - - testCases := []struct { - name string - misbehaviour func() *ibctmtypes.Misbehaviour - malleate func() - expPass bool - }{ - { - "misbehaviour height is at same height as trusted height", - func() *ibctmtypes.Misbehaviour { - return &ibctmtypes.Misbehaviour{ - ClientId: s.path.EndpointA.ClientID, - Header1: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight), - clientHeight, - altTime, - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - Header2: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight), - clientHeight, - s.providerCtx().BlockTime(), - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - } - }, - func() {}, - false, - }, { - "invalid chain ID", - func() *ibctmtypes.Misbehaviour { - - mb := &ibctmtypes.Misbehaviour{ - ClientId: s.path.EndpointA.ClientID, - Header1: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight+1), - clientHeight, - altTime, - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - Header2: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight+1), - clientHeight, - altTime, - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - } - - mb.Header1.Header.ChainID = "wrongchainid" - return mb - - }, - func() {}, - false, - }, - { - "invalid client ID", - func() *ibctmtypes.Misbehaviour { - - mb := &ibctmtypes.Misbehaviour{ - ClientId: "wrongclientid", - Header1: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight+1), - clientHeight, - altTime, - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - Header2: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight+1), - clientHeight, - altTime, - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - } - - return mb - - }, - func() {}, - false, - }, { - "different trusted height shouldn't pass", - func() *ibctmtypes.Misbehaviour { - return &ibctmtypes.Misbehaviour{ - ClientId: s.path.EndpointA.ClientID, - Header1: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight+1), - clientHeight, - altTime, - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - Header2: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight+1), - heightPlus3, - s.providerCtx().BlockTime(), - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - } - }, - func() {}, - false, - }, { - "trusting period misbehavior should pass", - func() *ibctmtypes.Misbehaviour { - return &ibctmtypes.Misbehaviour{ - ClientId: s.path.EndpointA.ClientID, - Header1: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight+1), - clientHeight, - altTime, - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - Header2: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight+1), - clientHeight, - s.providerCtx().BlockTime(), - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - } - }, - func() {}, - true, - }, - { - "time misbehavior should pass", - func() *ibctmtypes.Misbehaviour { - return &ibctmtypes.Misbehaviour{ - ClientId: s.path.EndpointA.ClientID, - Header1: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight+5), - clientHeight, - s.providerCtx().BlockTime(), - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - Header2: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight+1), - clientHeight, - altTime, - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - } - }, - func() {}, - true, - }, - { - "both later height should pass", - func() *ibctmtypes.Misbehaviour { - return &ibctmtypes.Misbehaviour{ - ClientId: s.path.EndpointA.ClientID, - Header1: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(heightPlus5.RevisionHeight+1), - clientHeight, - s.providerCtx().BlockTime(), - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - Header2: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(heightPlus5.RevisionHeight+1), - clientHeight, - altTime, - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - } - }, - func() { - - consumerConsState.(*ibctmtypes.ConsensusState).NextValidatorsHash = clientTMValset.Hash() - clientState := ibctmtypes.NewClientState(s.consumerChain.ChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), []string{"upgrade", "upgradedIBCState"}, false, false) - - // store trusted consensus state for Header2 - intermediateConsState := &ibctmtypes.ConsensusState{ - Timestamp: altTime, - NextValidatorsHash: clientTMValset.Hash(), - } - - s.providerApp.GetIBCKeeper().ClientKeeper.SetClientConsensusState(s.providerCtx(), s.path.EndpointA.ClientID, heightPlus3, intermediateConsState) - - clientState.LatestHeight = heightPlus3 - s.providerApp.GetIBCKeeper().ClientKeeper.SetClientState(s.providerCtx(), s.path.EndpointA.ClientID, clientState) - }, - true, - }, - { - "trusted ConsensusState1 not found", - func() *ibctmtypes.Misbehaviour { - return &ibctmtypes.Misbehaviour{ - ClientId: s.path.EndpointA.ClientID, - Header1: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight), - heightPlus3, - altTime, - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - Header2: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight), - clientHeight, - s.providerCtx().BlockTime(), - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - } - }, - func() {}, - false, - }, - { - "trusted ConsensusState2 not found", - func() *ibctmtypes.Misbehaviour { - return &ibctmtypes.Misbehaviour{ - ClientId: s.path.EndpointA.ClientID, - Header1: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight), - clientHeight, - altTime, - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - Header2: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight), - heightPlus3, - s.providerCtx().BlockTime(), - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - } - }, - func() {}, - false, - }, - { - "client state not found", - func() *ibctmtypes.Misbehaviour { - return &ibctmtypes.Misbehaviour{} - }, - func() {}, - false, - }, { - "client already is not active - client is frozen", - func() *ibctmtypes.Misbehaviour { - return &ibctmtypes.Misbehaviour{} - }, - func() { - consumerConsState.(*ibctmtypes.ConsensusState).NextValidatorsHash = clientTMValset.Hash() - clientState := ibctmtypes.NewClientState(s.consumerChain.ChainID, ibctmtypes.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), []string{"upgrade", "upgradedIBCState"}, false, false) - - clientState.FrozenHeight = ibcclientypes.NewHeight(0, 1) - s.providerApp.GetIBCKeeper().ClientKeeper.SetClientState(s.providerCtx(), s.path.EndpointA.ClientID, clientState) - }, - false, - }, - { - "misbehaviour check failed", //TODO: verify as client is already frozen - func() *ibctmtypes.Misbehaviour { - return &ibctmtypes.Misbehaviour{ - ClientId: s.path.EndpointA.ClientID, - Header1: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight+1), - clientHeight, - s.providerCtx().BlockTime(), - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - Header2: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight+1), - clientHeight, - altTime, - altValset, - altValset, - clientTMValset, - altSigners, - ), - } - }, - func() {}, - false, - }, - } - - for i, tc := range testCases { - - s.Run(tc.name, func() { - // run each test against fresh client states - cCtx, _ := s.providerCtx().CacheContext() + // test cases + // misbehaviour nil + // misbheaviour header 1 nil + // misbehaviour header 2 nil - tc.malleate() - - err := s.providerApp.GetProviderKeeper().CheckConsumerMisbehaviour( - cCtx, - *tc.misbehaviour(), - ) - - // Misbehaviour passed - if tc.expPass { - s.NoError(err, "valid test case %s failed with error %s", tc.name, err) - clientState, found := s.providerApp.GetIBCKeeper().ClientKeeper.GetClientState(cCtx, tc.misbehaviour().ClientId) - s.Require().True(found, "valid test case %d failed: %s", i, tc.name) - s.Require().True(!clientState.(*ibctmtypes.ClientState).FrozenHeight.IsZero(), "valid test case %d failed: %s", i, tc.name) - - } else { - // Misbehaviour rejected - s.Require().Error(err, "invalid test case %d passed: %s", i, tc.name) - } - }) - } -} - -func (s *CCVTestSuite) TestGetByzantineValidators() { + // misbehaviour no common height s.SetupCCVChannel(s.path) // required to have the consumer client revision height greater than 0 @@ -522,19 +123,153 @@ func (s *CCVTestSuite) TestGetByzantineValidators() { ), } - err := s.providerApp.GetProviderKeeper().CheckConsumerMisbehaviour( - s.providerCtx(), - *misb, - ) + _ = misb - s.NoError(err) + // emptyHeader := &ibctmtypes.Header{} - byzVals, err := s.providerApp.GetProviderKeeper().GetByzantineValidators( - s.providerCtx(), - *misb, - ) - s.NoError(err) - s.Require().Equal(len(altValset.Validators), len(byzVals)) + testCases := []struct { + name string + misbehaviour *ibctmtypes.Misbehaviour + expPass bool + }{ + { + "invalid misbehaviour - Header1 is empty", + &ibctmtypes.Misbehaviour{ + Header1: &ibctmtypes.Header{}, + Header2: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime, + altValset, + altValset, + clientTMValset, + altSigners, + )}, + false, + }, { + "invalid misbehaviour - Header2 is empty", + &ibctmtypes.Misbehaviour{ + Header1: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime, + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + Header2: &ibctmtypes.Header{}, + }, + false, + }, { + "invalid misbehaviour - ClientId is empty", + &ibctmtypes.Misbehaviour{ + ClientId: "unknown-client-id", + Header1: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime, + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + Header2: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime, + altValset, + altValset, + clientTMValset, + altSigners, + ), + }, + false, + }, + { + "light client attack - lunatic attack", + &ibctmtypes.Misbehaviour{ + ClientId: s.path.EndpointA.ClientID, + Header1: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime, + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + Header2: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime, + altValset, + altValset, + clientTMValset, + altSigners, + ), + }, + true, + }, { + "light client attack - equivocation", + &ibctmtypes.Misbehaviour{ + ClientId: s.path.EndpointA.ClientID, + Header1: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime, + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + Header2: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime, + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + }, + true, + }, + } + + // check how it's tested on CometBFTNewExtensionOptionsDecorator + + for _, tc := range testCases { + s.Run(tc.name, func() { + ev, err := s.providerApp.GetProviderKeeper().ConstructLigthClientEvidence( + s.providerCtx(), + *tc.misbehaviour, + ) + if tc.expPass { + s.NoError(err) + s.Require().Equal(len(altValset.Validators), len(ev.ByzantineValidators)) + fmt.Println("headers 2 validators") + + for _, v := range tc.misbehaviour.Header2.ValidatorSet.Validators { + fmt.Println(v.String()) + } + fmt.Println("byzantine validators") + for _, v := range ev.ByzantineValidators { + fmt.Println(v.String()) + } + // TODO: check that the byzantine validators == altValset.Validators + } else { + s.Error(err) + } + }) + } - // TODO: check that the byzantine validators == altValset.Validators } diff --git a/testutil/integration/debug_test.go b/testutil/integration/debug_test.go index 0b87bdf92a..da2d75355c 100644 --- a/testutil/integration/debug_test.go +++ b/testutil/integration/debug_test.go @@ -265,10 +265,6 @@ func TestHandleConsumerMisbehaviour(t *testing.T) { runCCVTestByName(t, "TestHandleConsumerMisbehaviour") } -func TestCheckConsumerMisbehaviour(t *testing.T) { - runCCVTestByName(t, "TestCheckConsumerMisbehaviour") -} - -func TestGetByzantineValidators(t *testing.T) { - runCCVTestByName(t, "TestGetByzantineValidators") +func TestConstructLigthClientEvidence(t *testing.T) { + runCCVTestByName(t, "TestConstructLigthClientEvidence") } diff --git a/testutil/keeper/mocks.go b/testutil/keeper/mocks.go index dc01a7f094..1e792e3a4a 100644 --- a/testutil/keeper/mocks.go +++ b/testutil/keeper/mocks.go @@ -678,6 +678,20 @@ func (m *MockClientKeeper) EXPECT() *MockClientKeeperMockRecorder { return m.recorder } +// CheckMisbehaviourAndUpdateState mocks base method. +func (m *MockClientKeeper) CheckMisbehaviourAndUpdateState(ctx types.Context, misbehaviour exported.Misbehaviour) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CheckMisbehaviourAndUpdateState", ctx, misbehaviour) + ret0, _ := ret[0].(error) + return ret0 +} + +// CheckMisbehaviourAndUpdateState indicates an expected call of CheckMisbehaviourAndUpdateState. +func (mr *MockClientKeeperMockRecorder) CheckMisbehaviourAndUpdateState(ctx, misbehaviour interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckMisbehaviourAndUpdateState", reflect.TypeOf((*MockClientKeeper)(nil).CheckMisbehaviourAndUpdateState), ctx, misbehaviour) +} + // ClientStore mocks base method. func (m *MockClientKeeper) ClientStore(ctx types.Context, clientID string) types.KVStore { m.ctrl.T.Helper() diff --git a/x/ccv/provider/keeper/misbehaviour.go b/x/ccv/provider/keeper/misbehaviour.go index a57add7564..8df61c4bb5 100644 --- a/x/ccv/provider/keeper/misbehaviour.go +++ b/x/ccv/provider/keeper/misbehaviour.go @@ -4,153 +4,91 @@ import ( "fmt" "time" + "github.com/cosmos/interchain-security/v2/x/ccv/provider/types" + sdk "github.com/cosmos/cosmos-sdk/types" evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" - "github.com/cosmos/ibc-go/v4/modules/core/exported" ibctmtypes "github.com/cosmos/ibc-go/v4/modules/light-clients/07-tendermint/types" tmtypes "github.com/tendermint/tendermint/types" ) +// HandleConsumerMisbehaviour checks whether the given IBC misbehaviour is valid and, if they are, the misbehaving +// CheckConsumerMisbehaviour check that the given IBC misbehaviour headers forms a valid light client attck evidence. +// proceed to the jailing and tombstoning of the bzyantine validators. func (k Keeper) HandleConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) error { - if err := k.CheckConsumerMisbehaviour(ctx, misbehaviour); err != nil { - return err - } + logger := ctx.Logger() - byzantineValidators, err := k.GetByzantineValidators(ctx, misbehaviour) - if err != nil { + if err := k.clientKeeper.CheckMisbehaviourAndUpdateState(ctx, &misbehaviour); err != nil { return err } // Since the misbehaviour packet was received within the trusting period // w.r.t to the last trusted consensus it entails that the infraction age // isn't too old. see ibc-go/modules/light-clients/07-tendermint/types/misbehaviour_handle.go - for _, v := range byzantineValidators { - // convert address to key assigned - consuAddr := sdk.ConsAddress(v.Address.Bytes()) - provAddr := k.GetProviderAddrFromConsumerAddr(ctx, misbehaviour.Header1.Header.ChainID, consuAddr) - k.stakingKeeper.ValidatorByConsAddr(ctx, consuAddr) - val, ok := k.stakingKeeper.GetValidatorByConsAddr(ctx, provAddr) - if !ok || val.IsUnbonded() { - // Defensive: Simulation doesn't take unbonding periods into account, and - // Tendermint might break this assumption at some point. - k.Logger(ctx).Error("validator not found or is unbonded", provAddr.String()) - continue - } - // TODO: continue if validator is already tombstoned/jailed + log - k.stakingKeeper.Jail(ctx, provAddr) - k.slashingKeeper.JailUntil(ctx, provAddr, evidencetypes.DoubleSignJailEndTime) - k.slashingKeeper.Tombstone(ctx, provAddr) - // store misbehaviour? - } - - logger := ctx.Logger() - logger.Info( - "confirmed equivocation", - "byzantine validators", byzantineValidators, - ) - - return nil -} - -func (k Keeper) CheckConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) error { - - clientID := misbehaviour.GetClientID() - - clientState, found := k.clientKeeper.GetClientState(ctx, clientID) - if !found { - return fmt.Errorf("types.ErrClientNotFound cannot check misbehaviour for client with ID %s", clientID) - } - - clientStore := k.clientKeeper.ClientStore(ctx, misbehaviour.GetClientID()) - - if status := clientState.Status(ctx, clientStore, k.cdc); status != exported.Active { - return fmt.Errorf("types.ErrClientNotActive cannot process misbehaviour for client (%s) with status %s", clientID, status) - } - - if err := misbehaviour.ValidateBasic(); err != nil { - return err - } - clientState, err := clientState.CheckMisbehaviourAndUpdateState(ctx, k.cdc, clientStore, &misbehaviour) + // construct a ligth client attack evidence + evidence, err := k.ConstructLigthClientEvidence(ctx, misbehaviour) if err != nil { return err } - k.clientKeeper.SetClientState(ctx, clientID, clientState) - k.Logger(ctx).Info("client frozen due to misbehaviour", "client-id", clientID) - - // get byzantine validators - sh, err := tmtypes.SignedHeaderFromProto(misbehaviour.Header1.SignedHeader) - if err != nil { - return err - } + // jail and tombstone the byzantine validators + for _, v := range evidence.ByzantineValidators { + // convert consumer consensus address + consuAddr := sdk.ConsAddress(v.Address.Bytes()) + provAddr := k.GetProviderAddrFromConsumerAddr(ctx, misbehaviour.Header1.Header.ChainID, types.ConsumerConsAddress{consuAddr}) + k.stakingKeeper.ValidatorByConsAddr(ctx, consuAddr) + val, ok := k.stakingKeeper.GetValidatorByConsAddr(ctx, provAddr.Address) - vs, err := tmtypes.ValidatorSetFromProto(misbehaviour.Header1.ValidatorSet) - if err != nil { - return err - } - ev := tmtypes.LightClientAttackEvidence{ - ConflictingBlock: &tmtypes.LightBlock{SignedHeader: sh, ValidatorSet: vs}, - } + if !ok || val.IsUnbonded() { + logger.Error("validator not found or is unbonded", provAddr.String()) + continue + } - h2, err := tmtypes.HeaderFromProto(misbehaviour.Header2.Header) - if err != nil { - return err - } + // jail validator if not already + if !val.IsJailed() { + k.stakingKeeper.Jail(ctx, provAddr.Address) + } - // WIP: return byzantine validators according to the light client committed + // tombstone validator if not already + if !k.slashingKeeper.IsTombstoned(ctx, provAddr.Address) { + k.slashingKeeper.Tombstone(ctx, provAddr.Address) + } - // if this is an equivocation or amnesia attack, i.e. the validator sets are the same, then we - // return the height of the conflicting block else if it is a lunatic attack and the validator sets - // are not the same then we send the height of the common header. - if ev.ConflictingHeaderIsInvalid(&h2) { - ev.CommonHeight = misbehaviour.Header2.Header.Height - ev.Timestamp = misbehaviour.Header2.Header.Time - ev.TotalVotingPower = misbehaviour.Header2.ValidatorSet.TotalVotingPower - } else { - ev.CommonHeight = misbehaviour.Header1.Header.Height - ev.Timestamp = misbehaviour.Header1.Header.Time - ev.TotalVotingPower = misbehaviour.Header1.ValidatorSet.TotalVotingPower + // update jail time to end after double sign jail duration + k.slashingKeeper.JailUntil(ctx, provAddr.Address, evidencetypes.DoubleSignJailEndTime) } - ev.ByzantineValidators = ev.GetByzantineValidators(vs, sh) - - logger := ctx.Logger() logger.Info( "confirmed equivocation", - "byzantine validators", ev.ByzantineValidators, + "byzantine validators", evidence.ByzantineValidators, ) - // TBD - // defer func() { - // telemetry.IncrCounterWithLabels( - // []string{"ibc", "client", "misbehaviour"}, - // 1, - // []metrics.Label{ - // telemetry.NewLabel(types.LabelClientType, misbehaviour.ClientType()), - // telemetry.NewLabel(types.LabelClientID, misbehaviour.GetClientID()), - // }, - // ) - // }() - - // EmitSubmitMisbehaviourEvent(ctx, clientID, clientState) return nil } -func (k Keeper) GetByzantineValidators(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) ([]*tmtypes.Validator, error) { - trusted, err := HeaderToLightBlock(*misbehaviour.Header1) +// ConstructLigthClientEvidence constructs and returns a CometBFT Ligth Client Attack(LCA) evidence struct +// from the given misbehaviour +func (k Keeper) ConstructLigthClientEvidence(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) (*tmtypes.LightClientAttackEvidence, error) { + + // construct the trusted and conflicetd ligth blocks + trusted, err := headerToLightBlock(*misbehaviour.Header1) if err != nil { return nil, err } - conflicted, err := HeaderToLightBlock(*misbehaviour.Header2) + conflicted, err := headerToLightBlock(*misbehaviour.Header2) if err != nil { return nil, err } - commonHeight, commonTs, commonValset, err := k.GetCommonFromMisbehaviour(ctx, misbehaviour) + + // get common header using the IBC misbehaviour + commonHeight, commonTs, commonValset, err := k.GetTrustedInfoFromMisbehaviour(ctx, misbehaviour) if err != nil { return nil, err } + // construct the LCA evidence by copying the CometBFT constructor + // see newLightClientAttackEvidence() in tendermint/light/detector.go ev := tmtypes.LightClientAttackEvidence{ ConflictingBlock: conflicted, } @@ -165,11 +103,16 @@ func (k Keeper) GetByzantineValidators(ctx sdk.Context, misbehaviour ibctmtypes. ev.TotalVotingPower = trusted.ValidatorSet.TotalVotingPower() } - return ev.GetByzantineValidators(commonValset, trusted.SignedHeader), nil + ev.ByzantineValidators = ev.GetByzantineValidators(commonValset, trusted.SignedHeader) + + return &ev, nil } -func (k Keeper) GetCommonFromMisbehaviour(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) (int64, time.Time, *tmtypes.ValidatorSet, error) { - // A common trusted height is required +// GetCommonFromMisbehaviour checks whether the given ibc misbehaviour's headers share common trusted height +// and that a consensus state exists for this height. In this case, it returns the associated trusted height, timestamp and valset. +func (k Keeper) GetTrustedInfoFromMisbehaviour(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) (int64, time.Time, *tmtypes.ValidatorSet, error) { + + // a common trusted height is required commonHeight := misbehaviour.Header1.TrustedHeight if !commonHeight.EQ(misbehaviour.Header2.TrustedHeight) { return 0, time.Time{}, nil, fmt.Errorf("misbehaviour headers have different trusted height: %v , %v", commonHeight, misbehaviour.Header2.TrustedHeight) @@ -188,7 +131,8 @@ func (k Keeper) GetCommonFromMisbehaviour(ctx sdk.Context, misbehaviour ibctmtyp return int64(commonHeight.RevisionHeight), time.Unix(0, int64(cs.GetTimestamp())), vs, nil } -func HeaderToLightBlock(h ibctmtypes.Header) (*tmtypes.LightBlock, error) { +// headerToLightBlock returns a CometBFT ligth block from the given IBC header +func headerToLightBlock(h ibctmtypes.Header) (*tmtypes.LightBlock, error) { sh, err := tmtypes.SignedHeaderFromProto(h.SignedHeader) if err != nil { return nil, err diff --git a/x/ccv/provider/keeper/msg_server.go b/x/ccv/provider/keeper/msg_server.go index f675423c69..ca22b66f0d 100644 --- a/x/ccv/provider/keeper/msg_server.go +++ b/x/ccv/provider/keeper/msg_server.go @@ -129,7 +129,7 @@ func (k msgServer) RegisterConsumerRewardDenom(goCtx context.Context, msg *types func (k msgServer) SubmitConsumerMisbehaviour(goCtx context.Context, msg *types.MsgSubmitConsumerMisbehaviour) (*types.MsgSubmitConsumerMisbehaviourResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) - if err := k.Keeper.CheckConsumerMisbehaviour(ctx, *msg.Misbehaviour); err != nil { + if err := k.Keeper.HandleConsumerMisbehaviour(ctx, *msg.Misbehaviour); err != nil { return &types.MsgSubmitConsumerMisbehaviourResponse{}, err } diff --git a/x/ccv/types/expected_keepers.go b/x/ccv/types/expected_keepers.go index d24400b114..b4dea9b670 100644 --- a/x/ccv/types/expected_keepers.go +++ b/x/ccv/types/expected_keepers.go @@ -89,6 +89,7 @@ type ClientKeeper interface { ClientStore(ctx sdk.Context, clientID string) sdk.KVStore SetClientState(ctx sdk.Context, clientID string, clientState ibcexported.ClientState) GetClientConsensusState(ctx sdk.Context, clientID string, height ibcexported.Height) (ibcexported.ConsensusState, bool) + CheckMisbehaviourAndUpdateState(ctx sdk.Context, misbehaviour ibcexported.Misbehaviour) error } // DistributionKeeper defines the expected interface of the distribution keeper From 2c796cd71428358e5df0b2d9c87e0674405f3512 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Wed, 21 Jun 2023 14:30:21 +0200 Subject: [PATCH 18/45] update e2e tests' --- tests/integration/misbehaviour.go | 83 ++++++++----------------------- 1 file changed, 22 insertions(+), 61 deletions(-) diff --git a/tests/integration/misbehaviour.go b/tests/integration/misbehaviour.go index 8ee0a71cde..39242c68d2 100644 --- a/tests/integration/misbehaviour.go +++ b/tests/integration/misbehaviour.go @@ -1,7 +1,6 @@ package integration import ( - "fmt" "time" sdk "github.com/cosmos/cosmos-sdk/types" @@ -11,12 +10,6 @@ import ( tmtypes "github.com/tendermint/tendermint/types" ) -const ( - trustingPeriod time.Duration = time.Hour * 24 * 7 * 2 - ubdPeriod time.Duration = time.Hour * 24 * 7 * 3 - maxClockDrift time.Duration = time.Second * 10 -) - func (s *CCVTestSuite) TestHandleConsumerMisbehaviour() { s.SetupCCVChannel(s.path) // required to have the consumer client revision height greater than 0 @@ -72,18 +65,9 @@ func (s *CCVTestSuite) TestHandleConsumerMisbehaviour() { s.Require().True(val.Jailed) s.Require().True(s.providerApp.GetTestSlashingKeeper().IsTombstoned(s.providerCtx(), provAddr.Address)) } - } func (s *CCVTestSuite) TestConstructLigthClientEvidence() { - - // test cases - // misbehaviour nil - // misbheaviour header 1 nil - // misbehaviour header 2 nil - - // misbehaviour no common height - s.SetupCCVChannel(s.path) // required to have the consumer client revision height greater than 0 s.SendEmptyVSCPacket() @@ -94,38 +78,11 @@ func (s *CCVTestSuite) TestConstructLigthClientEvidence() { clientTMValset := tmtypes.NewValidatorSet(s.consumerChain.Vals.Validators) clientSigners := s.consumerChain.Signers - altValset := tmtypes.NewValidatorSet(s.consumerChain.Vals.Validators[0:2]) + altValset := tmtypes.NewValidatorSet(s.consumerChain.Vals.Validators[0:3]) altSigners := make(map[string]tmtypes.PrivValidator, 1) altSigners[clientTMValset.Validators[0].Address.String()] = clientSigners[clientTMValset.Validators[0].Address.String()] altSigners[clientTMValset.Validators[1].Address.String()] = clientSigners[clientTMValset.Validators[1].Address.String()] - - misb := &ibctmtypes.Misbehaviour{ - ClientId: s.path.EndpointA.ClientID, - Header1: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight+1), - clientHeight, - altTime, - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - Header2: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight+1), - clientHeight, - altTime, - altValset, - altValset, - clientTMValset, - altSigners, - ), - } - - _ = misb - - // emptyHeader := &ibctmtypes.Header{} + altSigners[clientTMValset.Validators[2].Address.String()] = clientSigners[clientTMValset.Validators[2].Address.String()] testCases := []struct { name string @@ -145,9 +102,11 @@ func (s *CCVTestSuite) TestConstructLigthClientEvidence() { altValset, clientTMValset, altSigners, - )}, + ), + }, false, - }, { + }, + { "invalid misbehaviour - Header2 is empty", &ibctmtypes.Misbehaviour{ Header1: s.consumerChain.CreateTMClientHeader( @@ -163,7 +122,8 @@ func (s *CCVTestSuite) TestConstructLigthClientEvidence() { Header2: &ibctmtypes.Header{}, }, false, - }, { + }, + { "invalid misbehaviour - ClientId is empty", &ibctmtypes.Misbehaviour{ ClientId: "unknown-client-id", @@ -216,7 +176,8 @@ func (s *CCVTestSuite) TestConstructLigthClientEvidence() { ), }, true, - }, { + }, + { "light client attack - equivocation", &ibctmtypes.Misbehaviour{ ClientId: s.path.EndpointA.ClientID, @@ -234,7 +195,7 @@ func (s *CCVTestSuite) TestConstructLigthClientEvidence() { s.consumerChain.ChainID, int64(clientHeight.RevisionHeight+1), clientHeight, - altTime, + altTime.Add(time.Minute), clientTMValset, clientTMValset, clientTMValset, @@ -245,8 +206,6 @@ func (s *CCVTestSuite) TestConstructLigthClientEvidence() { }, } - // check how it's tested on CometBFTNewExtensionOptionsDecorator - for _, tc := range testCases { s.Run(tc.name, func() { ev, err := s.providerApp.GetProviderKeeper().ConstructLigthClientEvidence( @@ -255,21 +214,23 @@ func (s *CCVTestSuite) TestConstructLigthClientEvidence() { ) if tc.expPass { s.NoError(err) - s.Require().Equal(len(altValset.Validators), len(ev.ByzantineValidators)) - fmt.Println("headers 2 validators") + // For both lunatic and equivocation attack all the validators + // who signed the bad header (Header2) should be in returned in the evidence + h2Valset := tc.misbehaviour.Header2.ValidatorSet + + s.Equal(len(h2Valset.Validators), len(ev.ByzantineValidators)) + + vs, err := tmtypes.ValidatorSetFromProto(tc.misbehaviour.Header2.ValidatorSet) + s.NoError(err) - for _, v := range tc.misbehaviour.Header2.ValidatorSet.Validators { - fmt.Println(v.String()) - } - fmt.Println("byzantine validators") for _, v := range ev.ByzantineValidators { - fmt.Println(v.String()) + idx, _ := vs.GetByAddress(v.Address) + s.True(idx >= 0) } - // TODO: check that the byzantine validators == altValset.Validators + } else { s.Error(err) } }) } - } From 7310e4217a3f17f5bba9a8d46b5a0642935eb57b Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Mon, 26 Jun 2023 10:29:18 +0200 Subject: [PATCH 19/45] fix typo and improve docs --- .../ccv/provider/v1/tx.proto | 3 +- tests/integration/misbehaviour.go | 6 +- testutil/integration/debug_test.go | 4 +- x/ccv/provider/keeper/double_vote.go | 39 ---------- x/ccv/provider/keeper/misbehaviour.go | 10 +-- x/ccv/provider/types/codec.go | 5 ++ x/ccv/provider/types/tx.pb.go | 77 +++++++++---------- 7 files changed, 54 insertions(+), 90 deletions(-) delete mode 100644 x/ccv/provider/keeper/double_vote.go diff --git a/proto/interchain_security/ccv/provider/v1/tx.proto b/proto/interchain_security/ccv/provider/v1/tx.proto index 1bcfad7cbc..61be3064ea 100644 --- a/proto/interchain_security/ccv/provider/v1/tx.proto +++ b/proto/interchain_security/ccv/provider/v1/tx.proto @@ -8,7 +8,6 @@ import "gogoproto/gogo.proto"; import "cosmos_proto/cosmos.proto"; import "google/protobuf/any.proto"; import "ibc/lightclients/tendermint/v1/tendermint.proto"; -import "tendermint/types/evidence.proto"; // Msg defines the Msg service. service Msg { @@ -46,10 +45,10 @@ message MsgRegisterConsumerRewardDenom { // MsgRegisterConsumerRewardDenomResponse defines the Msg/RegisterConsumerRewardDenom response type. message MsgRegisterConsumerRewardDenomResponse {} -// MsgSubmitConsumerMisbehaviour reports a consumer chain Misbehaviour // MsgSubmitConsumerMisbehaviour defines a message that reports a misbehaviour // observed on a consumer chain +// Note that the misbheaviour' headers must contain the same trusted states message MsgSubmitConsumerMisbehaviour { option (gogoproto.equal) = false; option (gogoproto.goproto_getters) = false; diff --git a/tests/integration/misbehaviour.go b/tests/integration/misbehaviour.go index 39242c68d2..13ad7e6230 100644 --- a/tests/integration/misbehaviour.go +++ b/tests/integration/misbehaviour.go @@ -59,7 +59,7 @@ func (s *CCVTestSuite) TestHandleConsumerMisbehaviour() { for _, v := range altValset.Validators { consuAddr := sdk.ConsAddress(v.Address.Bytes()) - provAddr := s.providerApp.GetProviderKeeper().GetProviderAddrFromConsumerAddr(s.providerCtx(), s.consumerChain.ChainID, types.ConsumerConsAddress{consuAddr}) + provAddr := s.providerApp.GetProviderKeeper().GetProviderAddrFromConsumerAddr(s.providerCtx(), s.consumerChain.ChainID, types.NewConsumerConsAddress(consuAddr)) val, ok := s.providerApp.GetTestStakingKeeper().GetValidatorByConsAddr(s.providerCtx(), provAddr.Address) s.Require().True(ok) s.Require().True(val.Jailed) @@ -67,7 +67,7 @@ func (s *CCVTestSuite) TestHandleConsumerMisbehaviour() { } } -func (s *CCVTestSuite) TestConstructLigthClientEvidence() { +func (s *CCVTestSuite) TestConstructLightClientEvidence() { s.SetupCCVChannel(s.path) // required to have the consumer client revision height greater than 0 s.SendEmptyVSCPacket() @@ -208,7 +208,7 @@ func (s *CCVTestSuite) TestConstructLigthClientEvidence() { for _, tc := range testCases { s.Run(tc.name, func() { - ev, err := s.providerApp.GetProviderKeeper().ConstructLigthClientEvidence( + ev, err := s.providerApp.GetProviderKeeper().ConstructLightClientEvidence( s.providerCtx(), *tc.misbehaviour, ) diff --git a/testutil/integration/debug_test.go b/testutil/integration/debug_test.go index da2d75355c..3bafef8295 100644 --- a/testutil/integration/debug_test.go +++ b/testutil/integration/debug_test.go @@ -265,6 +265,6 @@ func TestHandleConsumerMisbehaviour(t *testing.T) { runCCVTestByName(t, "TestHandleConsumerMisbehaviour") } -func TestConstructLigthClientEvidence(t *testing.T) { - runCCVTestByName(t, "TestConstructLigthClientEvidence") +func TestConstructLightClientEvidence(t *testing.T) { + runCCVTestByName(t, "TestConstructLightClientEvidence") } diff --git a/x/ccv/provider/keeper/double_vote.go b/x/ccv/provider/keeper/double_vote.go deleted file mode 100644 index fc7875009f..0000000000 --- a/x/ccv/provider/keeper/double_vote.go +++ /dev/null @@ -1,39 +0,0 @@ -package keeper - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" - ibctmtypes "github.com/cosmos/ibc-go/v4/modules/light-clients/07-tendermint/types" - tmev "github.com/tendermint/tendermint/evidence" - tmproto "github.com/tendermint/tendermint/proto/tendermint/types" - tmtypes "github.com/tendermint/tendermint/types" -) - -func (k Keeper) HandleConsumerDoubleVoting(ctx sdk.Context, evidence *tmproto.DuplicateVoteEvidence, header *ibctmtypes.Header) error { - - // TODO: check header against consumer chain client - - ev, err := tmtypes.DuplicateVoteEvidenceFromProto(evidence) - if err != nil { - return err - } - valset, err := tmtypes.ValidatorSetFromProto(header.TrustedValidators) - if err != nil { - return err - } - - // TODO: figure out if the evidence age must also be checked - if err := tmev.VerifyDuplicateVote(ev, header.Header.ChainID, valset); err != nil { - return err - } - - // TODO: convert misbehaving validator consumer pubkey - // TODO: call k.evidenceKeeper.HandleEquivocationEvidence() using correct infraction height - - logger := ctx.Logger() - logger.Info( - "confirmed equivocation", - "byzantine validator", sdk.ConsAddress(evidence.VoteA.GetValidatorAddress()), - ) - - return nil -} diff --git a/x/ccv/provider/keeper/misbehaviour.go b/x/ccv/provider/keeper/misbehaviour.go index 8df61c4bb5..f616e8fea8 100644 --- a/x/ccv/provider/keeper/misbehaviour.go +++ b/x/ccv/provider/keeper/misbehaviour.go @@ -13,7 +13,7 @@ import ( ) // HandleConsumerMisbehaviour checks whether the given IBC misbehaviour is valid and, if they are, the misbehaving -// CheckConsumerMisbehaviour check that the given IBC misbehaviour headers forms a valid light client attck evidence. +// CheckConsumerMisbehaviour check that the given IBC misbehaviour headers forms a valid light client attack evidence. // proceed to the jailing and tombstoning of the bzyantine validators. func (k Keeper) HandleConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) error { logger := ctx.Logger() @@ -27,7 +27,7 @@ func (k Keeper) HandleConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmty // isn't too old. see ibc-go/modules/light-clients/07-tendermint/types/misbehaviour_handle.go // construct a ligth client attack evidence - evidence, err := k.ConstructLigthClientEvidence(ctx, misbehaviour) + evidence, err := k.ConstructLightClientEvidence(ctx, misbehaviour) if err != nil { return err } @@ -36,7 +36,7 @@ func (k Keeper) HandleConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmty for _, v := range evidence.ByzantineValidators { // convert consumer consensus address consuAddr := sdk.ConsAddress(v.Address.Bytes()) - provAddr := k.GetProviderAddrFromConsumerAddr(ctx, misbehaviour.Header1.Header.ChainID, types.ConsumerConsAddress{consuAddr}) + provAddr := k.GetProviderAddrFromConsumerAddr(ctx, misbehaviour.Header1.Header.ChainID, types.NewConsumerConsAddress(consuAddr)) k.stakingKeeper.ValidatorByConsAddr(ctx, consuAddr) val, ok := k.stakingKeeper.GetValidatorByConsAddr(ctx, provAddr.Address) @@ -67,9 +67,9 @@ func (k Keeper) HandleConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmty return nil } -// ConstructLigthClientEvidence constructs and returns a CometBFT Ligth Client Attack(LCA) evidence struct +// ConstructLightClientEvidence constructs and returns a CometBFT Ligth Client Attack(LCA) evidence struct // from the given misbehaviour -func (k Keeper) ConstructLigthClientEvidence(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) (*tmtypes.LightClientAttackEvidence, error) { +func (k Keeper) ConstructLightClientEvidence(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) (*tmtypes.LightClientAttackEvidence, error) { // construct the trusted and conflicetd ligth blocks trusted, err := headerToLightBlock(*misbehaviour.Header1) diff --git a/x/ccv/provider/types/codec.go b/x/ccv/provider/types/codec.go index 3e4b34dd42..0ef3c2d296 100644 --- a/x/ccv/provider/types/codec.go +++ b/x/ccv/provider/types/codec.go @@ -36,6 +36,11 @@ func RegisterInterfaces(registry codectypes.InterfaceRegistry) { &EquivocationProposal{}, ) + registry.RegisterImplementations( + (*sdk.Msg)(nil), + &MsgSubmitConsumerMisbehaviour{}, + ) + msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) } diff --git a/x/ccv/provider/types/tx.pb.go b/x/ccv/provider/types/tx.pb.go index 27757abd0c..f27ab52533 100644 --- a/x/ccv/provider/types/tx.pb.go +++ b/x/ccv/provider/types/tx.pb.go @@ -12,7 +12,6 @@ import ( grpc1 "github.com/gogo/protobuf/grpc" proto "github.com/gogo/protobuf/proto" _ "github.com/regen-network/cosmos-proto" - _ "github.com/tendermint/tendermint/proto/tendermint/types" _ "google.golang.org/genproto/googleapis/api/annotations" grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" @@ -195,6 +194,7 @@ var xxx_messageInfo_MsgRegisterConsumerRewardDenomResponse proto.InternalMessage // MsgSubmitConsumerMisbehaviour defines a message that reports a misbehaviour // observed on a consumer chain +// Note that the misbheaviour' headers must contain the same trusted states type MsgSubmitConsumerMisbehaviour struct { Submitter string `protobuf:"bytes,1,opt,name=submitter,proto3" json:"submitter,omitempty"` // The Misbehaviour of the consumer chain wrapping @@ -285,44 +285,43 @@ func init() { } var fileDescriptor_43221a4391e9fbf4 = []byte{ - // 578 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x54, 0x3f, 0x6f, 0xd3, 0x4e, - 0x18, 0x8e, 0x7f, 0xd5, 0x0f, 0xda, 0x6b, 0x41, 0xc2, 0xea, 0xd0, 0x9a, 0xe0, 0x40, 0x10, 0xd0, - 0xa1, 0xf8, 0x94, 0x30, 0x20, 0x2a, 0x31, 0x24, 0x65, 0x81, 0x2a, 0x12, 0x32, 0x03, 0x12, 0x03, - 0x91, 0x7d, 0x7e, 0xb9, 0x9c, 0x88, 0xef, 0xac, 0xbb, 0xb3, 0xa9, 0xbf, 0x01, 0x23, 0x4c, 0x88, - 0xad, 0x1f, 0x00, 0x89, 0xaf, 0xc1, 0xd8, 0x91, 0x09, 0xa1, 0x64, 0x61, 0x66, 0x64, 0x42, 0xfe, - 0x97, 0xb8, 0x22, 0x44, 0x11, 0xb0, 0xbd, 0x7f, 0x1e, 0x3f, 0xcf, 0xf3, 0xea, 0x3d, 0xbf, 0x68, - 0x9f, 0x71, 0x0d, 0x92, 0x8c, 0x3c, 0xc6, 0x87, 0x0a, 0x48, 0x2c, 0x99, 0x4e, 0x31, 0x21, 0x09, - 0x8e, 0xa4, 0x48, 0x58, 0x00, 0x12, 0x27, 0x1d, 0xac, 0x8f, 0x9d, 0x48, 0x0a, 0x2d, 0xcc, 0xeb, - 0x0b, 0xd0, 0x0e, 0x21, 0x89, 0x53, 0xa1, 0x9d, 0xa4, 0x63, 0x35, 0xa9, 0x10, 0x74, 0x0c, 0xd8, - 0x8b, 0x18, 0xf6, 0x38, 0x17, 0xda, 0xd3, 0x4c, 0x70, 0x55, 0x50, 0x58, 0xdb, 0x54, 0x50, 0x91, - 0x87, 0x38, 0x8b, 0xca, 0xea, 0x2e, 0x11, 0x2a, 0x14, 0x6a, 0x58, 0x34, 0x8a, 0xa4, 0x6a, 0x95, - 0x74, 0x79, 0xe6, 0xc7, 0x2f, 0xb0, 0xc7, 0xd3, 0xb2, 0x85, 0x99, 0x4f, 0xf0, 0x98, 0xd1, 0x91, - 0x26, 0x63, 0x06, 0x5c, 0x2b, 0xac, 0x81, 0x07, 0x20, 0x43, 0xc6, 0x75, 0xee, 0x7b, 0x96, 0x95, - 0x1f, 0xb4, 0x6a, 0x7d, 0x9d, 0x46, 0xa0, 0x30, 0x64, 0xb6, 0x39, 0x81, 0x02, 0xd0, 0x7e, 0x67, - 0xa0, 0xed, 0x81, 0xa2, 0x3d, 0xa5, 0x18, 0xe5, 0x87, 0x82, 0xab, 0x38, 0x04, 0x79, 0x04, 0xa9, - 0xb9, 0x8b, 0xd6, 0x8b, 0xb1, 0x59, 0xb0, 0x63, 0x5c, 0x35, 0xf6, 0x36, 0xdc, 0xf3, 0x79, 0xfe, - 0x30, 0x30, 0xef, 0xa2, 0x0b, 0xd5, 0xf8, 0x43, 0x2f, 0x08, 0xe4, 0xce, 0x7f, 0x59, 0xbf, 0x6f, - 0x7e, 0xff, 0xd2, 0xba, 0x98, 0x7a, 0xe1, 0xf8, 0xa0, 0x9d, 0x55, 0x41, 0xa9, 0xb6, 0xbb, 0x55, - 0x01, 0x7b, 0x41, 0x20, 0xcd, 0x6b, 0x68, 0x8b, 0x94, 0x12, 0xc3, 0x97, 0x90, 0xee, 0xac, 0xe5, - 0xbc, 0x9b, 0x64, 0x2e, 0x7b, 0xb0, 0xfe, 0xfa, 0xa4, 0xd5, 0xf8, 0x76, 0xd2, 0x6a, 0xb4, 0x6d, - 0xd4, 0x5c, 0x64, 0xcc, 0x05, 0x15, 0x09, 0xae, 0xa0, 0xfd, 0x1c, 0xd9, 0x03, 0x45, 0x5d, 0xa0, - 0x4c, 0x69, 0x90, 0x15, 0xc2, 0x85, 0x57, 0x9e, 0x0c, 0x1e, 0x00, 0x17, 0xa1, 0xb9, 0x8d, 0xfe, - 0x0f, 0xb2, 0xa0, 0xf4, 0x5f, 0x24, 0x66, 0x13, 0x6d, 0x04, 0x10, 0x09, 0xc5, 0xb4, 0x28, 0x9d, - 0xbb, 0xf3, 0x42, 0x4d, 0x7f, 0x0f, 0xdd, 0x5c, 0xce, 0x3f, 0x73, 0xf2, 0xde, 0x40, 0x57, 0x06, - 0x8a, 0x3e, 0x89, 0xfd, 0x90, 0xe9, 0x0a, 0x38, 0x60, 0xca, 0x87, 0x91, 0x97, 0x30, 0x11, 0xcb, - 0x4c, 0x53, 0xe5, 0x5d, 0x0d, 0xb2, 0x74, 0x33, 0x2f, 0x98, 0x8f, 0xd1, 0x56, 0x58, 0x43, 0xe7, - 0xa6, 0x36, 0xbb, 0xfb, 0x0e, 0xf3, 0x89, 0x53, 0x5f, 0xb6, 0x53, 0x5b, 0x6f, 0xd2, 0x71, 0xea, - 0x0a, 0xee, 0x19, 0x86, 0xda, 0x14, 0xb7, 0xd0, 0x8d, 0xa5, 0xd6, 0xaa, 0x21, 0xba, 0x3f, 0xd6, - 0xd0, 0xda, 0x40, 0x51, 0xf3, 0xad, 0x81, 0x2e, 0xfd, 0xfa, 0x1a, 0xee, 0x39, 0x2b, 0xfc, 0x08, - 0xce, 0xa2, 0x7d, 0x59, 0xbd, 0x3f, 0xfe, 0xb4, 0xf2, 0x66, 0x7e, 0x34, 0xd0, 0xe5, 0x65, 0x8b, - 0x3e, 0x5c, 0x55, 0x62, 0x09, 0x89, 0x75, 0xf4, 0x0f, 0x48, 0x66, 0x8e, 0x3f, 0x18, 0xc8, 0x5a, - 0xf2, 0x1e, 0xfa, 0xab, 0x6a, 0xfd, 0x9e, 0xc3, 0x7a, 0xf4, 0xf7, 0x1c, 0x95, 0xdd, 0xfe, 0xd3, - 0x4f, 0x13, 0xdb, 0x38, 0x9d, 0xd8, 0xc6, 0xd7, 0x89, 0x6d, 0xbc, 0x99, 0xda, 0x8d, 0xd3, 0xa9, - 0xdd, 0xf8, 0x3c, 0xb5, 0x1b, 0xcf, 0xee, 0x53, 0xa6, 0x47, 0xb1, 0xef, 0x10, 0x11, 0x96, 0x57, - 0x0a, 0xcf, 0x65, 0x6f, 0xcf, 0x0e, 0x68, 0xd2, 0xc5, 0xc7, 0x67, 0xaf, 0x68, 0x7e, 0x6d, 0xfc, - 0x73, 0xf9, 0x95, 0xb9, 0xf3, 0x33, 0x00, 0x00, 0xff, 0xff, 0x7c, 0xb5, 0x5a, 0x59, 0x76, 0x05, - 0x00, 0x00, + // 568 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x54, 0x3f, 0x6f, 0x13, 0x4f, + 0x10, 0xf5, 0xfd, 0xa2, 0x1f, 0x24, 0x9b, 0x80, 0xc4, 0xc9, 0x85, 0x73, 0x98, 0x33, 0x18, 0x01, + 0x29, 0xc2, 0xae, 0x6c, 0x0a, 0x44, 0x24, 0x0a, 0x3b, 0x34, 0x10, 0x59, 0x42, 0x47, 0x81, 0x44, + 0x81, 0x75, 0xb7, 0xbb, 0xac, 0x57, 0xf8, 0x76, 0x4f, 0xbb, 0x7b, 0x47, 0xee, 0x1b, 0x50, 0x42, + 0x85, 0xe8, 0xf2, 0x01, 0x90, 0xf8, 0x1a, 0x94, 0x29, 0xa9, 0x10, 0xb2, 0x1b, 0x6a, 0x4a, 0x2a, + 0xe4, 0xfb, 0x63, 0x5f, 0x84, 0xb1, 0x2c, 0xa0, 0xdb, 0x99, 0x79, 0xfb, 0xde, 0x1b, 0xcd, 0x68, + 0xc0, 0x3e, 0x17, 0x86, 0x2a, 0x3c, 0xf2, 0xb9, 0x18, 0x6a, 0x8a, 0x63, 0xc5, 0x4d, 0x8a, 0x30, + 0x4e, 0x50, 0xa4, 0x64, 0xc2, 0x09, 0x55, 0x28, 0xe9, 0x20, 0x73, 0x0c, 0x23, 0x25, 0x8d, 0xb4, + 0xaf, 0x2f, 0x41, 0x43, 0x8c, 0x13, 0x58, 0xa2, 0x61, 0xd2, 0x71, 0x9a, 0x4c, 0x4a, 0x36, 0xa6, + 0xc8, 0x8f, 0x38, 0xf2, 0x85, 0x90, 0xc6, 0x37, 0x5c, 0x0a, 0x9d, 0x53, 0x38, 0x75, 0x26, 0x99, + 0xcc, 0x9e, 0x68, 0xf6, 0x2a, 0xb2, 0xbb, 0x58, 0xea, 0x50, 0xea, 0x61, 0x5e, 0xc8, 0x83, 0xb2, + 0x54, 0xd0, 0x65, 0x51, 0x10, 0xbf, 0x40, 0xbe, 0x48, 0x8b, 0x12, 0xe2, 0x01, 0x46, 0x63, 0xce, + 0x46, 0x06, 0x8f, 0x39, 0x15, 0x46, 0x23, 0x43, 0x05, 0xa1, 0x2a, 0xe4, 0xc2, 0x64, 0xbe, 0xe7, + 0x51, 0xfe, 0xa1, 0xfd, 0xce, 0x02, 0xf5, 0x81, 0x66, 0x3d, 0xad, 0x39, 0x13, 0x87, 0x52, 0xe8, + 0x38, 0xa4, 0xea, 0x88, 0xa6, 0xf6, 0x2e, 0xd8, 0xcc, 0xbb, 0xe2, 0xa4, 0x61, 0x5d, 0xb5, 0xf6, + 0xb6, 0xbc, 0xf3, 0x59, 0xfc, 0x90, 0xd8, 0x77, 0xc1, 0x85, 0xb2, 0xbb, 0xa1, 0x4f, 0x88, 0x6a, + 0xfc, 0x37, 0xab, 0xf7, 0xed, 0xef, 0x5f, 0x5a, 0x17, 0x53, 0x3f, 0x1c, 0x1f, 0xb4, 0x67, 0x59, + 0xaa, 0x75, 0xdb, 0xdb, 0x29, 0x81, 0x3d, 0x42, 0x94, 0x7d, 0x0d, 0xec, 0xe0, 0x42, 0x62, 0xf8, + 0x92, 0xa6, 0x8d, 0x8d, 0x8c, 0x77, 0x1b, 0x2f, 0x64, 0x0f, 0x36, 0x5f, 0x9f, 0xb4, 0x6a, 0xdf, + 0x4e, 0x5a, 0xb5, 0xb6, 0x0b, 0x9a, 0xcb, 0x8c, 0x79, 0x54, 0x47, 0x52, 0x68, 0xda, 0x7e, 0x0e, + 0xdc, 0x81, 0x66, 0x1e, 0x65, 0x5c, 0x1b, 0xaa, 0x4a, 0x84, 0x47, 0x5f, 0xf9, 0x8a, 0x3c, 0xa0, + 0x42, 0x86, 0x76, 0x1d, 0xfc, 0x4f, 0x66, 0x8f, 0xc2, 0x7f, 0x1e, 0xd8, 0x4d, 0xb0, 0x45, 0x68, + 0x24, 0x35, 0x37, 0xb2, 0x70, 0xee, 0x2d, 0x12, 0x15, 0xfd, 0x3d, 0x70, 0x73, 0x35, 0xff, 0xdc, + 0xc9, 0x7b, 0x0b, 0x5c, 0x19, 0x68, 0xf6, 0x24, 0x0e, 0x42, 0x6e, 0x4a, 0xe0, 0x80, 0xeb, 0x80, + 0x8e, 0xfc, 0x84, 0xcb, 0x58, 0xcd, 0x34, 0x75, 0x56, 0x35, 0x54, 0x15, 0x6e, 0x16, 0x09, 0xfb, + 0x31, 0xd8, 0x09, 0x2b, 0xe8, 0xcc, 0xd4, 0x76, 0x77, 0x1f, 0xf2, 0x00, 0xc3, 0xea, 0x2c, 0x61, + 0x65, 0x7a, 0x49, 0x07, 0x56, 0x15, 0xbc, 0x33, 0x0c, 0x95, 0x2e, 0x6e, 0x81, 0x1b, 0x2b, 0xad, + 0x95, 0x4d, 0x74, 0x7f, 0x6c, 0x80, 0x8d, 0x81, 0x66, 0xf6, 0x5b, 0x0b, 0x5c, 0xfa, 0x75, 0x1b, + 0xee, 0xc1, 0x35, 0xf6, 0x1c, 0x2e, 0x9b, 0x97, 0xd3, 0xfb, 0xe3, 0xaf, 0xa5, 0x37, 0xfb, 0xa3, + 0x05, 0x2e, 0xaf, 0x1a, 0xf4, 0xe1, 0xba, 0x12, 0x2b, 0x48, 0x9c, 0xa3, 0x7f, 0x40, 0x32, 0x77, + 0xfc, 0xc1, 0x02, 0xce, 0x8a, 0x7d, 0xe8, 0xaf, 0xab, 0xf5, 0x7b, 0x0e, 0xe7, 0xd1, 0xdf, 0x73, + 0x94, 0x76, 0xfb, 0x4f, 0x3f, 0x4d, 0x5c, 0xeb, 0x74, 0xe2, 0x5a, 0x5f, 0x27, 0xae, 0xf5, 0x66, + 0xea, 0xd6, 0x4e, 0xa7, 0x6e, 0xed, 0xf3, 0xd4, 0xad, 0x3d, 0xbb, 0xcf, 0xb8, 0x19, 0xc5, 0x01, + 0xc4, 0x32, 0x2c, 0x8e, 0x10, 0x5a, 0xc8, 0xde, 0x9e, 0xdf, 0xc7, 0xa4, 0x8b, 0x8e, 0xcf, 0x1e, + 0x49, 0x93, 0x46, 0x54, 0x07, 0xe7, 0xb2, 0x2b, 0x73, 0xe7, 0x67, 0x00, 0x00, 0x00, 0xff, 0xff, + 0xe3, 0x4f, 0x57, 0x26, 0x55, 0x05, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. From b1f021c71827bca84f932f1549dab60d51dd8c89 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Wed, 19 Jul 2023 14:36:09 +0200 Subject: [PATCH 20/45] remove unwanted tm evidence protofile --- .../proto/tendermint/types/evidence.proto | 38 ------------------- 1 file changed, 38 deletions(-) delete mode 100644 third_party/proto/tendermint/types/evidence.proto diff --git a/third_party/proto/tendermint/types/evidence.proto b/third_party/proto/tendermint/types/evidence.proto deleted file mode 100644 index 451b8dca3c..0000000000 --- a/third_party/proto/tendermint/types/evidence.proto +++ /dev/null @@ -1,38 +0,0 @@ -syntax = "proto3"; -package tendermint.types; - -option go_package = "github.com/tendermint/tendermint/proto/tendermint/types"; - -import "gogoproto/gogo.proto"; -import "google/protobuf/timestamp.proto"; -import "tendermint/types/types.proto"; -import "tendermint/types/validator.proto"; - -message Evidence { - oneof sum { - DuplicateVoteEvidence duplicate_vote_evidence = 1; - LightClientAttackEvidence light_client_attack_evidence = 2; - } -} - -// DuplicateVoteEvidence contains evidence of a validator signed two conflicting votes. -message DuplicateVoteEvidence { - tendermint.types.Vote vote_a = 1; - tendermint.types.Vote vote_b = 2; - int64 total_voting_power = 3; - int64 validator_power = 4; - google.protobuf.Timestamp timestamp = 5 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; -} - -// LightClientAttackEvidence contains evidence of a set of validators attempting to mislead a light client. -message LightClientAttackEvidence { - tendermint.types.LightBlock conflicting_block = 1; - int64 common_height = 2; - repeated tendermint.types.Validator byzantine_validators = 3; - int64 total_voting_power = 4; - google.protobuf.Timestamp timestamp = 5 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; -} - -message EvidenceList { - repeated Evidence evidence = 1 [(gogoproto.nullable) = false]; -} From f4c7e73bd3faffa5fb93df406fa2b63f976deaf1 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Wed, 19 Jul 2023 14:41:19 +0200 Subject: [PATCH 21/45] fix typos --- x/ccv/provider/keeper/misbehaviour.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/x/ccv/provider/keeper/misbehaviour.go b/x/ccv/provider/keeper/misbehaviour.go index f616e8fea8..3fc0c59a19 100644 --- a/x/ccv/provider/keeper/misbehaviour.go +++ b/x/ccv/provider/keeper/misbehaviour.go @@ -26,7 +26,7 @@ func (k Keeper) HandleConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmty // w.r.t to the last trusted consensus it entails that the infraction age // isn't too old. see ibc-go/modules/light-clients/07-tendermint/types/misbehaviour_handle.go - // construct a ligth client attack evidence + // construct a light client attack evidence evidence, err := k.ConstructLightClientEvidence(ctx, misbehaviour) if err != nil { return err @@ -67,11 +67,11 @@ func (k Keeper) HandleConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmty return nil } -// ConstructLightClientEvidence constructs and returns a CometBFT Ligth Client Attack(LCA) evidence struct +// ConstructLightClientEvidence constructs and returns a CometBFT Light Client Attack(LCA) evidence struct // from the given misbehaviour func (k Keeper) ConstructLightClientEvidence(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) (*tmtypes.LightClientAttackEvidence, error) { - // construct the trusted and conflicetd ligth blocks + // construct the trusted and conflicted light blocks trusted, err := headerToLightBlock(*misbehaviour.Header1) if err != nil { return nil, err @@ -111,7 +111,6 @@ func (k Keeper) ConstructLightClientEvidence(ctx sdk.Context, misbehaviour ibctm // GetCommonFromMisbehaviour checks whether the given ibc misbehaviour's headers share common trusted height // and that a consensus state exists for this height. In this case, it returns the associated trusted height, timestamp and valset. func (k Keeper) GetTrustedInfoFromMisbehaviour(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) (int64, time.Time, *tmtypes.ValidatorSet, error) { - // a common trusted height is required commonHeight := misbehaviour.Header1.TrustedHeight if !commonHeight.EQ(misbehaviour.Header2.TrustedHeight) { @@ -131,7 +130,7 @@ func (k Keeper) GetTrustedInfoFromMisbehaviour(ctx sdk.Context, misbehaviour ibc return int64(commonHeight.RevisionHeight), time.Unix(0, int64(cs.GetTimestamp())), vs, nil } -// headerToLightBlock returns a CometBFT ligth block from the given IBC header +// headerToLightBlock returns a CometBFT light block from the given IBC header func headerToLightBlock(h ibctmtypes.Header) (*tmtypes.LightBlock, error) { sh, err := tmtypes.SignedHeaderFromProto(h.SignedHeader) if err != nil { From b60499ca9c04defdc6867ec7b69c15b89740afd4 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Fri, 21 Jul 2023 10:49:02 +0200 Subject: [PATCH 22/45] update submit-consumer-misbehaviour cli description --- x/ccv/provider/client/cli/tx.go | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/x/ccv/provider/client/cli/tx.go b/x/ccv/provider/client/cli/tx.go index 87b4179b0b..47299ba1d1 100644 --- a/x/ccv/provider/client/cli/tx.go +++ b/x/ccv/provider/client/cli/tx.go @@ -104,9 +104,17 @@ $ %s tx provider register-consumer-reward-denom untrn --from mykey func NewSubmitConsumerMisbehaviourCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "submit-consumer-misbehaviour [misbeaviour]", - Short: "submit a light client misbehaviour for a consumer chain", - Args: cobra.ExactArgs(1), + Use: "submit-consumer-misbehaviour [misbehaviour]", + Short: "submit a IBC misbehaviour for a consumer chain", + Long: strings.TrimSpace( + fmt.Sprintf(`Submit a IBC misbehaviour detected on a consumer chain. +A IBC misbehaviour contains two conflicting IBC client headers, which are used to form a light client attack evidence. +The misbehaviour type definition can be found in the IBC client messages, see ibc-go/proto/ibc/core/client/v1/tx.proto`. + +Examples: +%s tx provider submit-consumer-misbehaviour [path/to/misbehaviour.json] --from node0 --home ../node0 --chain-id $CID + `, version.AppName)), + Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { clientCtx, err := client.GetClientTxContext(cmd) if err != nil { From b00337aef5d3e121b762ce09439e9c7e5e26cf79 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Fri, 21 Jul 2023 11:52:55 +0200 Subject: [PATCH 23/45] check that header1 and header2 have the same TrustedValidators --- x/ccv/provider/client/cli/tx.go | 2 +- x/ccv/provider/keeper/misbehaviour.go | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/x/ccv/provider/client/cli/tx.go b/x/ccv/provider/client/cli/tx.go index 47299ba1d1..491e1e618f 100644 --- a/x/ccv/provider/client/cli/tx.go +++ b/x/ccv/provider/client/cli/tx.go @@ -109,7 +109,7 @@ func NewSubmitConsumerMisbehaviourCmd() *cobra.Command { Long: strings.TrimSpace( fmt.Sprintf(`Submit a IBC misbehaviour detected on a consumer chain. A IBC misbehaviour contains two conflicting IBC client headers, which are used to form a light client attack evidence. -The misbehaviour type definition can be found in the IBC client messages, see ibc-go/proto/ibc/core/client/v1/tx.proto`. +The misbehaviour type definition can be found in the IBC client messages, see ibc-go/proto/ibc/core/client/v1/tx.proto. Examples: %s tx provider submit-consumer-misbehaviour [path/to/misbehaviour.json] --from node0 --home ../node0 --chain-id $CID diff --git a/x/ccv/provider/keeper/misbehaviour.go b/x/ccv/provider/keeper/misbehaviour.go index 3fc0c59a19..937df77ed0 100644 --- a/x/ccv/provider/keeper/misbehaviour.go +++ b/x/ccv/provider/keeper/misbehaviour.go @@ -2,6 +2,7 @@ package keeper import ( "fmt" + "reflect" "time" "github.com/cosmos/interchain-security/v2/x/ccv/provider/types" @@ -16,7 +17,7 @@ import ( // CheckConsumerMisbehaviour check that the given IBC misbehaviour headers forms a valid light client attack evidence. // proceed to the jailing and tombstoning of the bzyantine validators. func (k Keeper) HandleConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) error { - logger := ctx.Logger() + logger := k.Logger(ctx) if err := k.clientKeeper.CheckMisbehaviourAndUpdateState(ctx, &misbehaviour); err != nil { return err @@ -111,18 +112,23 @@ func (k Keeper) ConstructLightClientEvidence(ctx sdk.Context, misbehaviour ibctm // GetCommonFromMisbehaviour checks whether the given ibc misbehaviour's headers share common trusted height // and that a consensus state exists for this height. In this case, it returns the associated trusted height, timestamp and valset. func (k Keeper) GetTrustedInfoFromMisbehaviour(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) (int64, time.Time, *tmtypes.ValidatorSet, error) { - // a common trusted height is required + // a common trusted validator set and height is required commonHeight := misbehaviour.Header1.TrustedHeight if !commonHeight.EQ(misbehaviour.Header2.TrustedHeight) { return 0, time.Time{}, nil, fmt.Errorf("misbehaviour headers have different trusted height: %v , %v", commonHeight, misbehaviour.Header2.TrustedHeight) } - cs, ok := k.clientKeeper.GetClientConsensusState(ctx, misbehaviour.GetClientID(), misbehaviour.Header1.TrustedHeight) + commonValset := misbehaviour.Header1.TrustedValidators + if !reflect.DeepEqual(commonValset.Validators, misbehaviour.Header2.TrustedValidators.Validators) { + return 0, time.Time{}, nil, fmt.Errorf("misbehaviour headers have different trusted validator set: %v , %v", commonHeight, misbehaviour.Header2.TrustedHeight) + } + + cs, ok := k.clientKeeper.GetClientConsensusState(ctx, misbehaviour.GetClientID(), commonHeight) if !ok { return 0, time.Time{}, nil, fmt.Errorf("cannot find consensus state at trusted height %d for client %s", commonHeight, misbehaviour.GetClientID()) } - vs, err := tmtypes.ValidatorSetFromProto(misbehaviour.Header1.ValidatorSet) + vs, err := tmtypes.ValidatorSetFromProto(commonValset) if err != nil { return 0, time.Time{}, nil, err } From 457ce98e7a4e5d8db527b2ae254ae435f31752ea Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Mon, 31 Jul 2023 10:22:44 +0200 Subject: [PATCH 24/45] feat: add e2e tests for ICS misbehaviour (#1118) * remove unwanted changes * fix hermes config with assigned key * revert unwanted changes * revert local setup * remove log file * typo * update doc * update ICS misbehaviour test * update ICS misbehaviour test * revert mixed commits * add doc * lint --- Dockerfile | 2 +- go.mod | 3 +- go.sum | 14 +- tests/e2e/actions.go | 28 +- tests/e2e/actions_consumer_misbehaviour.go | 92 +++++++ tests/e2e/config.go | 84 ++++++ tests/e2e/main.go | 5 + tests/e2e/state.go | 39 +++ tests/e2e/steps.go | 7 + tests/e2e/steps_consumer_misbehaviour.go | 263 +++++++++++++++++++ tests/e2e/testnet-scripts/fork-consumer.sh | 112 ++++++++ tests/e2e/testnet-scripts/hermes-config.toml | 18 +- tests/e2e/testnet-scripts/start-chain.sh | 9 +- x/ccv/provider/keeper/misbehaviour.go | 6 +- 14 files changed, 659 insertions(+), 23 deletions(-) create mode 100644 tests/e2e/actions_consumer_misbehaviour.go create mode 100644 tests/e2e/steps_consumer_misbehaviour.go create mode 100644 tests/e2e/testnet-scripts/fork-consumer.sh diff --git a/Dockerfile b/Dockerfile index 4d81392316..03939617be 100644 --- a/Dockerfile +++ b/Dockerfile @@ -28,7 +28,7 @@ RUN go mod tidy RUN make install # Get Hermes build -FROM ghcr.io/informalsystems/hermes:1.4.1 AS hermes-builder +FROM otacrew/hermes-ics:latest AS hermes-builder # Get CometMock FROM informalofftermatt/cometmock:latest as cometmock-builder diff --git a/go.mod b/go.mod index 7ef3e43ffb..58b8eaa667 100644 --- a/go.mod +++ b/go.mod @@ -80,7 +80,6 @@ require ( github.com/go-kit/kit v0.12.0 // indirect github.com/go-kit/log v0.2.1 // indirect github.com/go-logfmt/logfmt v0.5.1 // indirect - github.com/gobwas/ws v1.1.0 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/gogo/gateway v1.1.0 // indirect github.com/golang/glog v1.0.0 // indirect @@ -116,7 +115,7 @@ require ( github.com/minio/highwayhash v1.0.2 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mtibben/percent v0.2.1 // indirect - github.com/onsi/ginkgo v1.16.5 // indirect + github.com/onsi/ginkgo v1.16.4 // indirect github.com/onsi/gomega v1.20.0 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.0.5 // indirect diff --git a/go.sum b/go.sum index 5afd840ee9..ed38bf1bab 100644 --- a/go.sum +++ b/go.sum @@ -382,15 +382,12 @@ github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= -github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= -github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= +github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8= github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= -github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= -github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo= github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= -github.com/gobwas/ws v1.1.0 h1:7RFti/xnNkMJnrK7D1yQ/iCIB5OrrY/54/H930kIbHA= -github.com/gobwas/ws v1.1.0/go.mod h1:nzvNcVha5eUziGrbxFCo6qFIojQHjJV5cLYIbezhfL0= github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= @@ -780,8 +777,8 @@ github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= @@ -1268,7 +1265,6 @@ golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/tests/e2e/actions.go b/tests/e2e/actions.go index 06bcb7f5f7..9c7d41af17 100644 --- a/tests/e2e/actions.go +++ b/tests/e2e/actions.go @@ -63,7 +63,7 @@ type StartChainAction struct { validators []StartChainValidator // Genesis changes specific to this action, appended to genesis changes defined in chain config genesisChanges string - skipGentx bool + isConsumer bool } type StartChainValidator struct { @@ -133,7 +133,7 @@ func (tr TestRun) startChain( cmd := exec.Command("docker", "exec", tr.containerConfig.instanceName, "/bin/bash", "/testnet-scripts/start-chain.sh", chainConfig.binaryName, string(vals), string(chainConfig.chainId), chainConfig.ipPrefix, genesisChanges, - fmt.Sprint(action.skipGentx), + 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 @@ -170,6 +170,7 @@ func (tr TestRun) startChain( tr.addChainToRelayer(addChainToRelayerAction{ chain: action.chain, validator: action.validators[0].id, + consumer: action.isConsumer, }, verbose) } @@ -280,6 +281,8 @@ func (tr TestRun) submitConsumerAdditionProposal( if err != nil { log.Fatal(err, "\n", string(bz)) } + + tr.waitBlocks(action.chain, 1, 5*time.Second) } type submitConsumerRemovalProposalAction struct { @@ -521,7 +524,7 @@ func (tr TestRun) voteGovProposal( } wg.Wait() - time.Sleep(time.Duration(tr.chainConfigs[action.chain].votingWaitTime) * time.Second) + time.Sleep((time.Duration(tr.chainConfigs[action.chain].votingWaitTime)) * time.Second) } type startConsumerChainAction struct { @@ -564,7 +567,7 @@ func (tr TestRun) startConsumerChain( chain: action.consumerChain, validators: action.validators, genesisChanges: consumerGenesis, - skipGentx: true, + isConsumer: true, }, verbose) } @@ -698,6 +701,7 @@ func (tr TestRun) startChangeover( type addChainToRelayerAction struct { chain chainID validator validatorID + consumer bool } const hermesChainConfigTemplate = ` @@ -715,6 +719,7 @@ rpc_timeout = "10s" store_prefix = "ibc" trusting_period = "14days" websocket_addr = "%s" +ccv_consumer_chain = %v [chains.gas_price] denom = "stake" @@ -813,7 +818,7 @@ func (tr TestRun) addChainToHermes( keyName, rpcAddr, wsAddr, - // action.consumer, + action.consumer, ) bashCommand := fmt.Sprintf(`echo '%s' >> %s`, chainConfig, "/root/.hermes/config.toml") @@ -827,7 +832,16 @@ func (tr TestRun) addChainToHermes( } // Save mnemonic to file within container - saveMnemonicCommand := fmt.Sprintf(`echo '%s' > %s`, tr.validatorConfigs[action.validator].mnemonic, "/root/.hermes/mnemonic.txt") + var mnemonic string + if tr.validatorConfigs[action.validator].useConsumerKey && action.consumer { + mnemonic = tr.validatorConfigs[action.validator].consumerMnemonic + } else { + mnemonic = tr.validatorConfigs[action.validator].mnemonic + } + + saveMnemonicCommand := fmt.Sprintf(`echo '%s' > %s`, mnemonic, "/root/.hermes/mnemonic.txt") + fmt.Println("Add to hermes", action.validator) + fmt.Println(mnemonic) //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. bz, err = exec.Command("docker", "exec", tr.containerConfig.instanceName, "bash", "-c", saveMnemonicCommand, @@ -1767,6 +1781,8 @@ func (tr TestRun) assignConsumerPubKey(action assignConsumerPubKeyAction, verbos valCfg.useConsumerKey = true tr.validatorConfigs[action.validator] = valCfg } + + time.Sleep(1 * time.Second) } // slashThrottleDequeue polls slash queue sizes until nextQueueSize is achieved diff --git a/tests/e2e/actions_consumer_misbehaviour.go b/tests/e2e/actions_consumer_misbehaviour.go new file mode 100644 index 0000000000..2b01c2818e --- /dev/null +++ b/tests/e2e/actions_consumer_misbehaviour.go @@ -0,0 +1,92 @@ +package main + +import ( + "bufio" + "fmt" + "log" + "os/exec" + "time" +) + +type forkConsumerChainAction struct { + consumerChain chainID + providerChain chainID + validator validatorID + relayerConfig string +} + +func (tr TestRun) forkConsumerChain(action forkConsumerChainAction, verbose bool) { + valCfg := tr.validatorConfigs[action.validator] + + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + configureNodeCmd := exec.Command("docker", "exec", tr.containerConfig.instanceName, "/bin/bash", + "/testnet-scripts/fork-consumer.sh", tr.chainConfigs[action.consumerChain].binaryName, + string(action.validator), string(action.consumerChain), + tr.chainConfigs[action.consumerChain].ipPrefix, + tr.chainConfigs[action.providerChain].ipPrefix, + valCfg.mnemonic, + action.relayerConfig, + ) + + if verbose { + fmt.Println("forkConsumerChain - 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("fork consumer validator : " + out) + } + if out == done { + break + } + } + if err := scanner.Err(); err != nil { + log.Fatal(err) + } + + time.Sleep(5 * time.Second) +} + +type updateLightClientAction struct { + hostChain chainID + relayerConfig string + clientID string +} + +func (tr TestRun) updateLightClient( + action updateLightClientAction, + verbose bool, +) { + // hermes clear packets ibc0 transfer channel-13 + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + cmd := exec.Command("docker", "exec", tr.containerConfig.instanceName, "hermes", + "--config", action.relayerConfig, + "update", + "client", + "--client", action.clientID, + "--host-chain", string(action.hostChain), + ) + if verbose { + log.Println("updateLightClientAction cmd:", cmd.String()) + } + + bz, err := cmd.CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + tr.waitBlocks(action.hostChain, 5, 30*time.Second) +} diff --git a/tests/e2e/config.go b/tests/e2e/config.go index 3038f69f4e..efed270ce7 100644 --- a/tests/e2e/config.go +++ b/tests/e2e/config.go @@ -376,6 +376,90 @@ func ChangeoverTestRun() TestRun { } } +func ConsumerMisbehaviourTestRun() TestRun { + return TestRun{ + name: "misbehaviour", + containerConfig: ContainerConfig{ + containerName: "interchain-security-container", + instanceName: "interchain-security-instance", + ccvVersion: "1", + now: time.Now(), + }, + 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", + valoperAddress: "cosmosvaloper19pe9pg5dv9k5fzgzmsrgnw9rl9asf7ddtrgtng", + valconsAddress: "cosmosvalcons1qmq08eruchr5sf5s3rwz7djpr5a25f7xw4mceq", + privValidatorKey: `{"address":"06C0F3E47CC5C748269088DC2F36411D3AAA27C6","pub_key":{"type":"tendermint/PubKeyEd25519","value":"RrclQz9bIhkIy/gfL485g3PYMeiIku4qeo495787X10="},"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"uX+ZpDMg89a6gtqs/+MQpCTSqlkZ0nJQJOhLlCJvwvdGtyVDP1siGQjL+B8vjzmDc9gx6IiS7ip6jj3nvztfXQ=="}}`, + nodeKey: `{"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"fjw4/DAhyRPnwKgXns5SV7QfswRSXMWJpHS7TyULDmJ8ofUc5poQP8dgr8bZRbCV5RV8cPqDq3FPdqwpmUbmdA=="}}`, + ipSuffix: "4", + + // consumer chain assigned key + consumerMnemonic: "exile install vapor thing little toss immune notable lounge december final easy strike title end program interest quote cloth forget forward job october twenty", + consumerDelAddress: "cosmos1eeeggku6dzk3mv7wph3zq035rhtd890sjswszd", + consumerValoperAddress: "cosmosvaloper1eeeggku6dzk3mv7wph3zq035rhtd890shy69w7", + consumerValconsAddress: "cosmosvalcons1muys5jyqk4xd27e208nym85kn0t4zjcfeu63fe", + consumerValPubKey: `{"@type":"/cosmos.crypto.ed25519.PubKey","key":"ujY14AgopV907IYgPAk/5x8c9267S4fQf89nyeCPTes="}`, + consumerPrivValidatorKey: `{"address":"DF090A4880B54CD57B2A79E64D9E969BD7514B09","pub_key":{"type":"tendermint/PubKeyEd25519","value":"ujY14AgopV907IYgPAk/5x8c9267S4fQf89nyeCPTes="},"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"TRJgf7lkTjs/sj43pyweEOanyV7H7fhnVivOi0A4yjW6NjXgCCilX3TshiA8CT/nHxz3brtLh9B/z2fJ4I9N6w=="}}`, + consumerNodeKey: `{"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"F966RL9pi20aXRzEBe4D0xRQJtZt696Xxz44XUON52cFc83FMn1WXJbP6arvA2JPyn2LA3DLKCFHSgALrCGXGA=="}}`, + useConsumerKey: true, + }, + validatorID("bob"): { + mnemonic: "glass trip produce surprise diamond spin excess gaze wash drum human solve dress minor artefact canoe hard ivory orange dinner hybrid moral potato jewel", + delAddress: "cosmos1dkas8mu4kyhl5jrh4nzvm65qz588hy9qcz08la", + valoperAddress: "cosmosvaloper1dkas8mu4kyhl5jrh4nzvm65qz588hy9qakmjnw", + valconsAddress: "cosmosvalcons1nx7n5uh0ztxsynn4sje6eyq2ud6rc6klc96w39", + privValidatorKey: `{"address":"99BD3A72EF12CD024E7584B3AC900AE3743C6ADF","pub_key":{"type":"tendermint/PubKeyEd25519","value":"mAN6RXYxSM4MNGSIriYiS7pHuwAcOHDQAy9/wnlSzOI="},"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"QePcwfWtOavNK7pBJrtoLMzarHKn6iBWfWPFeyV+IdmYA3pFdjFIzgw0ZIiuJiJLuke7ABw4cNADL3/CeVLM4g=="}}`, + nodeKey: `{"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"TQ4vHcO/vKdzGtWpelkX53WdMQd4kTsWGFrdcatdXFvWyO215Rewn5IRP0FszPLWr2DqPzmuH8WvxYGk5aeOXw=="}}`, + ipSuffix: "5", + + // consumer chain assigned key + consumerMnemonic: "grunt list hour endless observe better spoil penalty lab duck only layer vague fantasy satoshi record demise topple space shaft solar practice donor sphere", + consumerDelAddress: "cosmos1q90l6j6lzzgt460ehjj56azknlt5yrd4s38n97", + consumerValoperAddress: "cosmosvaloper1q90l6j6lzzgt460ehjj56azknlt5yrd449nxfd", + consumerValconsAddress: "cosmosvalcons1uuec3cjxajv5te08p220usrjhkfhg9wyvqn0tm", + consumerValPubKey: `{"@type":"/cosmos.crypto.ed25519.PubKey","key":"QlG+iYe6AyYpvY1z9RNJKCVlH14Q/qSz4EjGdGCru3o="}`, + consumerPrivValidatorKey: `{"address":"E73388E246EC9945E5E70A94FE4072BD937415C4","pub_key":{"type":"tendermint/PubKeyEd25519","value":"QlG+iYe6AyYpvY1z9RNJKCVlH14Q/qSz4EjGdGCru3o="},"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"OFR4w+FC6EMw5fAGTrHVexyPrjzQ7QfqgZOMgVf0izlCUb6Jh7oDJim9jXP1E0koJWUfXhD+pLPgSMZ0YKu7eg=="}}`, + consumerNodeKey: `{"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"uhPCqnL2KE8m/8OFNLQ5bN3CJr6mds+xfBi0E4umT/s2uWiJhet+vbYx88DHSdof3gGFNTIzAIxSppscBKX96w=="}}`, + useConsumerKey: false, + }, + }, + chainConfigs: map[chainID]ChainConfig{ + chainID("provi"): { + chainId: chainID("provi"), + binaryName: "interchain-security-pd", + ipPrefix: "7.7.7", + votingWaitTime: 20, + genesisChanges: ".app_state.gov.voting_params.voting_period = \"20s\" | " + + // Custom slashing parameters for testing validator downtime functionality + // See https://docs.cosmos.network/main/modules/slashing/04_begin_block.html#uptime-tracking + ".app_state.slashing.params.signed_blocks_window = \"10\" | " + + ".app_state.slashing.params.min_signed_per_window = \"0.500000000000000000\" | " + + ".app_state.slashing.params.downtime_jail_duration = \"2s\" | " + + ".app_state.slashing.params.slash_fraction_downtime = \"0.010000000000000000\" | " + + ".app_state.provider.params.slash_meter_replenish_fraction = \"1.0\" | " + // This disables slash packet throttling + ".app_state.provider.params.slash_meter_replenish_period = \"3s\"", + }, + chainID("consu"): { + chainId: chainID("consu"), + binaryName: "interchain-security-cd", + ipPrefix: "7.7.8", + votingWaitTime: 20, + genesisChanges: ".app_state.gov.voting_params.voting_period = \"20s\" | " + + ".app_state.slashing.params.signed_blocks_window = \"15\" | " + + ".app_state.slashing.params.min_signed_per_window = \"0.500000000000000000\" | " + + ".app_state.slashing.params.downtime_jail_duration = \"2s\" | " + + ".app_state.slashing.params.slash_fraction_downtime = \"0.010000000000000000\"", + }, + }, + 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/fast_sync = true/fast_sync = false/;`, + } +} + func (s *TestRun) SetDockerConfig(localSdkPath string, useGaia bool, gaiaTag string) { if localSdkPath != "" { fmt.Println("USING LOCAL SDK", localSdkPath) diff --git a/tests/e2e/main.go b/tests/e2e/main.go index 737a318de2..4c7b6722cd 100644 --- a/tests/e2e/main.go +++ b/tests/e2e/main.go @@ -62,6 +62,7 @@ func main() { {DemocracyTestRun(true), democracySteps}, {DemocracyTestRun(false), rewardDenomConsumerSteps}, {SlashThrottleTestRun(), slashThrottleSteps}, + {ConsumerMisbehaviourTestRun(), consumerMisbehaviourSteps}, } if includeMultiConsumer != nil && *includeMultiConsumer { testRuns = append(testRuns, testRunWithSteps{MultiConsumerTestRun(), multipleConsumers}) @@ -173,6 +174,10 @@ func (tr *TestRun) runStep(step Step, verbose bool) { tr.startRelayer(action, verbose) case registerConsumerRewardDenomAction: tr.registerConsumerRewardDenom(action, verbose) + case forkConsumerChainAction: + tr.forkConsumerChain(action, verbose) + case updateLightClientAction: + tr.updateLightClient(action, verbose) default: log.Fatalf("unknown action in testRun %s: %#v", tr.name, action) } diff --git a/tests/e2e/state.go b/tests/e2e/state.go index 15500dd01f..8d9ba9a81e 100644 --- a/tests/e2e/state.go +++ b/tests/e2e/state.go @@ -28,6 +28,7 @@ type ChainState struct { ConsumerChainQueueSizes *map[chainID]uint GlobalSlashQueueSize *uint RegisteredConsumerRewardDenoms *[]string + ClientsFrozenHeights *map[string]clienttypes.Height } type Proposal interface { @@ -184,6 +185,14 @@ func (tr TestRun) getChainState(chain chainID, modelState ChainState) ChainState chainState.RegisteredConsumerRewardDenoms = ®isteredConsumerRewardDenoms } + if modelState.ClientsFrozenHeights != nil { + chainClientsFrozenHeights := map[string]clienttypes.Height{} + for id := range *modelState.ClientsFrozenHeights { + chainClientsFrozenHeights[id] = tr.getClientFrozenHeight(chain, id) + } + chainState.ClientsFrozenHeights = &chainClientsFrozenHeights + } + return chainState } @@ -737,3 +746,33 @@ func (tr TestRun) getQueryNodeIP(chain chainID) string { } return fmt.Sprintf("%s.253", tr.chainConfigs[chain].ipPrefix) } + +// getClientFrozenHeight returns the frozen height for a client with the given client ID +// by querying the hosting chain with the given chainID +func (tr TestRun) getClientFrozenHeight(chain chainID, clientID string) clienttypes.Height { + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + cmd := exec.Command("docker", "exec", tr.containerConfig.instanceName, tr.chainConfigs[chainID("provi")].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 clienttypes.Height{RevisionHeight: uint64(revHeight), RevisionNumber: uint64(revNumber)} +} diff --git a/tests/e2e/steps.go b/tests/e2e/steps.go index 7613b05558..78a56654e6 100644 --- a/tests/e2e/steps.go +++ b/tests/e2e/steps.go @@ -87,3 +87,10 @@ var changeoverSteps = concatSteps( stepsPostChangeoverDelegate("sover"), ) + +var consumerMisbehaviourSteps = concatSteps( + // start provider and consumer chain + stepsStartChainsWithSoftOptOut("consu"), + // make consumer validator to misbehave and get jail + stepsCauseConsumerMisbehaviour("consu"), +) diff --git a/tests/e2e/steps_consumer_misbehaviour.go b/tests/e2e/steps_consumer_misbehaviour.go new file mode 100644 index 0000000000..832f72081f --- /dev/null +++ b/tests/e2e/steps_consumer_misbehaviour.go @@ -0,0 +1,263 @@ +package main + +import ( + clienttypes "github.com/cosmos/ibc-go/v4/modules/core/02-client/types" +) + +// starts a provider chain and a consumer chain with two validators, +// where the voting power is distributed in order that the smallest validator +// can soft opt-out of validating the consumer chain. +func stepsStartChainsWithSoftOptOut(consumerName string) []Step { + s := []Step{ + { + // Create a provider chain with two validators, where one validator holds 96% of the voting power + // and the other validator holds 4% of the voting power. + action: StartChainAction{ + chain: chainID("provi"), + validators: []StartChainValidator{ + {id: validatorID("alice"), stake: 500000000, allocation: 10000000000}, + {id: validatorID("bob"), stake: 20000000, allocation: 10000000000}, + }, + }, + state: State{ + chainID("provi"): ChainState{ + ValBalances: &map[validatorID]uint{ + validatorID("alice"): 9500000000, + validatorID("bob"): 9980000000, + }, + }, + }, + }, + { + action: submitConsumerAdditionProposalAction{ + chain: chainID("provi"), + from: validatorID("alice"), + deposit: 10000001, + consumerChain: chainID(consumerName), + spawnTime: 0, + initialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + }, + state: State{ + chainID("provi"): ChainState{ + ValBalances: &map[validatorID]uint{ + validatorID("alice"): 9489999999, + validatorID("bob"): 9980000000, + }, + Proposals: &map[uint]Proposal{ + 1: ConsumerAdditionProposal{ + Deposit: 10000001, + Chain: chainID(consumerName), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + Status: "PROPOSAL_STATUS_VOTING_PERIOD", + }, + }, + }, + }, + }, + // add a consumer key before the chain starts + // the key will be present in consumer genesis initial_val_set + { + action: assignConsumerPubKeyAction{ + chain: chainID(consumerName), + validator: validatorID("alice"), + consumerPubkey: `{"@type":"/cosmos.crypto.ed25519.PubKey","key":"ujY14AgopV907IYgPAk/5x8c9267S4fQf89nyeCPTes="}`, + // consumer chain has not started + // we don't need to reconfigure the node + // since it will start with consumer key + reconfigureNode: false, + }, + state: State{ + chainID(consumerName): ChainState{ + AssignedKeys: &map[validatorID]string{ + validatorID("alice"): "cosmosvalcons1muys5jyqk4xd27e208nym85kn0t4zjcfeu63fe", + }, + ProviderKeys: &map[validatorID]string{ + validatorID("alice"): "cosmosvalcons1qmq08eruchr5sf5s3rwz7djpr5a25f7xw4mceq", + }, + }, + }, + }, + { + action: voteGovProposalAction{ + chain: chainID("provi"), + from: []validatorID{validatorID("alice"), validatorID("bob")}, + vote: []string{"yes", "yes"}, + propNumber: 1, + }, + state: State{ + chainID("provi"): ChainState{ + Proposals: &map[uint]Proposal{ + 1: ConsumerAdditionProposal{ + Deposit: 10000001, + Chain: chainID(consumerName), + SpawnTime: 0, + InitialHeight: clienttypes.Height{RevisionNumber: 0, RevisionHeight: 1}, + Status: "PROPOSAL_STATUS_PASSED", + }, + }, + ValBalances: &map[validatorID]uint{ + validatorID("alice"): 9500000000, + validatorID("bob"): 9980000000, + }, + }, + }, + }, + { + // start a consumer chain using a single big validator knowing that it holds more than 2/3 of the voting power + // and that the other validators hold less than 5% so they won't get jailed thanks to the sof opt-out mechanism. + action: startConsumerChainAction{ + consumerChain: chainID(consumerName), + providerChain: chainID("provi"), + validators: []StartChainValidator{ + {id: validatorID("alice"), stake: 500000000, allocation: 10000000000}, + }, + // For consumers that're launching with the provider being on an earlier version + // of ICS before the soft opt-out threshold was introduced, we need to set the + // soft opt-out threshold to 0.05 in the consumer genesis to ensure that the + // consumer binary doesn't panic. Sdk requires that all params are set to valid + // values from the genesis file. + genesisChanges: ".app_state.ccvconsumer.params.soft_opt_out_threshold = \"0.05\"", + }, + state: State{ + chainID("provi"): ChainState{ + ValBalances: &map[validatorID]uint{ + validatorID("alice"): 9500000000, + validatorID("bob"): 9980000000, + }, + }, + chainID(consumerName): ChainState{ + ValBalances: &map[validatorID]uint{ + validatorID("alice"): 10000000000, + }, + }, + }, + }, + { + action: addIbcConnectionAction{ + chainA: chainID(consumerName), + chainB: chainID("provi"), + clientA: 0, + clientB: 0, + }, + state: State{}, + }, + { + action: addIbcChannelAction{ + chainA: chainID(consumerName), + chainB: chainID("provi"), + connectionA: 0, + portA: "consumer", // TODO: check port mapping + portB: "provider", + order: "ordered", + }, + state: State{}, + }, + // delegate some token and relay the resulting VSC packets + // in oder to initiates the CCV channel + { + action: delegateTokensAction{ + chain: chainID("provi"), + from: validatorID("alice"), + to: validatorID("alice"), + amount: 11000000, + }, + state: State{ + chainID("provi"): ChainState{ + ValPowers: &map[validatorID]uint{ + validatorID("alice"): 511, + validatorID("bob"): 20, + }, + }, + chainID(consumerName): ChainState{ + ValPowers: &map[validatorID]uint{ + validatorID("alice"): 500, + validatorID("bob"): 20, + }, + }, + }, + }, + { + action: relayPacketsAction{ + chainA: chainID("provi"), + chainB: chainID(consumerName), + port: "provider", + channel: 0, + }, + state: State{ + chainID(consumerName): ChainState{ + ValPowers: &map[validatorID]uint{ + validatorID("alice"): 511, + validatorID("bob"): 20, + }, + }, + }, + }, + } + + return s +} + +// stepsCauseConsumerMisbehaviour forks a consumer chain in order to cause a ICS misbehaviour. +// The malicious validator behind the attack gets jailed and the consumer client freezed on the provider. +func stepsCauseConsumerMisbehaviour(consumerName string) []Step { + consumerClientID := "07-tendermint-0" + forkRelayerConfig := "/root/.hermes/config_fork.toml" + return []Step{ + { + // start a consumer chain's validator clone + action: forkConsumerChainAction{ + consumerChain: chainID(consumerName), + providerChain: chainID("provi"), + validator: validatorID("alice"), + relayerConfig: forkRelayerConfig, + }, + state: State{}, + }, + { + // start relayer to detect ICS misbehaviour + action: startRelayerAction{}, + state: State{ + // The consumer client shouldn't be frozen on the provider yet since + // no client update packet was sent by the fork yet + chainID("provi"): ChainState{ + ClientsFrozenHeights: &map[string]clienttypes.Height{ + "07-tendermint-0": { + RevisionNumber: 0, + RevisionHeight: 0, + }, + }, + }, + }, + }, + { + // update the consumer client hosted on the provider + // using the consumer fork as the primary node + action: updateLightClientAction{ + hostChain: chainID("provi"), + relayerConfig: forkRelayerConfig, // this relayer config uses the "forked" consumer + clientID: consumerClientID, + }, + state: State{ + chainID("provi"): ChainState{ + ValPowers: &map[validatorID]uint{ + validatorID("alice"): 0, + validatorID("bob"): 20, + }, + ClientsFrozenHeights: &map[string]clienttypes.Height{ + "07-tendermint-0": { + RevisionNumber: 0, + RevisionHeight: 1, + }, + }, + }, + chainID(consumerName): ChainState{ + ValPowers: &map[validatorID]uint{ + validatorID("alice"): 511, + validatorID("bob"): 20, + }, + }, + }, + }, + } +} diff --git a/tests/e2e/testnet-scripts/fork-consumer.sh b/tests/e2e/testnet-scripts/fork-consumer.sh new file mode 100644 index 0000000000..0bf96fcb79 --- /dev/null +++ b/tests/e2e/testnet-scripts/fork-consumer.sh @@ -0,0 +1,112 @@ +#!/bin/bash +set -eux + +# The gaiad binary +BIN=$1 + +# the validator ID used to perform the fork +VAL_ID=$2 + +# The consumer chain ID +CHAIN_ID=$3 + +# chain's IP address prefix; $PROV_CHAIN_PREFIX, $CONS_CHAIN_PREFIX... +# see chain config for details +CONS_CHAIN_PREFIX=$4 + +PROV_CHAIN_PREFIX=$5 + +VAL_MNEMONIC=$6 + +FORK_HERMES_CONFIG=$7 + +FORK_NODE_DIR=/$CHAIN_ID/validatorfork + +# create directory for forking/double-signing node +mkdir $FORK_NODE_DIR +cp -r /$CHAIN_ID/validator$VAL_ID/* $FORK_NODE_DIR + +# remove persistent peers +rm -f $FORK_NODE_DIR/addrbook.json + +# add fork to hermes relayer +tee $FORK_HERMES_CONFIG< mnemonic.txt + +# Start the validator forking the consumer chain +# using the sybil IP allocation +ip netns exec $CHAIN_ID-sybil $BIN \ + --home $FORK_NODE_DIR \ + --address tcp://$CONS_CHAIN_PREFIX.252:26655 \ + --rpc.laddr tcp://$CONS_CHAIN_PREFIX.252:26658 \ + --grpc.address $CONS_CHAIN_PREFIX.252:9091 \ + --log_level info \ + --p2p.laddr tcp://$CONS_CHAIN_PREFIX.252:26656 \ + --grpc-web.enable=false start &> /consu/validatorfork/logs & + diff --git a/tests/e2e/testnet-scripts/hermes-config.toml b/tests/e2e/testnet-scripts/hermes-config.toml index eb8154d95b..89c1f0a0bb 100644 --- a/tests/e2e/testnet-scripts/hermes-config.toml +++ b/tests/e2e/testnet-scripts/hermes-config.toml @@ -1,2 +1,18 @@ [global] - log_level = "info" \ No newline at end of file +log_level = "debug" + +[mode] + +[mode.clients] +enabled = true +refresh = true +misbehaviour = true + +[mode.connections] +enabled = false + +[mode.channels] +enabled = false + +[mode.packets] +enabled = true \ No newline at end of file diff --git a/tests/e2e/testnet-scripts/start-chain.sh b/tests/e2e/testnet-scripts/start-chain.sh index 9d6e73fdbb..8bc0dbadca 100644 --- a/tests/e2e/testnet-scripts/start-chain.sh +++ b/tests/e2e/testnet-scripts/start-chain.sh @@ -198,6 +198,7 @@ do #'s/foo/bar/;s/abc/def/' sed -i "$TENDERMINT_CONFIG_TRANSFORM" $CHAIN_ID/validator$VAL_ID/config/config.toml fi + done @@ -257,8 +258,12 @@ do fi done - # Remove leading comma and concat to flag - PERSISTENT_PEERS="--p2p.persistent_peers ${PERSISTENT_PEERS:1}" + + if [ "$PERSISTENT_PEERS" != "" ]; then + # Remove leading comma and concat to flag + PERSISTENT_PEERS="--p2p.persistent_peers ${PERSISTENT_PEERS:1}" + fi + ARGS="$GAIA_HOME $LISTEN_ADDRESS $RPC_ADDRESS $GRPC_ADDRESS $LOG_LEVEL $P2P_ADDRESS $ENABLE_WEBGRPC $PERSISTENT_PEERS" if [[ "$USE_COMETMOCK" == "true" ]]; then diff --git a/x/ccv/provider/keeper/misbehaviour.go b/x/ccv/provider/keeper/misbehaviour.go index 937df77ed0..6cddda1092 100644 --- a/x/ccv/provider/keeper/misbehaviour.go +++ b/x/ccv/provider/keeper/misbehaviour.go @@ -20,9 +20,10 @@ func (k Keeper) HandleConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmty logger := k.Logger(ctx) if err := k.clientKeeper.CheckMisbehaviourAndUpdateState(ctx, &misbehaviour); err != nil { + logger.Info("Misbehaviour rejected", err.Error()) + return err } - // Since the misbehaviour packet was received within the trusting period // w.r.t to the last trusted consensus it entails that the infraction age // isn't too old. see ibc-go/modules/light-clients/07-tendermint/types/misbehaviour_handle.go @@ -33,6 +34,8 @@ func (k Keeper) HandleConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmty return err } + logger.Info("ConstructLightClientEvidence", fmt.Sprintf("%#+v\n", evidence)) + // jail and tombstone the byzantine validators for _, v := range evidence.ByzantineValidators { // convert consumer consensus address @@ -71,7 +74,6 @@ func (k Keeper) HandleConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmty // ConstructLightClientEvidence constructs and returns a CometBFT Light Client Attack(LCA) evidence struct // from the given misbehaviour func (k Keeper) ConstructLightClientEvidence(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) (*tmtypes.LightClientAttackEvidence, error) { - // construct the trusted and conflicted light blocks trusted, err := headerToLightBlock(*misbehaviour.Header1) if err != nil { From 67af6735d45e0cf091f530180adf3f45eab98261 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Wed, 2 Aug 2023 11:54:23 +0200 Subject: [PATCH 25/45] update to handle only equivocations --- tests/integration/misbehaviour.go | 26 +++---- testutil/integration/debug_test.go | 4 +- x/ccv/provider/keeper/misbehaviour.go | 98 ++++++++++++--------------- 3 files changed, 59 insertions(+), 69 deletions(-) diff --git a/tests/integration/misbehaviour.go b/tests/integration/misbehaviour.go index 13ad7e6230..2058451d7b 100644 --- a/tests/integration/misbehaviour.go +++ b/tests/integration/misbehaviour.go @@ -10,6 +10,9 @@ import ( tmtypes "github.com/tendermint/tendermint/types" ) +// TestHandleConsumerMisbehaviour tests that handling a valid misbehaviour +// ,with conflicting headers forming an equivocation, results in the jailing and tombstoning of the validators +// TODO: figure out how to signed headers with a subset a the validator set. func (s *CCVTestSuite) TestHandleConsumerMisbehaviour() { s.SetupCCVChannel(s.path) // required to have the consumer client revision height greater than 0 @@ -25,11 +28,6 @@ func (s *CCVTestSuite) TestHandleConsumerMisbehaviour() { clientTMValset := tmtypes.NewValidatorSet(s.consumerChain.Vals.Validators) clientSigners := s.consumerChain.Signers - altValset := tmtypes.NewValidatorSet(s.consumerChain.Vals.Validators[0:2]) - altSigners := make(map[string]tmtypes.PrivValidator, 1) - altSigners[clientTMValset.Validators[0].Address.String()] = clientSigners[clientTMValset.Validators[0].Address.String()] - altSigners[clientTMValset.Validators[1].Address.String()] = clientSigners[clientTMValset.Validators[1].Address.String()] - misb := &ibctmtypes.Misbehaviour{ ClientId: s.path.EndpointA.ClientID, Header1: s.consumerChain.CreateTMClientHeader( @@ -42,22 +40,25 @@ func (s *CCVTestSuite) TestHandleConsumerMisbehaviour() { clientTMValset, clientSigners, ), + // create a different header by changing the header timestamp only + // in order to create an equivocation, i.e. both headers have the same deterministic states Header2: s.consumerChain.CreateTMClientHeader( s.consumerChain.ChainID, int64(clientHeight.RevisionHeight+1), clientHeight, - altTime, - altValset, - altValset, + altTime.Add(10*time.Second), clientTMValset, - altSigners, + clientTMValset, + clientTMValset, + clientSigners, ), } err := s.providerApp.GetProviderKeeper().HandleConsumerMisbehaviour(s.providerCtx(), *misb) s.NoError(err) - for _, v := range altValset.Validators { + // verify that validators are jailed and tombstoned + for _, v := range clientTMValset.Validators { consuAddr := sdk.ConsAddress(v.Address.Bytes()) provAddr := s.providerApp.GetProviderKeeper().GetProviderAddrFromConsumerAddr(s.providerCtx(), s.consumerChain.ChainID, types.NewConsumerConsAddress(consuAddr)) val, ok := s.providerApp.GetTestStakingKeeper().GetValidatorByConsAddr(s.providerCtx(), provAddr.Address) @@ -65,9 +66,10 @@ func (s *CCVTestSuite) TestHandleConsumerMisbehaviour() { s.Require().True(val.Jailed) s.Require().True(s.providerApp.GetTestSlashingKeeper().IsTombstoned(s.providerCtx(), provAddr.Address)) } + } -func (s *CCVTestSuite) TestConstructLightClientEvidence() { +func (s *CCVTestSuite) TestGetByzantineValidators() { s.SetupCCVChannel(s.path) // required to have the consumer client revision height greater than 0 s.SendEmptyVSCPacket() @@ -208,7 +210,7 @@ func (s *CCVTestSuite) TestConstructLightClientEvidence() { for _, tc := range testCases { s.Run(tc.name, func() { - ev, err := s.providerApp.GetProviderKeeper().ConstructLightClientEvidence( + ev, err := s.providerApp.GetProviderKeeper().GetByzantineValidators( s.providerCtx(), *tc.misbehaviour, ) diff --git a/testutil/integration/debug_test.go b/testutil/integration/debug_test.go index 3bafef8295..6715d6368e 100644 --- a/testutil/integration/debug_test.go +++ b/testutil/integration/debug_test.go @@ -265,6 +265,6 @@ func TestHandleConsumerMisbehaviour(t *testing.T) { runCCVTestByName(t, "TestHandleConsumerMisbehaviour") } -func TestConstructLightClientEvidence(t *testing.T) { - runCCVTestByName(t, "TestConstructLightClientEvidence") +func TestGetByzantineValidators(t *testing.T) { + runCCVTestByName(t, "TestGetByzantineValidators") } diff --git a/x/ccv/provider/keeper/misbehaviour.go b/x/ccv/provider/keeper/misbehaviour.go index 6cddda1092..077315d887 100644 --- a/x/ccv/provider/keeper/misbehaviour.go +++ b/x/ccv/provider/keeper/misbehaviour.go @@ -1,9 +1,9 @@ package keeper import ( + "bytes" "fmt" - "reflect" - "time" + "sort" "github.com/cosmos/interchain-security/v2/x/ccv/provider/types" @@ -28,14 +28,13 @@ func (k Keeper) HandleConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmty // w.r.t to the last trusted consensus it entails that the infraction age // isn't too old. see ibc-go/modules/light-clients/07-tendermint/types/misbehaviour_handle.go - // construct a light client attack evidence - evidence, err := k.ConstructLightClientEvidence(ctx, misbehaviour) + // Get Byzantine validators from the conflicting headers + // Note that it only handle equivocation light client attacks + evidence, err := k.GetByzantineValidators(ctx, misbehaviour) if err != nil { return err } - logger.Info("ConstructLightClientEvidence", fmt.Sprintf("%#+v\n", evidence)) - // jail and tombstone the byzantine validators for _, v := range evidence.ByzantineValidators { // convert consumer consensus address @@ -71,71 +70,52 @@ func (k Keeper) HandleConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmty return nil } -// ConstructLightClientEvidence constructs and returns a CometBFT Light Client Attack(LCA) evidence struct -// from the given misbehaviour -func (k Keeper) ConstructLightClientEvidence(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) (*tmtypes.LightClientAttackEvidence, error) { +// GetByzantineValidators return the Byzantine validators from a given misbehaviour. Note that it only +// handles the equivocation light client attack and therefore will return an error an error the +// conflicting headers commit aren't for the same round. +func (k Keeper) GetByzantineValidators(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) (*tmtypes.LightClientAttackEvidence, error) { // construct the trusted and conflicted light blocks - trusted, err := headerToLightBlock(*misbehaviour.Header1) - if err != nil { - return nil, err - } - conflicted, err := headerToLightBlock(*misbehaviour.Header2) + header1, err := headerToLightBlock(*misbehaviour.Header1) if err != nil { return nil, err } - - // get common header using the IBC misbehaviour - commonHeight, commonTs, commonValset, err := k.GetTrustedInfoFromMisbehaviour(ctx, misbehaviour) + header2, err := headerToLightBlock(*misbehaviour.Header2) if err != nil { return nil, err } - // construct the LCA evidence by copying the CometBFT constructor - // see newLightClientAttackEvidence() in tendermint/light/detector.go - ev := tmtypes.LightClientAttackEvidence{ - ConflictingBlock: conflicted, - } - - if ev.ConflictingHeaderIsInvalid(trusted.Header) { - ev.CommonHeight = commonHeight - ev.Timestamp = commonTs - ev.TotalVotingPower = commonValset.TotalVotingPower() - } else { - ev.CommonHeight = trusted.Header.Height - ev.Timestamp = trusted.Header.Time - ev.TotalVotingPower = trusted.ValidatorSet.TotalVotingPower() - } - - ev.ByzantineValidators = ev.GetByzantineValidators(commonValset, trusted.SignedHeader) + var validators []*tmtypes.Validator - return &ev, nil -} + // Check if the light client attack correspond to a "equivocation" + // in this case return the validators that signed both headers -// GetCommonFromMisbehaviour checks whether the given ibc misbehaviour's headers share common trusted height -// and that a consensus state exists for this height. In this case, it returns the associated trusted height, timestamp and valset. -func (k Keeper) GetTrustedInfoFromMisbehaviour(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) (int64, time.Time, *tmtypes.ValidatorSet, error) { - // a common trusted validator set and height is required - commonHeight := misbehaviour.Header1.TrustedHeight - if !commonHeight.EQ(misbehaviour.Header2.TrustedHeight) { - return 0, time.Time{}, nil, fmt.Errorf("misbehaviour headers have different trusted height: %v , %v", commonHeight, misbehaviour.Header2.TrustedHeight) - } + if headersHaveConflictingStatesTransitions(header1, header2) { + return nil, fmt.Errorf("cannot get Byzantine validators; headers have conflicting states transitions; possible lunatic attack detected") + } else if header1.Commit.Round == header2.Commit.Round { + for i := 0; i < len(header2.Commit.Signatures); i++ { + sigA := header2.Commit.Signatures[i] + if sigA.Absent() { + continue + } - commonValset := misbehaviour.Header1.TrustedValidators - if !reflect.DeepEqual(commonValset.Validators, misbehaviour.Header2.TrustedValidators.Validators) { - return 0, time.Time{}, nil, fmt.Errorf("misbehaviour headers have different trusted validator set: %v , %v", commonHeight, misbehaviour.Header2.TrustedHeight) - } + sigB := header1.Commit.Signatures[i] + if sigB.Absent() { + continue + } - cs, ok := k.clientKeeper.GetClientConsensusState(ctx, misbehaviour.GetClientID(), commonHeight) - if !ok { - return 0, time.Time{}, nil, fmt.Errorf("cannot find consensus state at trusted height %d for client %s", commonHeight, misbehaviour.GetClientID()) + _, val := header2.ValidatorSet.GetByAddress(sigA.ValidatorAddress) + validators = append(validators, val) + } + sort.Sort(tmtypes.ValidatorsByVotingPower(validators)) + } else { + return nil, fmt.Errorf("cannot get Byzantine validators; misbehaviour is for an amnesia attack") } - vs, err := tmtypes.ValidatorSetFromProto(commonValset) - if err != nil { - return 0, time.Time{}, nil, err + ev := tmtypes.LightClientAttackEvidence{ + ByzantineValidators: validators, } - return int64(commonHeight.RevisionHeight), time.Unix(0, int64(cs.GetTimestamp())), vs, nil + return &ev, nil } // headerToLightBlock returns a CometBFT light block from the given IBC header @@ -155,3 +135,11 @@ func headerToLightBlock(h ibctmtypes.Header) (*tmtypes.LightBlock, error) { ValidatorSet: vs, }, nil } + +func headersHaveConflictingStatesTransitions(header1, header2 *tmtypes.LightBlock) bool { + return !bytes.Equal(header1.ValidatorsHash, header2.ValidatorsHash) || + !bytes.Equal(header1.NextValidatorsHash, header2.NextValidatorsHash) || + !bytes.Equal(header1.ConsensusHash, header2.ConsensusHash) || + !bytes.Equal(header1.AppHash, header2.AppHash) || + !bytes.Equal(header1.LastResultsHash, header2.LastResultsHash) +} From 245220bf309176967878d1f17f9b68859b9d2188 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Wed, 2 Aug 2023 12:23:53 +0200 Subject: [PATCH 26/45] improve doc --- tests/integration/misbehaviour.go | 21 +++++++------- x/ccv/provider/keeper/misbehaviour.go | 40 ++++++++++++--------------- 2 files changed, 29 insertions(+), 32 deletions(-) diff --git a/tests/integration/misbehaviour.go b/tests/integration/misbehaviour.go index 2058451d7b..5becb69f47 100644 --- a/tests/integration/misbehaviour.go +++ b/tests/integration/misbehaviour.go @@ -10,8 +10,8 @@ import ( tmtypes "github.com/tendermint/tendermint/types" ) -// TestHandleConsumerMisbehaviour tests that handling a valid misbehaviour -// ,with conflicting headers forming an equivocation, results in the jailing and tombstoning of the validators +// TestHandleConsumerMisbehaviour tests that handling a valid misbehaviour, +// with conflicting headers forming an equivocation, results in the jailing and tombstoning of the validators // TODO: figure out how to signed headers with a subset a the validator set. func (s *CCVTestSuite) TestHandleConsumerMisbehaviour() { s.SetupCCVChannel(s.path) @@ -86,6 +86,7 @@ func (s *CCVTestSuite) TestGetByzantineValidators() { altSigners[clientTMValset.Validators[1].Address.String()] = clientSigners[clientTMValset.Validators[1].Address.String()] altSigners[clientTMValset.Validators[2].Address.String()] = clientSigners[clientTMValset.Validators[2].Address.String()] + // TODO: figure out how to test an amnesia cases for "amnesia" attack testCases := []struct { name string misbehaviour *ibctmtypes.Misbehaviour @@ -109,7 +110,7 @@ func (s *CCVTestSuite) TestGetByzantineValidators() { false, }, { - "invalid misbehaviour - Header2 is empty", + "invalid headers - Header2 is empty", &ibctmtypes.Misbehaviour{ Header1: s.consumerChain.CreateTMClientHeader( s.consumerChain.ChainID, @@ -126,7 +127,7 @@ func (s *CCVTestSuite) TestGetByzantineValidators() { false, }, { - "invalid misbehaviour - ClientId is empty", + "invalid headers - ClientId is empty", &ibctmtypes.Misbehaviour{ ClientId: "unknown-client-id", Header1: s.consumerChain.CreateTMClientHeader( @@ -153,7 +154,7 @@ func (s *CCVTestSuite) TestGetByzantineValidators() { false, }, { - "light client attack - lunatic attack", + "invalid light client attack - lunatic attack", &ibctmtypes.Misbehaviour{ ClientId: s.path.EndpointA.ClientID, Header1: s.consumerChain.CreateTMClientHeader( @@ -177,10 +178,10 @@ func (s *CCVTestSuite) TestGetByzantineValidators() { altSigners, ), }, - true, + false, }, { - "light client attack - equivocation", + "valid light client attack - equivocation", &ibctmtypes.Misbehaviour{ ClientId: s.path.EndpointA.ClientID, Header1: s.consumerChain.CreateTMClientHeader( @@ -210,7 +211,7 @@ func (s *CCVTestSuite) TestGetByzantineValidators() { for _, tc := range testCases { s.Run(tc.name, func() { - ev, err := s.providerApp.GetProviderKeeper().GetByzantineValidators( + byzantineValidators, err := s.providerApp.GetProviderKeeper().GetByzantineValidators( s.providerCtx(), *tc.misbehaviour, ) @@ -220,12 +221,12 @@ func (s *CCVTestSuite) TestGetByzantineValidators() { // who signed the bad header (Header2) should be in returned in the evidence h2Valset := tc.misbehaviour.Header2.ValidatorSet - s.Equal(len(h2Valset.Validators), len(ev.ByzantineValidators)) + s.Equal(len(h2Valset.Validators), len(byzantineValidators)) vs, err := tmtypes.ValidatorSetFromProto(tc.misbehaviour.Header2.ValidatorSet) s.NoError(err) - for _, v := range ev.ByzantineValidators { + for _, v := range byzantineValidators { idx, _ := vs.GetByAddress(v.Address) s.True(idx >= 0) } diff --git a/x/ccv/provider/keeper/misbehaviour.go b/x/ccv/provider/keeper/misbehaviour.go index 077315d887..563c439739 100644 --- a/x/ccv/provider/keeper/misbehaviour.go +++ b/x/ccv/provider/keeper/misbehaviour.go @@ -30,13 +30,13 @@ func (k Keeper) HandleConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmty // Get Byzantine validators from the conflicting headers // Note that it only handle equivocation light client attacks - evidence, err := k.GetByzantineValidators(ctx, misbehaviour) + byzantineValidators, err := k.GetByzantineValidators(ctx, misbehaviour) if err != nil { return err } // jail and tombstone the byzantine validators - for _, v := range evidence.ByzantineValidators { + for _, v := range byzantineValidators { // convert consumer consensus address consuAddr := sdk.ConsAddress(v.Address.Bytes()) provAddr := k.GetProviderAddrFromConsumerAddr(ctx, misbehaviour.Header1.Header.ChainID, types.NewConsumerConsAddress(consuAddr)) @@ -64,16 +64,15 @@ func (k Keeper) HandleConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmty logger.Info( "confirmed equivocation", - "byzantine validators", evidence.ByzantineValidators, + "byzantine validators", byzantineValidators, ) return nil } -// GetByzantineValidators return the Byzantine validators from a given misbehaviour. Note that it only -// handles the equivocation light client attack and therefore will return an error an error the -// conflicting headers commit aren't for the same round. -func (k Keeper) GetByzantineValidators(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) (*tmtypes.LightClientAttackEvidence, error) { +// GetByzantineValidators returns the Byzantine validators from a given misbehaviour +// with the condition that it corresponds to an equivocation light client attack +func (k Keeper) GetByzantineValidators(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) ([]*tmtypes.Validator, error) { // construct the trusted and conflicted light blocks header1, err := headerToLightBlock(*misbehaviour.Header1) if err != nil { @@ -86,11 +85,11 @@ func (k Keeper) GetByzantineValidators(ctx sdk.Context, misbehaviour ibctmtypes. var validators []*tmtypes.Validator - // Check if the light client attack correspond to a "equivocation" - // in this case return the validators that signed both headers - - if headersHaveConflictingStatesTransitions(header1, header2) { + // If the headers transitions states aren't equal this is a "lunatic" attack + if !headersStatesTransitionsAreEqual(header1, header2) { return nil, fmt.Errorf("cannot get Byzantine validators; headers have conflicting states transitions; possible lunatic attack detected") + // Both headers' commit are in the round so it's an "equivocation" + // and we return the validators that signed both headers } else if header1.Commit.Round == header2.Commit.Round { for i := 0; i < len(header2.Commit.Signatures); i++ { sigA := header2.Commit.Signatures[i] @@ -107,15 +106,12 @@ func (k Keeper) GetByzantineValidators(ctx sdk.Context, misbehaviour ibctmtypes. validators = append(validators, val) } sort.Sort(tmtypes.ValidatorsByVotingPower(validators)) + // If the two previous conditions aren't satisfied, we have an "amnesia" attack } else { return nil, fmt.Errorf("cannot get Byzantine validators; misbehaviour is for an amnesia attack") } - ev := tmtypes.LightClientAttackEvidence{ - ByzantineValidators: validators, - } - - return &ev, nil + return validators, nil } // headerToLightBlock returns a CometBFT light block from the given IBC header @@ -136,10 +132,10 @@ func headerToLightBlock(h ibctmtypes.Header) (*tmtypes.LightBlock, error) { }, nil } -func headersHaveConflictingStatesTransitions(header1, header2 *tmtypes.LightBlock) bool { - return !bytes.Equal(header1.ValidatorsHash, header2.ValidatorsHash) || - !bytes.Equal(header1.NextValidatorsHash, header2.NextValidatorsHash) || - !bytes.Equal(header1.ConsensusHash, header2.ConsensusHash) || - !bytes.Equal(header1.AppHash, header2.AppHash) || - !bytes.Equal(header1.LastResultsHash, header2.LastResultsHash) +func headersStatesTransitionsAreEqual(header1, header2 *tmtypes.LightBlock) bool { + return bytes.Equal(header1.ValidatorsHash, header2.ValidatorsHash) && + bytes.Equal(header1.NextValidatorsHash, header2.NextValidatorsHash) && + bytes.Equal(header1.ConsensusHash, header2.ConsensusHash) && + bytes.Equal(header1.AppHash, header2.AppHash) && + bytes.Equal(header1.LastResultsHash, header2.LastResultsHash) } From d5b21222d1459e883dd83b3c2b6c50d40747b514 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Wed, 2 Aug 2023 12:36:23 +0200 Subject: [PATCH 27/45] update doc --- x/ccv/provider/keeper/misbehaviour.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/x/ccv/provider/keeper/misbehaviour.go b/x/ccv/provider/keeper/misbehaviour.go index 563c439739..38651465d4 100644 --- a/x/ccv/provider/keeper/misbehaviour.go +++ b/x/ccv/provider/keeper/misbehaviour.go @@ -13,29 +13,30 @@ import ( tmtypes "github.com/tendermint/tendermint/types" ) -// HandleConsumerMisbehaviour checks whether the given IBC misbehaviour is valid and, if they are, the misbehaving -// CheckConsumerMisbehaviour check that the given IBC misbehaviour headers forms a valid light client attack evidence. -// proceed to the jailing and tombstoning of the bzyantine validators. +// HandleConsumerMisbehaviour checks if the given IBC misbehaviour corresponds to an equivocation light client attack +// and in this case jail and tombstone the Byzantine validators func (k Keeper) HandleConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) error { logger := k.Logger(ctx) + // Check that the misbehaviour is valid and that the client isn't expired if err := k.clientKeeper.CheckMisbehaviourAndUpdateState(ctx, &misbehaviour); err != nil { logger.Info("Misbehaviour rejected", err.Error()) return err } + // Since the misbehaviour packet was received within the trusting period // w.r.t to the last trusted consensus it entails that the infraction age // isn't too old. see ibc-go/modules/light-clients/07-tendermint/types/misbehaviour_handle.go // Get Byzantine validators from the conflicting headers - // Note that it only handle equivocation light client attacks + // Note that it returns an error if the misbehaviour doesn't correspond to an equivocation byzantineValidators, err := k.GetByzantineValidators(ctx, misbehaviour) if err != nil { return err } - // jail and tombstone the byzantine validators + // jail and tombstone the Byzantine validators for _, v := range byzantineValidators { // convert consumer consensus address consuAddr := sdk.ConsAddress(v.Address.Bytes()) From c9a9f3889242f1a8ac63250345dac7f91a343d29 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Thu, 3 Aug 2023 09:23:24 +0200 Subject: [PATCH 28/45] update E2E tests comment --- tests/e2e/steps_consumer_misbehaviour.go | 27 ++++++++---------------- x/ccv/provider/keeper/misbehaviour.go | 4 ++-- 2 files changed, 11 insertions(+), 20 deletions(-) diff --git a/tests/e2e/steps_consumer_misbehaviour.go b/tests/e2e/steps_consumer_misbehaviour.go index 832f72081f..0654226d17 100644 --- a/tests/e2e/steps_consumer_misbehaviour.go +++ b/tests/e2e/steps_consumer_misbehaviour.go @@ -198,14 +198,13 @@ func stepsStartChainsWithSoftOptOut(consumerName string) []Step { return s } -// stepsCauseConsumerMisbehaviour forks a consumer chain in order to cause a ICS misbehaviour. -// The malicious validator behind the attack gets jailed and the consumer client freezed on the provider. +// stepsCauseConsumerMisbehaviour causes a ICS misbehaviour by forking a consumer chain. func stepsCauseConsumerMisbehaviour(consumerName string) []Step { consumerClientID := "07-tendermint-0" forkRelayerConfig := "/root/.hermes/config_fork.toml" return []Step{ { - // start a consumer chain's validator clone + // fork the consumer chain by cloning of its validator node action: forkConsumerChainAction{ consumerChain: chainID(consumerName), providerChain: chainID("provi"), @@ -217,21 +216,10 @@ func stepsCauseConsumerMisbehaviour(consumerName string) []Step { { // start relayer to detect ICS misbehaviour action: startRelayerAction{}, - state: State{ - // The consumer client shouldn't be frozen on the provider yet since - // no client update packet was sent by the fork yet - chainID("provi"): ChainState{ - ClientsFrozenHeights: &map[string]clienttypes.Height{ - "07-tendermint-0": { - RevisionNumber: 0, - RevisionHeight: 0, - }, - }, - }, - }, + state: State{}, }, { - // update the consumer client hosted on the provider + // update the consumer light client hosted on the provider // using the consumer fork as the primary node action: updateLightClientAction{ hostChain: chainID("provi"), @@ -240,14 +228,17 @@ func stepsCauseConsumerMisbehaviour(consumerName string) []Step { }, state: State{ chainID("provi"): ChainState{ + // No jailing should have occurred since a consumer fork triggers a ICS misbehaviour + // that can't be handled by a provider chain, see HandleConsumerMisbehaviour() x/ccv/provider/keeper/misbehaviour.go. ValPowers: &map[validatorID]uint{ - validatorID("alice"): 0, + validatorID("alice"): 511, validatorID("bob"): 20, }, + // The consumer light client shouldn't be frozen ClientsFrozenHeights: &map[string]clienttypes.Height{ "07-tendermint-0": { RevisionNumber: 0, - RevisionHeight: 1, + RevisionHeight: 0, }, }, }, diff --git a/x/ccv/provider/keeper/misbehaviour.go b/x/ccv/provider/keeper/misbehaviour.go index 38651465d4..507ec75b50 100644 --- a/x/ccv/provider/keeper/misbehaviour.go +++ b/x/ccv/provider/keeper/misbehaviour.go @@ -13,8 +13,8 @@ import ( tmtypes "github.com/tendermint/tendermint/types" ) -// HandleConsumerMisbehaviour checks if the given IBC misbehaviour corresponds to an equivocation light client attack -// and in this case jail and tombstone the Byzantine validators +// HandleConsumerMisbehaviour checks if the given IBC misbehaviour corresponds to an equivocation light client attack, +// and in this case, jails and tombstones the Byzantine validators func (k Keeper) HandleConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) error { logger := k.Logger(ctx) From 28e8813ce0ad205639c05b29fe88a44572ce5a41 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Fri, 4 Aug 2023 07:55:34 +0200 Subject: [PATCH 29/45] optimize signatures check --- tests/integration/misbehaviour.go | 47 ++++++------------ x/ccv/provider/keeper/misbehaviour.go | 70 ++++++++++++++++++--------- 2 files changed, 61 insertions(+), 56 deletions(-) diff --git a/tests/integration/misbehaviour.go b/tests/integration/misbehaviour.go index 5becb69f47..e4ad997a13 100644 --- a/tests/integration/misbehaviour.go +++ b/tests/integration/misbehaviour.go @@ -80,6 +80,7 @@ func (s *CCVTestSuite) TestGetByzantineValidators() { clientTMValset := tmtypes.NewValidatorSet(s.consumerChain.Vals.Validators) clientSigners := s.consumerChain.Signers + // Create a validator set subset altValset := tmtypes.NewValidatorSet(s.consumerChain.Vals.Validators[0:3]) altSigners := make(map[string]tmtypes.PrivValidator, 1) altSigners[clientTMValset.Validators[0].Address.String()] = clientSigners[clientTMValset.Validators[0].Address.String()] @@ -88,9 +89,10 @@ func (s *CCVTestSuite) TestGetByzantineValidators() { // TODO: figure out how to test an amnesia cases for "amnesia" attack testCases := []struct { - name string - misbehaviour *ibctmtypes.Misbehaviour - expPass bool + name string + misbehaviour *ibctmtypes.Misbehaviour + expByzantineValidators []*tmtypes.Validator + expPass bool }{ { "invalid misbehaviour - Header1 is empty", @@ -107,6 +109,7 @@ func (s *CCVTestSuite) TestGetByzantineValidators() { altSigners, ), }, + nil, false, }, { @@ -124,33 +127,7 @@ func (s *CCVTestSuite) TestGetByzantineValidators() { ), Header2: &ibctmtypes.Header{}, }, - false, - }, - { - "invalid headers - ClientId is empty", - &ibctmtypes.Misbehaviour{ - ClientId: "unknown-client-id", - Header1: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight+1), - clientHeight, - altTime, - clientTMValset, - clientTMValset, - clientTMValset, - clientSigners, - ), - Header2: s.consumerChain.CreateTMClientHeader( - s.consumerChain.ChainID, - int64(clientHeight.RevisionHeight+1), - clientHeight, - altTime, - altValset, - altValset, - clientTMValset, - altSigners, - ), - }, + nil, false, }, { @@ -178,7 +155,10 @@ func (s *CCVTestSuite) TestGetByzantineValidators() { altSigners, ), }, - false, + // Expect to get only the validators + // who signed both headers are returned + altValset.Validators, + true, }, { "valid light client attack - equivocation", @@ -205,6 +185,9 @@ func (s *CCVTestSuite) TestGetByzantineValidators() { clientSigners, ), }, + // Expect to get the entire valset since + // all validators double-signed + clientTMValset.Validators, true, }, } @@ -226,7 +209,7 @@ func (s *CCVTestSuite) TestGetByzantineValidators() { vs, err := tmtypes.ValidatorSetFromProto(tc.misbehaviour.Header2.ValidatorSet) s.NoError(err) - for _, v := range byzantineValidators { + for _, v := range tc.expByzantineValidators { idx, _ := vs.GetByAddress(v.Address) s.True(idx >= 0) } diff --git a/x/ccv/provider/keeper/misbehaviour.go b/x/ccv/provider/keeper/misbehaviour.go index 507ec75b50..f0af650bb7 100644 --- a/x/ccv/provider/keeper/misbehaviour.go +++ b/x/ccv/provider/keeper/misbehaviour.go @@ -2,7 +2,6 @@ package keeper import ( "bytes" - "fmt" "sort" "github.com/cosmos/interchain-security/v2/x/ccv/provider/types" @@ -86,32 +85,55 @@ func (k Keeper) GetByzantineValidators(ctx sdk.Context, misbehaviour ibctmtypes. var validators []*tmtypes.Validator - // If the headers transitions states aren't equal this is a "lunatic" attack - if !headersStatesTransitionsAreEqual(header1, header2) { - return nil, fmt.Errorf("cannot get Byzantine validators; headers have conflicting states transitions; possible lunatic attack detected") - // Both headers' commit are in the round so it's an "equivocation" - // and we return the validators that signed both headers - } else if header1.Commit.Round == header2.Commit.Round { - for i := 0; i < len(header2.Commit.Signatures); i++ { - sigA := header2.Commit.Signatures[i] - if sigA.Absent() { - continue - } - - sigB := header1.Commit.Signatures[i] - if sigB.Absent() { - continue - } - - _, val := header2.ValidatorSet.GetByAddress(sigA.ValidatorAddress) - validators = append(validators, val) + // // If the headers transitions states aren't equal this is a "lunatic" attack + // // both we still return the validators which double signed + // if !headersStatesTransitionsAreEqual(header1, header2) { + + // compare the signatures in the headers + // and return the intersection of validators who signed both + // create a map with the validators that signed header1 + header1Signers := map[string]struct{}{} + for _, sign := range header1.Commit.Signatures { + if sign.Absent() { + continue } - sort.Sort(tmtypes.ValidatorsByVotingPower(validators)) - // If the two previous conditions aren't satisfied, we have an "amnesia" attack - } else { - return nil, fmt.Errorf("cannot get Byzantine validators; misbehaviour is for an amnesia attack") + header1Signers[sign.ValidatorAddress.String()] = struct{}{} } + // iterate over the header2 signers + // and check if they also signed header1 + for _, sign := range header2.Commit.Signatures { + if sign.Absent() { + continue + } + if _, ok := header1Signers[sign.ValidatorAddress.String()]; ok { + _, val := header1.ValidatorSet.GetByAddress(sign.ValidatorAddress) + validators = append(validators, val) + } + } + // } else if header1.Commit.Round == header2.Commit.Round { // Is it still required to compare the rounds here? + // // This is an equivocation attack as both commits are in the same round. + // // We then find the validators that voted for the both headers. + // // + // // Validator hashes are the same therefore the indexing order of validators are the same and thus we + // // only need a single loop to find the validators that voted twice. + // for i := 0; i < len(header1.Commit.Signatures); i++ { + // sigA := header1.Commit.Signatures[i] + // if sigA.Absent() { + // continue + // } + + // sigB := header2.Commit.Signatures[i] + // if sigB.Absent() { + // continue + // } + + // _, val := header1.ValidatorSet.GetByAddress(sigA.ValidatorAddress) + // validators = append(validators, val) + // } + // } + + sort.Sort(tmtypes.ValidatorsByVotingPower(validators)) return validators, nil } From 5c32de86b1d1a4695660a731b4de3825fb0410ef Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Fri, 4 Aug 2023 11:17:34 +0200 Subject: [PATCH 30/45] doc --- tests/e2e/steps_consumer_misbehaviour.go | 12 +++---- x/ccv/provider/keeper/misbehaviour.go | 41 +++--------------------- 2 files changed, 10 insertions(+), 43 deletions(-) diff --git a/tests/e2e/steps_consumer_misbehaviour.go b/tests/e2e/steps_consumer_misbehaviour.go index 0654226d17..ff2e9112e3 100644 --- a/tests/e2e/steps_consumer_misbehaviour.go +++ b/tests/e2e/steps_consumer_misbehaviour.go @@ -219,8 +219,8 @@ func stepsCauseConsumerMisbehaviour(consumerName string) []Step { state: State{}, }, { - // update the consumer light client hosted on the provider - // using the consumer fork as the primary node + // update the fork consumer client to create a light client attack + // which should trigger a ICS misbehaviour message action: updateLightClientAction{ hostChain: chainID("provi"), relayerConfig: forkRelayerConfig, // this relayer config uses the "forked" consumer @@ -228,17 +228,17 @@ func stepsCauseConsumerMisbehaviour(consumerName string) []Step { }, state: State{ chainID("provi"): ChainState{ - // No jailing should have occurred since a consumer fork triggers a ICS misbehaviour - // that can't be handled by a provider chain, see HandleConsumerMisbehaviour() x/ccv/provider/keeper/misbehaviour.go. + // validator should be jailed ValPowers: &map[validatorID]uint{ validatorID("alice"): 511, validatorID("bob"): 20, }, - // The consumer light client shouldn't be frozen + // The consumer light client should be frozen + // TODO: to be changed when merging PR #1148 ClientsFrozenHeights: &map[string]clienttypes.Height{ "07-tendermint-0": { RevisionNumber: 0, - RevisionHeight: 0, + RevisionHeight: 1, }, }, }, diff --git a/x/ccv/provider/keeper/misbehaviour.go b/x/ccv/provider/keeper/misbehaviour.go index f0af650bb7..844a5523ea 100644 --- a/x/ccv/provider/keeper/misbehaviour.go +++ b/x/ccv/provider/keeper/misbehaviour.go @@ -1,7 +1,6 @@ package keeper import ( - "bytes" "sort" "github.com/cosmos/interchain-security/v2/x/ccv/provider/types" @@ -85,13 +84,10 @@ func (k Keeper) GetByzantineValidators(ctx sdk.Context, misbehaviour ibctmtypes. var validators []*tmtypes.Validator - // // If the headers transitions states aren't equal this is a "lunatic" attack - // // both we still return the validators which double signed - // if !headersStatesTransitionsAreEqual(header1, header2) { - - // compare the signatures in the headers + // compare the signatures of the headers // and return the intersection of validators who signed both - // create a map with the validators that signed header1 + + // create a map with the validators' address that signed header1 header1Signers := map[string]struct{}{} for _, sign := range header1.Commit.Signatures { if sign.Absent() { @@ -101,7 +97,7 @@ func (k Keeper) GetByzantineValidators(ctx sdk.Context, misbehaviour ibctmtypes. } // iterate over the header2 signers - // and check if they also signed header1 + // and check if they s signed header1 for _, sign := range header2.Commit.Signatures { if sign.Absent() { continue @@ -111,27 +107,6 @@ func (k Keeper) GetByzantineValidators(ctx sdk.Context, misbehaviour ibctmtypes. validators = append(validators, val) } } - // } else if header1.Commit.Round == header2.Commit.Round { // Is it still required to compare the rounds here? - // // This is an equivocation attack as both commits are in the same round. - // // We then find the validators that voted for the both headers. - // // - // // Validator hashes are the same therefore the indexing order of validators are the same and thus we - // // only need a single loop to find the validators that voted twice. - // for i := 0; i < len(header1.Commit.Signatures); i++ { - // sigA := header1.Commit.Signatures[i] - // if sigA.Absent() { - // continue - // } - - // sigB := header2.Commit.Signatures[i] - // if sigB.Absent() { - // continue - // } - - // _, val := header1.ValidatorSet.GetByAddress(sigA.ValidatorAddress) - // validators = append(validators, val) - // } - // } sort.Sort(tmtypes.ValidatorsByVotingPower(validators)) return validators, nil @@ -154,11 +129,3 @@ func headerToLightBlock(h ibctmtypes.Header) (*tmtypes.LightBlock, error) { ValidatorSet: vs, }, nil } - -func headersStatesTransitionsAreEqual(header1, header2 *tmtypes.LightBlock) bool { - return bytes.Equal(header1.ValidatorsHash, header2.ValidatorsHash) && - bytes.Equal(header1.NextValidatorsHash, header2.NextValidatorsHash) && - bytes.Equal(header1.ConsensusHash, header2.ConsensusHash) && - bytes.Equal(header1.AppHash, header2.AppHash) && - bytes.Equal(header1.LastResultsHash, header2.LastResultsHash) -} From 1ae356ff0ab5fa9e29d5193711322fd3a5fc8dbd Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Fri, 4 Aug 2023 14:02:22 +0200 Subject: [PATCH 31/45] update e2e tests --- tests/e2e/steps_consumer_misbehaviour.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/e2e/steps_consumer_misbehaviour.go b/tests/e2e/steps_consumer_misbehaviour.go index ff2e9112e3..d41e0bd2c0 100644 --- a/tests/e2e/steps_consumer_misbehaviour.go +++ b/tests/e2e/steps_consumer_misbehaviour.go @@ -228,9 +228,9 @@ func stepsCauseConsumerMisbehaviour(consumerName string) []Step { }, state: State{ chainID("provi"): ChainState{ - // validator should be jailed + // validator should be jailed on the provider ValPowers: &map[validatorID]uint{ - validatorID("alice"): 511, + validatorID("alice"): 0, validatorID("bob"): 20, }, // The consumer light client should be frozen From 451152a540f6bdf24d2614e3c7cd7d1a5e04be8f Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Fri, 4 Aug 2023 15:12:45 +0200 Subject: [PATCH 32/45] linter --- tests/integration/misbehaviour.go | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/integration/misbehaviour.go b/tests/integration/misbehaviour.go index e4ad997a13..26cfb6d3be 100644 --- a/tests/integration/misbehaviour.go +++ b/tests/integration/misbehaviour.go @@ -66,7 +66,6 @@ func (s *CCVTestSuite) TestHandleConsumerMisbehaviour() { s.Require().True(val.Jailed) s.Require().True(s.providerApp.GetTestSlashingKeeper().IsTombstoned(s.providerCtx(), provAddr.Address)) } - } func (s *CCVTestSuite) TestGetByzantineValidators() { From 8844518880238331f45347e5930a9675388baa21 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Fri, 4 Aug 2023 17:48:28 +0200 Subject: [PATCH 33/45] remove todo --- tests/integration/misbehaviour.go | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/integration/misbehaviour.go b/tests/integration/misbehaviour.go index 26cfb6d3be..33417a0618 100644 --- a/tests/integration/misbehaviour.go +++ b/tests/integration/misbehaviour.go @@ -12,7 +12,6 @@ import ( // TestHandleConsumerMisbehaviour tests that handling a valid misbehaviour, // with conflicting headers forming an equivocation, results in the jailing and tombstoning of the validators -// TODO: figure out how to signed headers with a subset a the validator set. func (s *CCVTestSuite) TestHandleConsumerMisbehaviour() { s.SetupCCVChannel(s.path) // required to have the consumer client revision height greater than 0 From 43dc212e3124b3ced5da6bf4f5fd9a428ddfd914 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Fri, 11 Aug 2023 11:17:45 +0200 Subject: [PATCH 34/45] Feat: avoid race condition in ICS misbehaviour handling (#1148) * remove unwanted changes * fix hermes config with assigned key * revert unwanted changes * revert local setup * remove log file * typo * update doc * update ICS misbehaviour test * update ICS misbehaviour test * revert mixed commits * update ICS misbehaviour test * update ICS misbehaviour test * Add test for MsgSubmitConsumerMisbehaviour parsing * fix linter * save progress * add CheckMisbehaviourAndUpdateState * update integration tests * typo * remove e2e tests from another PRs * cleaning' * Update x/ccv/provider/keeper/misbehaviour.go Co-authored-by: Anca Zamfir * Update x/ccv/provider/keeper/misbehaviour.go Co-authored-by: Anca Zamfir * update integration tests * save * save * nits * remove todo * lint * Update x/ccv/provider/keeper/misbehaviour.go --------- Co-authored-by: Anca Zamfir Co-authored-by: Marius Poke --- tests/e2e/steps_consumer_misbehaviour.go | 5 +- tests/integration/misbehaviour.go | 174 +++++++++++++++++++++++ testutil/integration/debug_test.go | 4 + x/ccv/provider/client/cli/tx.go | 8 +- x/ccv/provider/keeper/misbehaviour.go | 36 ++++- x/ccv/provider/types/codec.go | 5 + x/ccv/provider/types/msg.go | 6 +- 7 files changed, 227 insertions(+), 11 deletions(-) diff --git a/tests/e2e/steps_consumer_misbehaviour.go b/tests/e2e/steps_consumer_misbehaviour.go index d41e0bd2c0..7d7fda94b1 100644 --- a/tests/e2e/steps_consumer_misbehaviour.go +++ b/tests/e2e/steps_consumer_misbehaviour.go @@ -233,12 +233,11 @@ func stepsCauseConsumerMisbehaviour(consumerName string) []Step { validatorID("alice"): 0, validatorID("bob"): 20, }, - // The consumer light client should be frozen - // TODO: to be changed when merging PR #1148 + // The consumer light client should not be frozen ClientsFrozenHeights: &map[string]clienttypes.Height{ "07-tendermint-0": { RevisionNumber: 0, - RevisionHeight: 1, + RevisionHeight: 0, }, }, }, diff --git a/tests/integration/misbehaviour.go b/tests/integration/misbehaviour.go index 33417a0618..c3b590d5c1 100644 --- a/tests/integration/misbehaviour.go +++ b/tests/integration/misbehaviour.go @@ -218,3 +218,177 @@ func (s *CCVTestSuite) TestGetByzantineValidators() { }) } } + +func (s *CCVTestSuite) TestCheckMisbehaviour() { + s.SetupCCVChannel(s.path) + // required to have the consumer client revision height greater than 0 + s.SendEmptyVSCPacket() + + // create signing info for all validators + for _, v := range s.providerChain.Vals.Validators { + s.setDefaultValSigningInfo(*v) + } + + // create a new header timestamp + headerTs := s.providerCtx().BlockTime().Add(time.Minute) + + // get trusted validators and height + clientHeight := s.consumerChain.LastHeader.TrustedHeight + clientTMValset := tmtypes.NewValidatorSet(s.consumerChain.Vals.Validators) + clientSigners := s.consumerChain.Signers + + // create an alternative validator set using more than 1/3 of the trusted validator set + altValset := tmtypes.NewValidatorSet(s.consumerChain.Vals.Validators[0:2]) + altSigners := make(map[string]tmtypes.PrivValidator, 1) + altSigners[clientTMValset.Validators[0].Address.String()] = clientSigners[clientTMValset.Validators[0].Address.String()] + altSigners[clientTMValset.Validators[1].Address.String()] = clientSigners[clientTMValset.Validators[1].Address.String()] + testCases := []struct { + name string + misbehaviour *ibctmtypes.Misbehaviour + expPass bool + }{ + { + "client state not found - shouldn't pass", + &ibctmtypes.Misbehaviour{ + ClientId: "clientID", + Header1: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + headerTs, + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + Header2: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + headerTs, + altValset, + altValset, + clientTMValset, + altSigners, + ), + }, + false, + }, + { + "invalid misbehaviour with empty header1 - shouldn't pass", + &ibctmtypes.Misbehaviour{ + Header1: &ibctmtypes.Header{}, + Header2: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + headerTs, + altValset, + altValset, + clientTMValset, + altSigners, + ), + }, + false, + }, + { + "invalid misbehaviour with different header height - shouldn't pass", + &ibctmtypes.Misbehaviour{ + ClientId: s.path.EndpointA.ClientID, + Header1: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + headerTs, + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + Header2: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+2), + clientHeight, + headerTs, + altValset, + altValset, + clientTMValset, + altSigners, + ), + }, + false, + }, + { + "valid misbehaviour - should pass", + &ibctmtypes.Misbehaviour{ + ClientId: s.path.EndpointA.ClientID, + Header1: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + headerTs, + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + // create header using a different validator set + Header2: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + headerTs, + altValset, + altValset, + clientTMValset, + altSigners, + ), + }, + true, + }, + { + "valid misbehaviour with already frozen client - should pass", + &ibctmtypes.Misbehaviour{ + ClientId: s.path.EndpointA.ClientID, + Header1: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + headerTs, + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + // the resulting Header2 will have a different BlockID + // than Header1 since doesn't share the same valset and signers + Header2: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + headerTs, + altValset, + altValset, + clientTMValset, + altSigners, + ), + }, + true, + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + err := s.providerApp.GetProviderKeeper().CheckMisbehaviour(s.providerCtx(), *tc.misbehaviour) + cs, ok := s.providerApp.GetIBCKeeper().ClientKeeper.GetClientState(s.providerCtx(), s.path.EndpointA.ClientID) + s.Require().True(ok) + // verify that the client wasn't frozen + s.Require().Zero(cs.(*ibctmtypes.ClientState).FrozenHeight) + if tc.expPass { + s.NoError(err) + } else { + s.Error(err) + } + }) + } +} diff --git a/testutil/integration/debug_test.go b/testutil/integration/debug_test.go index 6715d6368e..2d9421300b 100644 --- a/testutil/integration/debug_test.go +++ b/testutil/integration/debug_test.go @@ -268,3 +268,7 @@ func TestHandleConsumerMisbehaviour(t *testing.T) { func TestGetByzantineValidators(t *testing.T) { runCCVTestByName(t, "TestGetByzantineValidators") } + +func TestCheckMisbehaviour(t *testing.T) { + runCCVTestByName(t, "TestCheckMisbehaviour") +} diff --git a/x/ccv/provider/client/cli/tx.go b/x/ccv/provider/client/cli/tx.go index 491e1e618f..22dbd6b864 100644 --- a/x/ccv/provider/client/cli/tx.go +++ b/x/ccv/provider/client/cli/tx.go @@ -10,9 +10,9 @@ import ( "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/tx" "github.com/cosmos/cosmos-sdk/version" + ibctmtypes "github.com/cosmos/ibc-go/v4/modules/light-clients/07-tendermint/types" sdk "github.com/cosmos/cosmos-sdk/types" - ibctmtypes "github.com/cosmos/ibc-go/v4/modules/light-clients/07-tendermint/types" "github.com/cosmos/interchain-security/v2/x/ccv/provider/types" ) @@ -125,12 +125,12 @@ Examples: WithTxConfig(clientCtx.TxConfig).WithAccountRetriever(clientCtx.AccountRetriever) submitter := clientCtx.GetFromAddress() - var misbehavior ibctmtypes.Misbehaviour - if err := clientCtx.Codec.UnmarshalInterfaceJSON([]byte(args[1]), &misbehavior); err != nil { + var misbehaviour ibctmtypes.Misbehaviour + if err := clientCtx.Codec.UnmarshalInterfaceJSON([]byte(args[1]), &misbehaviour); err != nil { return err } - msg, err := types.NewMsgSubmitConsumerMisbehaviour(submitter, &misbehavior) + msg, err := types.NewMsgSubmitConsumerMisbehaviour(submitter, &misbehaviour) if err != nil { return err } diff --git a/x/ccv/provider/keeper/misbehaviour.go b/x/ccv/provider/keeper/misbehaviour.go index 844a5523ea..c44c1d46dc 100644 --- a/x/ccv/provider/keeper/misbehaviour.go +++ b/x/ccv/provider/keeper/misbehaviour.go @@ -6,7 +6,9 @@ import ( "github.com/cosmos/interchain-security/v2/x/ccv/provider/types" sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" + ibcclienttypes "github.com/cosmos/ibc-go/v4/modules/core/02-client/types" ibctmtypes "github.com/cosmos/ibc-go/v4/modules/light-clients/07-tendermint/types" tmtypes "github.com/tendermint/tendermint/types" ) @@ -17,7 +19,7 @@ func (k Keeper) HandleConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmty logger := k.Logger(ctx) // Check that the misbehaviour is valid and that the client isn't expired - if err := k.clientKeeper.CheckMisbehaviourAndUpdateState(ctx, &misbehaviour); err != nil { + if err := k.CheckMisbehaviour(ctx, misbehaviour); err != nil { logger.Info("Misbehaviour rejected", err.Error()) return err @@ -129,3 +131,35 @@ func headerToLightBlock(h ibctmtypes.Header) (*tmtypes.LightBlock, error) { ValidatorSet: vs, }, nil } + +// CheckMisbehaviour checks that headers in the given misbehaviour forms +// a valid light client attack and that the corresponding light client isn't expired +func (k Keeper) CheckMisbehaviour(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) error { + if err := misbehaviour.ValidateBasic(); err != nil { + return err + } + + clientState, found := k.clientKeeper.GetClientState(ctx, misbehaviour.GetClientID()) + if !found { + return sdkerrors.Wrapf(ibcclienttypes.ErrClientNotFound, "cannot check misbehaviour for client with ID %s", misbehaviour.GetClientID()) + } + + clientStore := k.clientKeeper.ClientStore(ctx, misbehaviour.GetClientID()) + + // Check that the headers are at the same height to ensure that + // the misbehaviour is for a light client attack and not a time violation, + // see ibc-go/modules/light-clients/07-tendermint/types/misbehaviour_handle.go#L54 + if !misbehaviour.Header1.GetHeight().EQ(misbehaviour.Header2.GetHeight()) { + return sdkerrors.Wrap(ibcclienttypes.ErrInvalidMisbehaviour, "headers are not at same height") + } + + // CheckMisbehaviourAndUpdateState verifies the misbehaviour against the consensus states + // but does NOT update the light client status. + // Note CheckMisbehaviourAndUpdateState returns an error if the light client is expired + _, err := clientState.CheckMisbehaviourAndUpdateState(ctx, k.cdc, clientStore, &misbehaviour) + if err != nil { + return err + } + + return nil +} diff --git a/x/ccv/provider/types/codec.go b/x/ccv/provider/types/codec.go index 0ef3c2d296..2f28b398f5 100644 --- a/x/ccv/provider/types/codec.go +++ b/x/ccv/provider/types/codec.go @@ -6,6 +6,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/msgservice" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + "github.com/cosmos/ibc-go/v4/modules/core/exported" ) // RegisterLegacyAminoCodec registers the necessary x/ibc transfer interfaces and concrete types @@ -40,6 +41,10 @@ func RegisterInterfaces(registry codectypes.InterfaceRegistry) { (*sdk.Msg)(nil), &MsgSubmitConsumerMisbehaviour{}, ) + registry.RegisterInterface( + "ibc.core.client.v1.Misbehaviour", + (*exported.Misbehaviour)(nil), + ) msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) } diff --git a/x/ccv/provider/types/msg.go b/x/ccv/provider/types/msg.go index 9496122342..5c217f53b8 100644 --- a/x/ccv/provider/types/msg.go +++ b/x/ccv/provider/types/msg.go @@ -6,7 +6,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - ibctmtypes "github.com/cosmos/ibc-go/v4/modules/light-clients/07-tendermint/types" ) @@ -147,8 +146,8 @@ func (msg MsgRegisterConsumerRewardDenom) ValidateBasic() error { return nil } -func NewMsgSubmitConsumerMisbehaviour(submitter sdk.AccAddress, m *ibctmtypes.Misbehaviour) (*MsgSubmitConsumerMisbehaviour, error) { - return &MsgSubmitConsumerMisbehaviour{Submitter: submitter.String(), Misbehaviour: m}, nil +func NewMsgSubmitConsumerMisbehaviour(submitter sdk.AccAddress, misbehaviour *ibctmtypes.Misbehaviour) (*MsgSubmitConsumerMisbehaviour, error) { + return &MsgSubmitConsumerMisbehaviour{Submitter: submitter.String(), Misbehaviour: misbehaviour}, nil } // Route implements the sdk.Msg interface. @@ -164,6 +163,7 @@ func (msg MsgSubmitConsumerMisbehaviour) ValidateBasic() error { if msg.Submitter == "" { return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, msg.Submitter) } + if err := msg.Misbehaviour.ValidateBasic(); err != nil { return err } From a67093a5d4f42dc27afbbf0756e4b4ff21154811 Mon Sep 17 00:00:00 2001 From: Marius Poke Date: Fri, 11 Aug 2023 19:27:21 +0200 Subject: [PATCH 35/45] Update x/ccv/provider/client/cli/tx.go Co-authored-by: Anca Zamfir --- x/ccv/provider/client/cli/tx.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/ccv/provider/client/cli/tx.go b/x/ccv/provider/client/cli/tx.go index 22dbd6b864..a7e3ae000f 100644 --- a/x/ccv/provider/client/cli/tx.go +++ b/x/ccv/provider/client/cli/tx.go @@ -105,7 +105,7 @@ $ %s tx provider register-consumer-reward-denom untrn --from mykey func NewSubmitConsumerMisbehaviourCmd() *cobra.Command { cmd := &cobra.Command{ Use: "submit-consumer-misbehaviour [misbehaviour]", - Short: "submit a IBC misbehaviour for a consumer chain", + Short: "submit an IBC misbehaviour for a consumer chain", Long: strings.TrimSpace( fmt.Sprintf(`Submit a IBC misbehaviour detected on a consumer chain. A IBC misbehaviour contains two conflicting IBC client headers, which are used to form a light client attack evidence. From d2b82ea53c7b0b5ec60aef1ef8b32f08013256ef Mon Sep 17 00:00:00 2001 From: Marius Poke Date: Fri, 11 Aug 2023 19:27:35 +0200 Subject: [PATCH 36/45] Update x/ccv/provider/client/cli/tx.go Co-authored-by: Anca Zamfir --- x/ccv/provider/client/cli/tx.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x/ccv/provider/client/cli/tx.go b/x/ccv/provider/client/cli/tx.go index a7e3ae000f..1cad8b38ce 100644 --- a/x/ccv/provider/client/cli/tx.go +++ b/x/ccv/provider/client/cli/tx.go @@ -107,8 +107,8 @@ func NewSubmitConsumerMisbehaviourCmd() *cobra.Command { Use: "submit-consumer-misbehaviour [misbehaviour]", Short: "submit an IBC misbehaviour for a consumer chain", Long: strings.TrimSpace( - fmt.Sprintf(`Submit a IBC misbehaviour detected on a consumer chain. -A IBC misbehaviour contains two conflicting IBC client headers, which are used to form a light client attack evidence. + fmt.Sprintf(`Submit an IBC misbehaviour detected on a consumer chain. +An IBC misbehaviour contains two conflicting IBC client headers, which are used to form a light client attack evidence. The misbehaviour type definition can be found in the IBC client messages, see ibc-go/proto/ibc/core/client/v1/tx.proto. Examples: From 5d2842ef04defcf5a8798c3ae36d87e205a415f5 Mon Sep 17 00:00:00 2001 From: mpoke Date: Mon, 14 Aug 2023 21:58:45 +0200 Subject: [PATCH 37/45] add attributes to EventTypeSubmitConsumerMisbehaviour --- x/ccv/provider/keeper/msg_server.go | 3 +++ x/ccv/types/events.go | 7 +++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/x/ccv/provider/keeper/msg_server.go b/x/ccv/provider/keeper/msg_server.go index ca22b66f0d..80f325cc1b 100644 --- a/x/ccv/provider/keeper/msg_server.go +++ b/x/ccv/provider/keeper/msg_server.go @@ -138,6 +138,9 @@ func (k msgServer) SubmitConsumerMisbehaviour(goCtx context.Context, msg *types. ccvtypes.EventTypeSubmitConsumerMisbehaviour, sdk.NewAttribute(ccvtypes.AttributeConsumerMisbehaviour, msg.Misbehaviour.String()), sdk.NewAttribute(ccvtypes.AttributeSubmitterAddress, msg.Submitter), + sdk.NewAttribute(ccvtypes.AttributeMisbehaviourClientId, msg.Misbehaviour.ClientId), + sdk.NewAttribute(ccvtypes.AttributeMisbehaviourHeight1, msg.Misbehaviour.Header1.GetHeight().String()), + sdk.NewAttribute(ccvtypes.AttributeMisbehaviourHeight2, msg.Misbehaviour.Header2.GetHeight().String()), ), }) diff --git a/x/ccv/types/events.go b/x/ccv/types/events.go index 99b1e05d15..df91333fa0 100644 --- a/x/ccv/types/events.go +++ b/x/ccv/types/events.go @@ -33,8 +33,11 @@ const ( AttributeUnbondingPeriod = "unbonding_period" AttributeProviderValidatorAddress = "provider_validator_address" AttributeConsumerConsensusPubKey = "consumer_consensus_pub_key" - AttributeSubmitterAddress = "provider_validator_address" - AttributeConsumerMisbehaviour = "consumer_consensus_pub_key" + AttributeSubmitterAddress = "submitter_address" + AttributeConsumerMisbehaviour = "consumer_misbehaviour" + AttributeMisbehaviourClientId = "misbehaviour_client_id" + AttributeMisbehaviourHeight1 = "misbehaviour_height_1" + AttributeMisbehaviourHeight2 = "misbehaviour_height_2" AttributeDistributionCurrentHeight = "current_distribution_height" AttributeDistributionNextHeight = "next_distribution_height" From 2af9f4a4f82fde2964e775a7e6f62cff892b4ee1 Mon Sep 17 00:00:00 2001 From: Marius Poke Date: Mon, 14 Aug 2023 22:03:53 +0200 Subject: [PATCH 38/45] Update x/ccv/provider/keeper/misbehaviour.go Co-authored-by: Anca Zamfir --- x/ccv/provider/keeper/misbehaviour.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/ccv/provider/keeper/misbehaviour.go b/x/ccv/provider/keeper/misbehaviour.go index c44c1d46dc..35e8080536 100644 --- a/x/ccv/provider/keeper/misbehaviour.go +++ b/x/ccv/provider/keeper/misbehaviour.go @@ -18,7 +18,7 @@ import ( func (k Keeper) HandleConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) error { logger := k.Logger(ctx) - // Check that the misbehaviour is valid and that the client isn't expired + // Check that the misbehaviour is valid and that the client consensus states at trusted heights are within trusting period if err := k.CheckMisbehaviour(ctx, misbehaviour); err != nil { logger.Info("Misbehaviour rejected", err.Error()) From 098435694527b485a6e405f3f509319265fbdec8 Mon Sep 17 00:00:00 2001 From: Marius Poke Date: Mon, 14 Aug 2023 22:07:27 +0200 Subject: [PATCH 39/45] Update x/ccv/provider/keeper/misbehaviour.go Co-authored-by: Anca Zamfir --- x/ccv/provider/keeper/misbehaviour.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/ccv/provider/keeper/misbehaviour.go b/x/ccv/provider/keeper/misbehaviour.go index 35e8080536..b6c5ccb930 100644 --- a/x/ccv/provider/keeper/misbehaviour.go +++ b/x/ccv/provider/keeper/misbehaviour.go @@ -26,7 +26,7 @@ func (k Keeper) HandleConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmty } // Since the misbehaviour packet was received within the trusting period - // w.r.t to the last trusted consensus it entails that the infraction age + // w.r.t to the trusted consensus states the infraction age // isn't too old. see ibc-go/modules/light-clients/07-tendermint/types/misbehaviour_handle.go // Get Byzantine validators from the conflicting headers From 496139b350509f3280c7037e4c68534313096883 Mon Sep 17 00:00:00 2001 From: mpoke Date: Mon, 14 Aug 2023 22:12:26 +0200 Subject: [PATCH 40/45] apply review suggestions --- x/ccv/provider/keeper/misbehaviour.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/x/ccv/provider/keeper/misbehaviour.go b/x/ccv/provider/keeper/misbehaviour.go index b6c5ccb930..2645118a08 100644 --- a/x/ccv/provider/keeper/misbehaviour.go +++ b/x/ccv/provider/keeper/misbehaviour.go @@ -75,11 +75,11 @@ func (k Keeper) HandleConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmty // with the condition that it corresponds to an equivocation light client attack func (k Keeper) GetByzantineValidators(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) ([]*tmtypes.Validator, error) { // construct the trusted and conflicted light blocks - header1, err := headerToLightBlock(*misbehaviour.Header1) + lightBlock1, err := headerToLightBlock(*misbehaviour.Header1) if err != nil { return nil, err } - header2, err := headerToLightBlock(*misbehaviour.Header2) + lightBlock2, err := headerToLightBlock(*misbehaviour.Header2) if err != nil { return nil, err } @@ -91,7 +91,7 @@ func (k Keeper) GetByzantineValidators(ctx sdk.Context, misbehaviour ibctmtypes. // create a map with the validators' address that signed header1 header1Signers := map[string]struct{}{} - for _, sign := range header1.Commit.Signatures { + for _, sign := range lightBlock1.Commit.Signatures { if sign.Absent() { continue } @@ -100,12 +100,12 @@ func (k Keeper) GetByzantineValidators(ctx sdk.Context, misbehaviour ibctmtypes. // iterate over the header2 signers // and check if they s signed header1 - for _, sign := range header2.Commit.Signatures { + for _, sign := range lightBlock2.Commit.Signatures { if sign.Absent() { continue } if _, ok := header1Signers[sign.ValidatorAddress.String()]; ok { - _, val := header1.ValidatorSet.GetByAddress(sign.ValidatorAddress) + _, val := lightBlock1.ValidatorSet.GetByAddress(sign.ValidatorAddress) validators = append(validators, val) } } From 2a0fe5388a458d01af6f1a5ea5c56bd5a7584766 Mon Sep 17 00:00:00 2001 From: mpoke Date: Mon, 14 Aug 2023 22:17:16 +0200 Subject: [PATCH 41/45] fix docstring --- x/ccv/provider/keeper/misbehaviour.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/x/ccv/provider/keeper/misbehaviour.go b/x/ccv/provider/keeper/misbehaviour.go index 2645118a08..7f26a9f8fe 100644 --- a/x/ccv/provider/keeper/misbehaviour.go +++ b/x/ccv/provider/keeper/misbehaviour.go @@ -30,7 +30,6 @@ func (k Keeper) HandleConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmty // isn't too old. see ibc-go/modules/light-clients/07-tendermint/types/misbehaviour_handle.go // Get Byzantine validators from the conflicting headers - // Note that it returns an error if the misbehaviour doesn't correspond to an equivocation byzantineValidators, err := k.GetByzantineValidators(ctx, misbehaviour) if err != nil { return err @@ -98,8 +97,7 @@ func (k Keeper) GetByzantineValidators(ctx sdk.Context, misbehaviour ibctmtypes. header1Signers[sign.ValidatorAddress.String()] = struct{}{} } - // iterate over the header2 signers - // and check if they s signed header1 + // iterate over the header2 signers and check if they signed header1 for _, sign := range lightBlock2.Commit.Signatures { if sign.Absent() { continue From 6f74563245b29e38e7358805918328c8bcfb592e Mon Sep 17 00:00:00 2001 From: Marius Poke Date: Mon, 14 Aug 2023 22:18:53 +0200 Subject: [PATCH 42/45] Update x/ccv/provider/keeper/misbehaviour.go Co-authored-by: Anca Zamfir --- x/ccv/provider/keeper/misbehaviour.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/x/ccv/provider/keeper/misbehaviour.go b/x/ccv/provider/keeper/misbehaviour.go index 7f26a9f8fe..7d283fe62f 100644 --- a/x/ccv/provider/keeper/misbehaviour.go +++ b/x/ccv/provider/keeper/misbehaviour.go @@ -151,9 +151,9 @@ func (k Keeper) CheckMisbehaviour(ctx sdk.Context, misbehaviour ibctmtypes.Misbe return sdkerrors.Wrap(ibcclienttypes.ErrInvalidMisbehaviour, "headers are not at same height") } - // CheckMisbehaviourAndUpdateState verifies the misbehaviour against the consensus states - // but does NOT update the light client status. - // Note CheckMisbehaviourAndUpdateState returns an error if the light client is expired + // CheckMisbehaviourAndUpdateState verifies the misbehaviour against the trusted consensus states + // but does NOT update the light client state. + // Note CheckMisbehaviourAndUpdateState returns an error if the trusted consensus states are expired _, err := clientState.CheckMisbehaviourAndUpdateState(ctx, k.cdc, clientStore, &misbehaviour) if err != nil { return err From e5f0cd0f4614a512aca152e2c2bbff462c365365 Mon Sep 17 00:00:00 2001 From: mpoke Date: Mon, 14 Aug 2023 22:23:35 +0200 Subject: [PATCH 43/45] fix link --- x/ccv/provider/keeper/misbehaviour.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/ccv/provider/keeper/misbehaviour.go b/x/ccv/provider/keeper/misbehaviour.go index 7d283fe62f..bb7855c8d6 100644 --- a/x/ccv/provider/keeper/misbehaviour.go +++ b/x/ccv/provider/keeper/misbehaviour.go @@ -146,7 +146,7 @@ func (k Keeper) CheckMisbehaviour(ctx sdk.Context, misbehaviour ibctmtypes.Misbe // Check that the headers are at the same height to ensure that // the misbehaviour is for a light client attack and not a time violation, - // see ibc-go/modules/light-clients/07-tendermint/types/misbehaviour_handle.go#L54 + // see https://github.com/cosmos/ibc-go/blob/8f53c21361f9d65448a850c2eafcf3ab3c384a61/modules/light-clients/07-tendermint/types/misbehaviour_handle.go#L56 if !misbehaviour.Header1.GetHeight().EQ(misbehaviour.Header2.GetHeight()) { return sdkerrors.Wrap(ibcclienttypes.ErrInvalidMisbehaviour, "headers are not at same height") } From 984f4141f69e4ee97ccaf42f54a74acb44a1e995 Mon Sep 17 00:00:00 2001 From: mpoke Date: Mon, 14 Aug 2023 22:47:06 +0200 Subject: [PATCH 44/45] apply review suggestions --- x/ccv/provider/keeper/misbehaviour.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/x/ccv/provider/keeper/misbehaviour.go b/x/ccv/provider/keeper/misbehaviour.go index bb7855c8d6..1ec3d75912 100644 --- a/x/ccv/provider/keeper/misbehaviour.go +++ b/x/ccv/provider/keeper/misbehaviour.go @@ -38,32 +38,32 @@ func (k Keeper) HandleConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmty // jail and tombstone the Byzantine validators for _, v := range byzantineValidators { // convert consumer consensus address - consuAddr := sdk.ConsAddress(v.Address.Bytes()) - provAddr := k.GetProviderAddrFromConsumerAddr(ctx, misbehaviour.Header1.Header.ChainID, types.NewConsumerConsAddress(consuAddr)) - k.stakingKeeper.ValidatorByConsAddr(ctx, consuAddr) - val, ok := k.stakingKeeper.GetValidatorByConsAddr(ctx, provAddr.Address) + consumerAddr := types.NewConsumerConsAddress(sdk.ConsAddress(v.Address.Bytes())) + providerAddr := k.GetProviderAddrFromConsumerAddr(ctx, misbehaviour.Header1.Header.ChainID, consumerAddr) + val, ok := k.stakingKeeper.GetValidatorByConsAddr(ctx, providerAddr.ToSdkConsAddr()) if !ok || val.IsUnbonded() { - logger.Error("validator not found or is unbonded", provAddr.String()) + logger.Error("validator not found or is unbonded", providerAddr.String()) continue } // jail validator if not already if !val.IsJailed() { - k.stakingKeeper.Jail(ctx, provAddr.Address) + k.stakingKeeper.Jail(ctx, providerAddr.ToSdkConsAddr()) } // tombstone validator if not already - if !k.slashingKeeper.IsTombstoned(ctx, provAddr.Address) { - k.slashingKeeper.Tombstone(ctx, provAddr.Address) + if !k.slashingKeeper.IsTombstoned(ctx, providerAddr.ToSdkConsAddr()) { + k.slashingKeeper.Tombstone(ctx, providerAddr.ToSdkConsAddr()) + k.Logger(ctx).Info("validator tombstoned", "provider cons addr", providerAddr.String()) } // update jail time to end after double sign jail duration - k.slashingKeeper.JailUntil(ctx, provAddr.Address, evidencetypes.DoubleSignJailEndTime) + k.slashingKeeper.JailUntil(ctx, providerAddr.ToSdkConsAddr(), evidencetypes.DoubleSignJailEndTime) } logger.Info( - "confirmed equivocation", + "confirmed equivocation light client attack", "byzantine validators", byzantineValidators, ) From 84e1bda18660773a2daf588d48474b0cba3ee0fc Mon Sep 17 00:00:00 2001 From: mpoke Date: Mon, 14 Aug 2023 22:52:10 +0200 Subject: [PATCH 45/45] update docstring --- x/ccv/provider/keeper/misbehaviour.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/x/ccv/provider/keeper/misbehaviour.go b/x/ccv/provider/keeper/misbehaviour.go index 1ec3d75912..4d8f517e95 100644 --- a/x/ccv/provider/keeper/misbehaviour.go +++ b/x/ccv/provider/keeper/misbehaviour.go @@ -70,8 +70,9 @@ func (k Keeper) HandleConsumerMisbehaviour(ctx sdk.Context, misbehaviour ibctmty return nil } -// GetByzantineValidators returns the Byzantine validators from a given misbehaviour -// with the condition that it corresponds to an equivocation light client attack +// GetByzantineValidators returns the validators that signed both headers. +// If the misbehavior is an equivocation light client attack, then these +// validators are the Byzantine validators. func (k Keeper) GetByzantineValidators(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) ([]*tmtypes.Validator, error) { // construct the trusted and conflicted light blocks lightBlock1, err := headerToLightBlock(*misbehaviour.Header1)