From 5591849cf038828f2eb0b4b2cccf5565e5321532 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Nov 2023 09:20:08 +0100 Subject: [PATCH 01/19] build(deps): bump cosmossdk.io/math from 1.1.2 to 1.2.0 (#1414) Bumps [cosmossdk.io/math](https://github.com/cosmos/cosmos-sdk) from 1.1.2 to 1.2.0. - [Release notes](https://github.com/cosmos/cosmos-sdk/releases) - [Changelog](https://github.com/cosmos/cosmos-sdk/blob/main/CHANGELOG.md) - [Commits](https://github.com/cosmos/cosmos-sdk/compare/math/v1.1.2...log/v1.2.0) --- updated-dependencies: - dependency-name: cosmossdk.io/math dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index a915a727b1..6381587dc8 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.20 require ( cosmossdk.io/errors v1.0.0 - cosmossdk.io/math v1.1.2 + cosmossdk.io/math v1.2.0 github.com/cometbft/cometbft v0.37.2 github.com/cometbft/cometbft-db v0.8.0 github.com/cosmos/cosmos-sdk v0.47.5 diff --git a/go.sum b/go.sum index b7c59ff2cd..d4bce198d6 100644 --- a/go.sum +++ b/go.sum @@ -197,8 +197,8 @@ cosmossdk.io/errors v1.0.0 h1:nxF07lmlBbB8NKQhtJ+sJm6ef5uV1XkvPXG2bUntb04= cosmossdk.io/errors v1.0.0/go.mod h1:+hJZLuhdDE0pYN8HkOrVNwrIOYvUGnn6+4fjnJs/oV0= cosmossdk.io/log v1.2.1 h1:Xc1GgTCicniwmMiKwDxUjO4eLhPxoVdI9vtMW8Ti/uk= cosmossdk.io/log v1.2.1/go.mod h1:GNSCc/6+DhFIj1aLn/j7Id7PaO8DzNylUZoOYBL9+I4= -cosmossdk.io/math v1.1.2 h1:ORZetZCTyWkI5GlZ6CZS28fMHi83ZYf+A2vVnHNzZBM= -cosmossdk.io/math v1.1.2/go.mod h1:l2Gnda87F0su8a/7FEKJfFdJrM0JZRXQaohlgJeyQh0= +cosmossdk.io/math v1.2.0 h1:8gudhTkkD3NxOP2YyyJIYYmt6dQ55ZfJkDOaxXpy7Ig= +cosmossdk.io/math v1.2.0/go.mod h1:l2Gnda87F0su8a/7FEKJfFdJrM0JZRXQaohlgJeyQh0= cosmossdk.io/tools/rosetta v0.2.1 h1:ddOMatOH+pbxWbrGJKRAawdBkPYLfKXutK9IETnjYxw= cosmossdk.io/tools/rosetta v0.2.1/go.mod h1:Pqdc1FdvkNV3LcNIkYWt2RQY6IP1ge6YWZk8MhhO9Hw= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= From b78a8cbe101018e25c99356f5233de9981b4fab5 Mon Sep 17 00:00:00 2001 From: Marius Poke Date: Tue, 14 Nov 2023 10:04:27 +0100 Subject: [PATCH 02/19] docs: adding missing unreleased changes (will go in v4) (#1420) adding missing unreleased changes (will go in v4) --- .../unreleased/dependencies/1373-bump-ibc.md | 3 +++ .../unreleased/features/1336-quint-model.md | 2 ++ .../improvements/1324-consumer-genesis.md | 16 ++++++++++++++++ .../improvements/1350-cleanup-types.md | 3 +++ .../state-breaking/1324-consumer-genesis.md | 2 ++ 5 files changed, 26 insertions(+) create mode 100644 .changelog/unreleased/dependencies/1373-bump-ibc.md create mode 100644 .changelog/unreleased/features/1336-quint-model.md create mode 100644 .changelog/unreleased/improvements/1324-consumer-genesis.md create mode 100644 .changelog/unreleased/improvements/1350-cleanup-types.md create mode 100644 .changelog/unreleased/state-breaking/1324-consumer-genesis.md diff --git a/.changelog/unreleased/dependencies/1373-bump-ibc.md b/.changelog/unreleased/dependencies/1373-bump-ibc.md new file mode 100644 index 0000000000..efe4e0c286 --- /dev/null +++ b/.changelog/unreleased/dependencies/1373-bump-ibc.md @@ -0,0 +1,3 @@ +- Bump [ibc-go](https://github.com/cosmos/ibc-go) to + [v7.3.1](https://github.com/cosmos/ibc-go/releases/tag/v7.3.1). + ([\#1373](https://github.com/cosmos/interchain-security/pull/1373)) \ No newline at end of file diff --git a/.changelog/unreleased/features/1336-quint-model.md b/.changelog/unreleased/features/1336-quint-model.md new file mode 100644 index 0000000000..96c4562b32 --- /dev/null +++ b/.changelog/unreleased/features/1336-quint-model.md @@ -0,0 +1,2 @@ +- Add Quint model of Replicated Security. + ([\#1336](https://github.com/cosmos/interchain-security/pull/1336)) \ No newline at end of file diff --git a/.changelog/unreleased/improvements/1324-consumer-genesis.md b/.changelog/unreleased/improvements/1324-consumer-genesis.md new file mode 100644 index 0000000000..a727be8341 --- /dev/null +++ b/.changelog/unreleased/improvements/1324-consumer-genesis.md @@ -0,0 +1,16 @@ +- Split out consumer genesis state to reduce shared data between provider and + consumer. ([\#1324](https://github.com/cosmos/interchain-security/pull/1324)) + - Note: This breaks json format used by augmenting Genesis files of consumer + chains with consumer genesis content exported from provider chain. Consumer + Genesis content exported from a provider chain using major version 1, 2 or 3 + of the provider module needs to be transformed with the transformation command + introduced by this PR: + ``` + Transform the consumer genesis file from a provider version v1, v2 or v3 to a version supported by this consumer. Result is printed to STDOUT. + + Example: + $ transform /path/to/ccv_consumer_genesis.json + + Usage: + interchain-security-cd genesis transform [genesis-file] [flags] + ``` \ No newline at end of file diff --git a/.changelog/unreleased/improvements/1350-cleanup-types.md b/.changelog/unreleased/improvements/1350-cleanup-types.md new file mode 100644 index 0000000000..6e26fc3992 --- /dev/null +++ b/.changelog/unreleased/improvements/1350-cleanup-types.md @@ -0,0 +1,3 @@ +- Refactor shared events, codecs and errors assign to + consumer and provider dedicated types where possible. + ([\#1350](https://github.com/cosmos/interchain-security/pull/1350)) \ No newline at end of file diff --git a/.changelog/unreleased/state-breaking/1324-consumer-genesis.md b/.changelog/unreleased/state-breaking/1324-consumer-genesis.md new file mode 100644 index 0000000000..b47f7199fd --- /dev/null +++ b/.changelog/unreleased/state-breaking/1324-consumer-genesis.md @@ -0,0 +1,2 @@ +- Split out consumer genesis state to reduce shared data between provider and + consumer. ([\#1324](https://github.com/cosmos/interchain-security/pull/1324)) \ No newline at end of file From a246ffdac09b2b90299296701aa717ae96703263 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Nov 2023 10:26:08 +0100 Subject: [PATCH 03/19] build(deps): bump postcss from 8.4.21 to 8.4.31 in /docs (#1344) Bumps [postcss](https://github.com/postcss/postcss) from 8.4.21 to 8.4.31. - [Release notes](https://github.com/postcss/postcss/releases) - [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/postcss/postcss/compare/8.4.21...8.4.31) --- updated-dependencies: - dependency-name: postcss dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs/package-lock.json | 40 +++++++++++++++++++++++++--------------- docs/package.json | 2 +- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/docs/package-lock.json b/docs/package-lock.json index aaeed671cd..8e7d1877c5 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -16,7 +16,7 @@ "@you54f/theme-github-codeblock": "^0.1.1", "autoprefixer": "^10.4.13", "clsx": "^1.2.1", - "postcss": "^8.4.21", + "postcss": "^8.4.31", "postcss-import": "^15.1.0", "prism-react-renderer": "^1.3.5", "react": "^17.0.2", @@ -8085,9 +8085,15 @@ } }, "node_modules/nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -8657,9 +8663,9 @@ } }, "node_modules/postcss": { - "version": "8.4.21", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", - "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "funding": [ { "type": "opencollective", @@ -8668,10 +8674,14 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "dependencies": { - "nanoid": "^3.3.4", + "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" }, @@ -18630,9 +18640,9 @@ } }, "nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==" + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==" }, "negotiator": { "version": "0.6.3", @@ -19035,11 +19045,11 @@ } }, "postcss": { - "version": "8.4.21", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", - "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "requires": { - "nanoid": "^3.3.4", + "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } diff --git a/docs/package.json b/docs/package.json index a2b516536d..0df776c7d6 100644 --- a/docs/package.json +++ b/docs/package.json @@ -22,7 +22,7 @@ "@you54f/theme-github-codeblock": "^0.1.1", "autoprefixer": "^10.4.13", "clsx": "^1.2.1", - "postcss": "^8.4.21", + "postcss": "^8.4.31", "postcss-import": "^15.1.0", "prism-react-renderer": "^1.3.5", "react": "^17.0.2", From c9fa4c192f4c91361dda4cbe1ef5efaa9826c692 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Nov 2023 11:00:29 +0100 Subject: [PATCH 04/19] build(deps): bump @babel/traverse from 7.21.2 to 7.23.2 in /docs (#1365) Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.21.2 to 7.23.2. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.23.2/packages/babel-traverse) --- updated-dependencies: - dependency-name: "@babel/traverse" dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs/package-lock.json | 361 +++++++++++++++++++++++++++-------------- 1 file changed, 239 insertions(+), 122 deletions(-) diff --git a/docs/package-lock.json b/docs/package-lock.json index 8e7d1877c5..1e1cb22f1b 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -204,16 +204,81 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "dependencies": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@babel/code-frame/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/code-frame/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/code-frame/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/@babel/compat-data": { "version": "7.21.0", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.0.tgz", @@ -260,11 +325,11 @@ } }, "node_modules/@babel/generator": { - "version": "7.21.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.1.tgz", - "integrity": "sha512-1lT45bAYlQhFn/BHivJs43AiW2rg3/UbLyShGfF3C0KmHvO5fSghWd5kBJy30kpRRucGzXStvnnCFniCR2kXAA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "dependencies": { - "@babel/types": "^7.21.0", + "@babel/types": "^7.23.0", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -396,9 +461,9 @@ } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "engines": { "node": ">=6.9.0" } @@ -415,23 +480,23 @@ } }, "node_modules/@babel/helper-function-name": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", - "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dependencies": { - "@babel/template": "^7.20.7", - "@babel/types": "^7.21.0" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -552,28 +617,28 @@ } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", - "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "engines": { "node": ">=6.9.0" } @@ -614,12 +679,12 @@ } }, "node_modules/@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "dependencies": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, "engines": { @@ -691,9 +756,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.21.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.2.tgz", - "integrity": "sha512-URpaIJQwEkEC2T9Kn+Ai6Xe/02iNaVCuT/PtoRz3GPVJVDpPd7mLo+VddTbhCRU9TXqW5mSrQfXZyi8kDKOVpQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", "bin": { "parser": "bin/babel-parser.js" }, @@ -1938,31 +2003,31 @@ } }, "node_modules/@babel/template": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", - "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.21.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.2.tgz", - "integrity": "sha512-ts5FFU/dSUPS13tv8XiEObDu9K+iagEKME9kAbaP7r0Y9KtZJZ+NGndDvWoRAYNpeWafbpFeki3q9QoMD6gxyw==", - "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.21.1", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.21.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.21.2", - "@babel/types": "^7.21.2", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -1971,12 +2036,12 @@ } }, "node_modules/@babel/types": { - "version": "7.21.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.2.tgz", - "integrity": "sha512-3wRZSs7jiFaB8AjxiiD+VqN5DTG2iRvJGQ+qYFrs/654lg6kGTQWIOFjlBo5RaXuAZjBmP3+OQH4dmhqiiyYxw==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "dependencies": { - "@babel/helper-string-parser": "^7.19.4", - "@babel/helper-validator-identifier": "^7.19.1", + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, "engines": { @@ -12945,11 +13010,63 @@ } }, "@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "requires": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } } }, "@babel/compat-data": { @@ -12987,11 +13104,11 @@ } }, "@babel/generator": { - "version": "7.21.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.1.tgz", - "integrity": "sha512-1lT45bAYlQhFn/BHivJs43AiW2rg3/UbLyShGfF3C0KmHvO5fSghWd5kBJy30kpRRucGzXStvnnCFniCR2kXAA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "requires": { - "@babel/types": "^7.21.0", + "@babel/types": "^7.23.0", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -13090,9 +13207,9 @@ } }, "@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==" + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==" }, "@babel/helper-explode-assignable-expression": { "version": "7.18.6", @@ -13103,20 +13220,20 @@ } }, "@babel/helper-function-name": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", - "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "requires": { - "@babel/template": "^7.20.7", - "@babel/types": "^7.21.0" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" } }, "@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "requires": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" } }, "@babel/helper-member-expression-to-functions": { @@ -13204,22 +13321,22 @@ } }, "@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "requires": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" } }, "@babel/helper-string-parser": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", - "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==" + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==" }, "@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==" }, "@babel/helper-validator-option": { "version": "7.21.0", @@ -13248,12 +13365,12 @@ } }, "@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "requires": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, "dependencies": { @@ -13309,9 +13426,9 @@ } }, "@babel/parser": { - "version": "7.21.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.2.tgz", - "integrity": "sha512-URpaIJQwEkEC2T9Kn+Ai6Xe/02iNaVCuT/PtoRz3GPVJVDpPd7mLo+VddTbhCRU9TXqW5mSrQfXZyi8kDKOVpQ==" + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==" }, "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { "version": "7.18.6", @@ -14116,39 +14233,39 @@ } }, "@babel/template": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", - "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" } }, "@babel/traverse": { - "version": "7.21.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.2.tgz", - "integrity": "sha512-ts5FFU/dSUPS13tv8XiEObDu9K+iagEKME9kAbaP7r0Y9KtZJZ+NGndDvWoRAYNpeWafbpFeki3q9QoMD6gxyw==", - "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.21.1", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.21.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.21.2", - "@babel/types": "^7.21.2", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "requires": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.21.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.2.tgz", - "integrity": "sha512-3wRZSs7jiFaB8AjxiiD+VqN5DTG2iRvJGQ+qYFrs/654lg6kGTQWIOFjlBo5RaXuAZjBmP3+OQH4dmhqiiyYxw==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "requires": { - "@babel/helper-string-parser": "^7.19.4", - "@babel/helper-validator-identifier": "^7.19.1", + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" } }, From 83dc3107b31da562b2deb1be79178b5271b75d5e Mon Sep 17 00:00:00 2001 From: MSalopek Date: Wed, 15 Nov 2023 10:47:42 +0100 Subject: [PATCH 05/19] refactor: make GetAllValidators a no-op (#1428) --- x/ccv/consumer/keeper/keeper.go | 15 --------------- x/ccv/consumer/keeper/validators.go | 6 ++++++ 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/x/ccv/consumer/keeper/keeper.go b/x/ccv/consumer/keeper/keeper.go index 0d0ac988ad..27076ac938 100644 --- a/x/ccv/consumer/keeper/keeper.go +++ b/x/ccv/consumer/keeper/keeper.go @@ -578,21 +578,6 @@ func (k Keeper) GetAllCCValidator(ctx sdk.Context) (validators []types.CrossChai return validators } -// Implement from stakingkeeper interface -func (k Keeper) GetAllValidators(ctx sdk.Context) (validators []stakingtypes.Validator) { - store := ctx.KVStore(k.storeKey) - - iterator := sdk.KVStorePrefixIterator(store, stakingtypes.ValidatorsKey) - defer iterator.Close() - - for ; iterator.Valid(); iterator.Next() { - validator := stakingtypes.MustUnmarshalValidator(k.cdc, iterator.Value()) - validators = append(validators, validator) - } - - return validators -} - // getAndIncrementPendingPacketsIdx returns the current pending packets index and increments it. // This index is used for implementing a FIFO queue of pending packets in the KV store. func (k Keeper) getAndIncrementPendingPacketsIdx(ctx sdk.Context) (toReturn uint64) { diff --git a/x/ccv/consumer/keeper/validators.go b/x/ccv/consumer/keeper/validators.go index 2233b22d28..c3c9638b4d 100644 --- a/x/ccv/consumer/keeper/validators.go +++ b/x/ccv/consumer/keeper/validators.go @@ -312,3 +312,9 @@ func (k Keeper) MustGetCurrentValidatorsAsABCIUpdates(ctx sdk.Context) []abci.Va func (k Keeper) ApplyAndReturnValidatorSetUpdates(sdk.Context) (updates []abci.ValidatorUpdate, err error) { return } + +// GetAllValidators is needed to implement StakingKeeper as expected by the Slashing module since cosmos-sdk/v0.47.x. +// Use GetAllCCValidator in places where access to all cross-chain validators is needed. +func (k Keeper) GetAllValidators(ctx sdk.Context) []stakingtypes.Validator { + return []stakingtypes.Validator{} +} From f6fd2306448e04a690d22674236d5cf774c0d335 Mon Sep 17 00:00:00 2001 From: Marius Poke Date: Wed, 15 Nov 2023 10:47:56 +0100 Subject: [PATCH 06/19] docs: adding details of why the ADR 012 was rejected (#1430) adding details of why the ADR was rejected --- docs/docs/adrs/adr-012-separate-releasing.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/docs/adrs/adr-012-separate-releasing.md b/docs/docs/adrs/adr-012-separate-releasing.md index 8b65a25638..fbc4b94198 100644 --- a/docs/docs/adrs/adr-012-separate-releasing.md +++ b/docs/docs/adrs/adr-012-separate-releasing.md @@ -65,7 +65,11 @@ We upgrade `main` to use a new version of SDK. This is a major version bump, tri ### Negative * ~~Slightly more complexity.~~Considerably more complex to manage the ICS library. -* This solution does not allow having provider and consumer on separate versions of e.g. the Cosmos SDK + This is because ICS needs to support multiple versions of SDK (e.g., 0.45, 0.47, 0.50). + In addition, ICS needs to support a special fork of SDK (with LSM included) for the Cosmos Hub. + This means that instead of focusing on main the development team needs to manage multiple release + branches with different dependency trees. +* This solution does not allow having provider and consumer on separate versions of e.g. the Cosmos SDK. ### Neutral From d044f21737896a43f8301acf054790f829a3bb97 Mon Sep 17 00:00:00 2001 From: Marius Poke Date: Wed, 15 Nov 2023 17:06:41 +0100 Subject: [PATCH 07/19] docs: adding some info on ICS processes (#1429) * moving docs/old/testing.md to TESTING.md * adding release process * adding info on ICS releases * updating contributing guidelines * apply review suggestions * Update RELEASES.md Co-authored-by: insumity * Update RELEASES.md Co-authored-by: insumity * adding note about editor for unclog * add some context on breaking changes * adding context on state-compatibility * Update STATE-COMPATIBILITY.md Co-authored-by: insumity --------- Co-authored-by: insumity --- CONTRIBUTING.md | 72 ++++++++-- README.md | 2 +- RELEASES.md | 97 ++++++++++++++ RELEASE_PROCESS.md | 119 +++++++++++++++++ STATE-COMPATIBILITY.md | 212 ++++++++++++++++++++++++++++++ docs/old/testing.md => TESTING.md | 0 6 files changed, 487 insertions(+), 15 deletions(-) create mode 100644 RELEASES.md create mode 100644 RELEASE_PROCESS.md create mode 100644 STATE-COMPATIBILITY.md rename docs/old/testing.md => TESTING.md (100%) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8975d37269..90456c842f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,11 +14,10 @@ - [Pull Request Templates](#pull-request-templates) - [Requesting Reviews](#requesting-reviews) - [Updating Documentation](#updating-documentation) + - [Changelog](#changelog) - [Dependencies](#dependencies) - [Protobuf](#protobuf) - [Branching Model and Release](#branching-model-and-release) - - [Semantic Versioning](#semantic-versioning) - - [Backwards Compatibility](#backwards-compatibility) - [PR Targeting](#pr-targeting) Thank you for considering making contributions to the Interchain Security (ICS) repository! 🎉👍 @@ -216,6 +215,63 @@ items. In addition, use the following review explanations: If you open a PR in ICS, it is mandatory to update the relevant documentation in `/docs`. +### Changelog + +To manage and generate our changelog, we currently use [unclog](https://github.com/informalsystems/unclog). + +Every PR with types `fix`, `feat`, `deps`, and `refactor` should include a file +`.changelog/unreleased/${section}/[${component}/]${pr-number}-${short-description}.md`, +where: + +- `section` is one of + `dependencies`, `improvements`, `features`, `bug-fixes`, `state-breaking`, `api-breaking`, + and _**if multiple apply, create multiple files**_, + not necessarily with the same `short-description` or content; +- `pr-number` is the PR number; +- `short-description` is a short (4 to 6 word), hyphen separated description of the change; +- `component` is used for changes that affect one of the components defined in the [config](.changelog/config.toml), e.g., `provider`, `consumer`. + +For examples, see the [.changelog](.changelog) folder. + +Use `unclog` to add a changelog entry in `.changelog` (check the [requirements](https://github.com/informalsystems/unclog#requirements) first): +```bash +# add a general entry +unclog add \ + -i "${pr-number}-${short-description}" \ + -p "${pr-number}" \ + -s "${section}" \ + -m "${description}" \ + +# add a entry to a component +unclog add + -i "${pr-number}-${short-description}" \ + -p "${pr-number}" \ + -c "${component}" \ + -s "${section}" \ + -m "${description}" \ +``` +where `${description}` is a detailed description of the changelog entry. + +For example, +```bash +# add an entry for bumping IBC to v7.2.0 +unclog add -i "1196-bump-ibc" -p 1196 -s dependencies -m "Bump [ibc-go](https://github.com/cosmos/ibc-go) to [v7.2.0](https://github.com/cosmos/ibc-go/releases/tag/v7.2.0)" + +# add an entry for changing the consumer module; +# note that the entry is added to both state-breaking and features sections +unclog add -i "1024-jail-throttling-v2" -p 1024 -c consumer -s state-breaking -m "Add the consumer-side changes for jail throttling with retries (cf. ADR 008)." +unclog add -i "1024-jail-throttling-v2" -p 1024 -c consumer -s features -m "Add the consumer-side changes for jail throttling with retries (cf. ADR 008)." +``` + +**Note:** `unclog add` requires an editor. This can be set either by configuring +an `$EDITOR` environment variable or by manually specify an editor binary path +via the `--editor` flag. + +**Note:** Changelog entries should answer the question: "what is important about this +change for users to know?" or "what problem does this solve for users?". It +should not simply be a reiteration of the title of the associated PR, unless the +title of the PR _very_ clearly explains the benefit of a change to a user. + ## Dependencies We use [Go Modules](https://github.com/golang/go/wiki/Modules) to manage @@ -243,18 +299,6 @@ To generate the protobuf stubs, you can run `make proto-gen`. ICS adheres to the [trunk based development branching model](https://trunkbaseddevelopment.com/). User branches should start with a user name, example: `{moniker}/{issue#}-branch-name`. -### Semantic Versioning - -ICS follows [semantic versioning](https://semver.org), but with the following deviations (similar to [IBC-Go](https://github.com/cosmos/ibc-go/blob/main/RELEASES.md)): - -- A library API breaking change will result in an increase of the MAJOR version number (X.y.z | x > 0). -- A state breaking change (change requiring coordinated upgrade and/or state migration for the consumer, the provider, or both) will result in an increase of the MINOR version number (x.Y.z | x > 0). -- Any other changes (including node API breaking changes) will result in an increase of the PATCH version number (x.y.Z | x > 0). - -### Backwards Compatibility - -A MAJOR version of ICS will always be backwards compatible with the previous MAJOR version of ICS. Versions before that are not supported. For example, a provider chain could run ICS at version 3.4.5, and would be compatible with consumers running ICS at 2.0.0, 2.1.2, 3.2.1, but not 1.2.7. - ### PR Targeting Ensure that you base and target your PRs on either `main` or a feature branch. diff --git a/README.md b/README.md index bb6da8be76..0b9ea0e5c9 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ Inspect the [Makefile](./Makefile) if curious. ## Testing -See [testing docs](./docs/old/testing.md). +See [testing docs](./TESTING.md). ## Learn more diff --git a/RELEASES.md b/RELEASES.md new file mode 100644 index 0000000000..92662b3318 --- /dev/null +++ b/RELEASES.md @@ -0,0 +1,97 @@ +# Releases + +- [Releases](#releases) + - [Semantic Versioning](#semantic-versioning) + - [Breaking Changes](#breaking-changes) + - [Release Cycle](#release-cycle) + - [Stable Release Policy](#stable-release-policy) + - [Version Matrix](#version-matrix) + - [Backwards Compatibility](#backwards-compatibility) + +## Semantic Versioning + +Interchain Security (ICS) follows [semantic versioning](https://semver.org), but with the following deviations (similar to [IBC-Go](https://github.com/cosmos/ibc-go/blob/main/RELEASES.md)): + +- A library API breaking change will result in an increase of the MAJOR version number (X.y.z | X > 0). +- A state-machine breaking change will result in an increase of the MINOR version number (x.Y.z | x > 0). +- Any other changes (including node API breaking changes) will result in an increase of the PATCH version number (x.y.Z | x > 0). + +### Breaking Changes + +A change is considered to be ***library API breaking*** if it modifies the integration of ICS on either consumer or provider chains (i.e., it changes the way ICS is used as a library). +Note that bumping the major version of [Cosmos SDK](https://github.com/cosmos/cosmos-sdk) or [IBC](https://github.com/cosmos/ibc-go) will be considered as a library API breaking change. + +A change is considered to be ***state-machine breaking*** if it requires a coordinated upgrade and/or state migration for either consumer or provider chains in order to preserve [state compatibility](./STATE-COMPATIBILITY.md). +Note that when bumping the dependencies of [Cosmos SDK](https://github.com/cosmos/cosmos-sdk) and [IBC](https://github.com/cosmos/ibc-go) we will only treat patch releases as non state-machine breaking. + +A change is considered to be ***node API breaking*** if it modifies the API provided by a node of either consumer or provider chains. +This includes events, queries, CLI interfaces. + +## Release Cycle + +ICS follows a traditional release cycle involving release candidates (RCs) releases before finalizing a new version. +The stable release guarantees do not go into affect until a final release is performed. + +❗***It is never advisable to use a non-final release in production.*** + +Final releases should contain little to no changes in comparison to the latest RC. + +A release should not be finalized until the development team and the external community have done sufficient integration tests on the targeted release. + +## Stable Release Policy + +The beginning of a new major release series is marked by the release of a new major version. +A major release series is comprised of all minor and patch releases made under the same major version number. +The series continues to receive bug fixes (released as minor or patch releases) until it reaches end of life. +The date when a major release series reaches end of life is determined by one of the following methods: + +- If there is no known chain using a major release series, then it reached end of life. + This is a temporary policy that is possible due to the relatively low number of consumer chains. +- If the next major release is made within the first 6 months, then the end of + life date of the major release series is 1 year after its initial release. +- If the next major release is made 6 months after the initial release, then the + end of life date of the major release series is 6 months after the release date + of the next major release. + +Only the following major release series have a stable release status. +All missing minor release versions have been discontinued. + +| Release | End of Life Date | +|---------|------------------| +| `v1.2.x` | February 21, 2024 | +| `v2.0.x` | June 09, 2024 | +| `v2.1.x-provider-lsm` | June 09, 2024 | +| `v2.3.x-provider-lsm` | June 09, 2024 | +| `v3.1.x` | July 10, 2024 | +| `v3.2.x` | July 10, 2024 | + +**Note**: As of [Gaia v12.0.0](https://github.com/cosmos/gaia/releases/tag/v12.0.0), +the Cosmos Hub uses a fork of Cosmos SDK ([v0.45.16-ics-lsm](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.45.16-ics-lsm)) +that contains the Liquid Staking Module (LSM). +This means the Cosmos Hub requires a fork of ICS. +This fork is maintained by the development team and released using the `-lsm` prefix. +As soon as the Cosmos Hub uses mainline Cosmos SDK, the `-lsm` releases will reach end of life. + +## Version Matrix + +Versions of Golang, IBC, Cosmos SDK and CometBFT used by ICS in the currently active releases: + +| ICS | Golang | IBC | Cosmos SDK | CometBFT | Note | +|-----|--------|-----|------------|----------|------| +| [v1.2.0-multiden](https://github.com/cosmos/interchain-security/releases/tag/v1.2.0-multiden) | 1.18 | v4.2.0 | v0.45.15-ics | v0.34.27 | Consumer only | +| [v2.0.0](https://github.com/cosmos/interchain-security/releases/tag/v2.0.0) | 1.19 | v4.4.2 | v0.45.15-ics | v0.34.28 | +| [v2.1.0-provider-lsm](https://github.com/cosmos/interchain-security/releases/tag/v2.1.0-provider-lsm) | 1.19 | v4.4.2 | v0.45.16-ics-lsm | v0.34.28 | Provider only (Cosmos Hub specific) | +| [v2.3.0-provider-lsm](https://github.com/cosmos/interchain-security/releases/tag/v2.3.0-provider-lsm) | 1.19 | v4.4.2 | v0.45.16-ics-lsm | v0.34.28 | Provider only (Cosmos Hub specific) | +| [v3.1.0](https://github.com/cosmos/interchain-security/releases/tag/v3.1.0) | 1.20 | v7.1.0 | v0.47.3 | v0.37.2 | + +### Backwards Compatibility + +A MAJOR version of ICS will always be backwards compatible with the previous MAJOR version of ICS. + +The following table indicates the compatibility of currently active releases: + +| consumer | provider | `v2.0.0` | `v2.1.0-provider-lsm` | `v2.3.0-provider-lsm` | `v3.1.0` | +|----------|----------|--------:|----------------------:|----------------------:|---------:| +| `v1.2.0-multiden` || ✅ | ✅ | ✅ | ✅ | +| `v2.0.0` || ✅ | ✅ | ✅ | ✅ | +| `v3.1.0` || ✅ | ✅ | ✅ | ✅ | diff --git a/RELEASE_PROCESS.md b/RELEASE_PROCESS.md new file mode 100644 index 0000000000..ef0863ed4f --- /dev/null +++ b/RELEASE_PROCESS.md @@ -0,0 +1,119 @@ +# Release Process + +- [Release Process](#release-process) + - [Changelog](#changelog) + - [Creating a new release branch](#creating-a-new-release-branch) + - [Cutting a new release](#cutting-a-new-release) + - [Update the changelog on main](#update-the-changelog-on-main) + - [Tagging Procedure](#tagging-procedure) + + +This document outlines the release process for Interchain Security (ICS). + +For details on ICS releases, see [RELEASES.md](./RELEASES.md). + +## Changelog + +For PRs that are changing production code, please add a changelog entry in `.changelog` (for details, see [contributing guidelines](./CONTRIBUTING.md#changelog)). + +To manage and generate the changelog on ICS, we currently use [unclog](https://github.com/informalsystems/unclog). +Read the [README.md](https://github.com/informalsystems/unclog#readme) in the unclog repo for instruction on how to install and use unclog. + +### Creating a new release branch + +Unreleased changes are collected on `main` in `.changelog/unreleased/`. +However, `.changelog/` on `main` contains also existing releases (e.g., `v3.2.0`). +Thus, when creating a new release branch (e.g., `release/v3.3.x`), the following steps are necessary: + +- create a new release branch, e.g., `release/v3.3.x` + ```bash + git checkout main + git pull + git checkout -b release/v3.3.x + ``` +- delete all the sub-folders in `.changelog/` except `unreleased/` + ```bash + find ./.changelog -mindepth 1 -maxdepth 1 -type d -not -name unreleased | xargs rm -r + ``` +- replace the content of `.changelog/epilogue.md` with the following text + ```md + ## Previous Versions + + [CHANGELOG of previous versions](https://github.com/cosmos/interchain-security/blob/main/CHANGELOG.md) + ``` +- push the release branch upstream + ```bash + git push + ``` + +### Cutting a new release + +Before cutting a _**release candidate**_ (e.g., `v3.3.0-rc0`), the following steps are necessary: + +- move to the release branch, e.g., `release/v3.3.x` + ```bash + git checkout release/v3.3.x + ``` +- move all entries in ".changelog/unreleased" to the release version, e.g., `v3.3.0`, i.e., + ```bash + unclog release v3.3.0 + ``` +- update `CHANGELOG.md`, i.e., + ```bash + unclog build > CHANGELOG.md + ``` +- open a PR (from this new created branch) against the release branch, e.g., `release/v3.3.x` + +Now you can cut the release candidate, e.g., v3.3.0-rc0 (follow the [Tagging Procedure](#tagging-procedure)). + +### Update the changelog on main + +Once the **final release** is cut, the new changelog section must be added to main: + +- checkout a new branch from the `main` branch, i.e., + ```bash + git checkout main + git pull + git checkout -b /backport_changelog + ``` +- bring the new changelog section from the release branch into this branch, e.g., + ```bash + git checkout release/v3.3.x .changelog/v3.3.0 + ``` +- remove duplicate entries that are both in `.changelog/unreleased/` and the new changelog section, e.g., `.changelog/v3.3.0` +- update `CHANGELOG.md`, i.e., + ```bash + unclog build > CHANGELOG.md + ``` +- open a PR (from this new created branch) against `main` + +## Tagging Procedure + +**Important**: _**Always create tags from your local machine**_ since all release +tags should be [signed and annotated](https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits). + +The following steps are the default for tagging a specific branch commit using git +on your local machine. Usually, release branches are labeled `release/v*`: + +Ensure you have checked out the commit you wish to tag and then do: +```bash +git pull --tags +git tag -s v3.2.0 -m v3.2.0 +git push origin v3.2.0 +``` + +To re-create a tag: +```bash +# delete a tag locally +git tag -d v3.2.0 + +# push the deletion to the remote +git push --delete origin v3.2.0 + +# redo the tagging +git tag -s v3.2.0 -m v3.2.0 +git push origin v3.2.0 +``` + +For final releases, once the tag is created, use the GitHub interface to create a release. +Note that this is not necessary for release candidates. \ No newline at end of file diff --git a/STATE-COMPATIBILITY.md b/STATE-COMPATIBILITY.md new file mode 100644 index 0000000000..1819aaf981 --- /dev/null +++ b/STATE-COMPATIBILITY.md @@ -0,0 +1,212 @@ +# State-Compatibility + +- [State-Compatibility](#state-compatibility) + - [Scope](#scope) + - [Validating State-Compatibility](#validating-state-compatibility) + - [AppHash](#apphash) + - [LastResultsHash](#lastresultshash) + - [Major Sources of State-incompatibility](#major-sources-of-state-incompatibility) + - [Creating Additional State](#creating-additional-state) + - [Changing Proto Field Definitions](#changing-proto-field-definitions) + - [Returning Different Errors Given Same Input](#returning-different-errors-given-same-input) + - [Variability in Gas Usage](#variability-in-gas-usage) + - [Secondary Limitations To Keep In Mind](#secondary-limitations-to-keep-in-mind) + - [Network Requests to External Services](#network-requests-to-external-services) + - [Randomness](#randomness) + - [Parallelism and Shared State](#parallelism-and-shared-state) + - [Hardware Errors](#hardware-errors) + + +It is critical for the patch releases to be state-machine compatible with prior releases in the same minor version. +For example, v3.2.1 must be state-machine compatible with v3.2.0. + +This is to ensure **determinism**, i.e., given the same input, the nodes will always produce the same output. + +State-incompatibility is allowed for major upgrades because all nodes in either the consumer or provider networks +perform it at the same time. Therefore, after the upgrade, the nodes continue functioning in a deterministic way. + +## Scope + +The state-machine scope includes the following areas: + +- All ICS messages including: + - Every msg's ValidateBasic method + - Every msg's MsgServer method + - Net gas usage, in all execution paths + - Error result returned + - State changes (namely every store write) +- AnteHandlers in "DeliverTx" mode +- All `BeginBlock`/`EndBlock` logic + +The following are **NOT** in the state-machine scope: + +- Events +- Queries that are not whitelisted +- CLI interfaces + +## Validating State-Compatibility + +CometBFT ensures state compatibility by validating a number of hashes that can be found [here](https://github.com/cometbft/cometbft/blob/9f76e8da150414ce73eed2c4f248947b657c7587/proto/tendermint/types/types.proto#L70-L77). + +`AppHash` and `LastResultsHash` are the common sources of problems stemming from our work. +To avoid these problems, let's now examine how these hashes work. + +### AppHash + +**Note:** The following explanation is simplified for clarity. + +An app hash is a hash of hashes of every store's Merkle root that is returned by ABCI's `Commit()` from Cosmos-SDK to CometBFT. +Cosmos-SDK [takes an app hash of the application state](https://github.com/osmosis-labs/cosmos-sdk/blob/5c9a51c277d067e0ec5cf48df30a85fae95bcd14/store/rootmulti/store.go#L430), and propagates it to CometBFT which, in turn, compares it to the app hash of the rest of the network. +Then, CometBFT ensures that the app hash of the local node matches the app hash of the network. + +### LastResultsHash + +`LastResultsHash` is the root hash of all results from the transactions in the block returned by the ABCI's `DeliverTx`. + +The [`LastResultsHash`](https://github.com/cometbft/cometbft/blob/v0.34.29/types/results.go#L47-L54) +in CometBFT [v0.34.29](https://github.com/cometbft/cometbft/releases/tag/v0.34.29) contains: + +1. Tx `GasWanted` + +2. Tx `GasUsed` + > `GasUsed` being Merkelized means that we cannot freely reorder methods that consume gas. + > We should also be careful of modifying any validation logic since changing the + > locations where we error or pass might affect transaction gas usage. + > + > There are plans to remove this field from being Merkelized in a subsequent CometBFT release, + > at which point we will have more flexibility in reordering operations / erroring. + +3. Tx response `Data` + + > The `Data` field includes the proto marshalled Tx response. Therefore, we cannot + > change these in patch releases. + +4. Tx response `Code` + + > This is an error code that is returned by the transaction flow. In the case of + > success, it is `0`. On a general error, it is `1`. Additionally, each module + > defines its custom error codes. + > For example, `x/provider` currently has [these error codes](./x/ccv/provider/types/errors.go) defined. + > + > As a result, it is important to avoid changing custom error codes or change + > the semantics of what is valid logic in transaction flows. + +Note that all of the above stem from `DeliverTx` execution path, which handles: + +- `AnteHandler`'s marked as deliver tx +- `msg.ValidateBasic` +- execution of a message from the message server + +The `DeliverTx` return back to the CometBFT is defined [here](https://github.com/cosmos/cosmos-sdk/blob/d11196aad04e57812dbc5ac6248d35375e6603af/baseapp/abci.go#L293-L303). + +## Major Sources of State-incompatibility + +### Creating Additional State + +By erroneously creating database entries that exist in Version A but not in +Version B, we can cause the app hash to differ across nodes running +these versions in the network. Therefore, this must be avoided. + +### Changing Proto Field Definitions + +For example, if we change a field that gets persisted to the database, +the app hash will differ across nodes running these versions in the network. + +Additionally, this affects `LastResultsHash` because it contains a `Data` field that is a marshaled proto message. + +### Returning Different Errors Given Same Input + +```go +// Version A +func (sk Keeper) validateAmount(ctx context.Context, amount math.Int) error { + if amount.IsNegative() { + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "amount must be positive or zero") + } + return nil +} +``` + +```go +// Version B +func (sk Keeper) validateAmount(ctx context.Context, amount math.Int) error { + if amount.IsNegative() || amount.IsZero() { + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "amount must be positive") + } + return nil +} +``` + +Note that now an amount of 0 can be valid in "Version A", but invalid in "Version B". +Therefore, if some nodes are running "Version A" and others are running "Version B", +the final app hash might not be deterministic. + +Additionally, a different error message does not matter because it +is not included in any hash. However, an error code `sdkerrors.ErrInvalidRequest` does. +It translates to the `Code` field in the `LastResultsHash` and participates in +its validation. + +### Variability in Gas Usage + +For transaction flows (or any other flow that consumes gas), it is important +that the gas usage is deterministic. + +Currently, gas usage is being Merklized in the state. As a result, reordering functions +becomes risky. + +Suppose my gas limit is 2000 and 1600 is used up before entering +`someInternalMethod`. Consider the following: + +```go +func someInternalMethod(ctx sdk.Context) { + object1 := readOnlyFunction1(ctx) # consumes 1000 gas + object2 := readOnlyFunction2(ctx) # consumes 500 gas + doStuff(ctx, object1, object2) +} +``` + +- It will run out of gas with `gasUsed = 2600` where 2600 getting merkelized +into the tx results. + +```go +func someInternalMethod(ctx sdk.Context) { + object2 := readOnlyFunction2(ctx) # consumes 500 gas + object1 := readOnlyFunction1(ctx) # consumes 1000 gas + doStuff(ctx, object1, object2) +} +``` + +- It will run out of gas with `gasUsed = 2100` where 2100 is getting merkelized +into the tx results. + +Therefore, we introduced a state-incompatibility by merklezing diverging gas +usage. + +## Secondary Limitations To Keep In Mind + +### Network Requests to External Services + +It is critical to avoid performing network requests to external services +since it is common for services to be unavailable or rate-limit. + +Imagine a service that returns exchange rates when clients query its HTTP endpoint. +This service might experience downtime or be restricted in some geographical areas. + +As a result, nodes may get diverging responses where some +get successful responses while others errors, leading to state breakage. + +### Randomness + +Randomness cannot be used in the state machine, as the state machine must be deterministic. + +**Note:** Iteration order over `map`s is non-deterministic, so to be deterministic +you must gather the keys, and sort them all prior to iterating over all values. + +### Parallelism and Shared State + +Threads and Goroutines might preempt differently in different hardware. Therefore, +they should be avoided for the sake of determinism. Additionally, it is hard +to predict when the multi-threaded state can be updated. + +### Hardware Errors + +This is out of the developer's control but is mentioned for completeness. \ No newline at end of file diff --git a/docs/old/testing.md b/TESTING.md similarity index 100% rename from docs/old/testing.md rename to TESTING.md From ec2f48caf8b11102b43986e83100a17c9511cf08 Mon Sep 17 00:00:00 2001 From: Marius Poke Date: Fri, 17 Nov 2023 16:46:39 +0100 Subject: [PATCH 08/19] docs: update security policy (#1434) update security.md --- SECURITY.md | 93 +++++++++++++---------------------------------------- 1 file changed, 22 insertions(+), 71 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index 636d34e850..95714540c7 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,79 +1,30 @@ -# Coordinated Vulnerability Disclosure Policy +## How to Report a Security Bug -The Cosmos ecosystem believes that strong security is a blend of highly -technical security researchers who care about security and the forward -progression of the ecosystem and the attentiveness and openness of Cosmos core -contributors to help continually secure our operations. +If you believe you have found a security vulnerability in the Interchain Stack, +you can report it to our primary vulnerability disclosure channel, the +[Cosmos HackerOne Bug Bounty program](https://hackerone.com/cosmos?type=team). -> **IMPORTANT**: *DO NOT* open public issues on this repository for security -> vulnerabilities. +If you prefer to report an issue via email, you may send a bug report to +security@interchain.io with the issue details, reproduction, impact, and other +information. Please submit only one unique email thread per vulnerability. +Any issues reported via email are ineligible for bounty rewards. -## Scope +Artifacts from an email report are saved at the time the email is triaged. +Please note: our team is not able to monitor dynamic content (e.g. a Google +Docs link that is edited after receipt) throughout the lifecycle of a report. +If you would like to share additional information or modify previous +information, please include it in an additional reply as an additional attachment. -| Scope | -|-----------------------| -| last release (tagged) | -| main branch | +***Please DO NOT file a public issue in this repository to report a security vulnerability.*** -The latest **release tag** of this repository is supported for security updates -as well as the **main** branch. Security vulnerabilities should be reported if -the vulnerability can be reproduced on either one of those. -## Reporting a Vulnerability +## Coordinated Vulnerability Disclosure Policy and Safe Harbor -| Reporting methods | -|---------------------------------------------------------------| -| [GitHub Private Vulnerability Reporting][gh-private-advisory] | -| [HackerOne bug bounty program][h1] | +For the most up-to-date version of the policies that govern vulnerability +disclosure, please consult the [HackerOne program page](https://hackerone.com/cosmos?type=team&view_policy=true). -All security vulnerabilities can be reported under GitHub's [Private -vulnerability reporting][gh-private-advisory] system. This will open a private -issue for the developers. Try to fill in as much of the questions as possible. -If you are not familiar with the CVSS system for assessing vulnerabilities, just -use the Low/High/Critical severity ratings. A partially filled in report for a -critical vulnerability is still better than no report at all. - -Vulnerabilities associated with the **Go, Rust or Protobuf code** of the -repository may be eligible for a [bug bounty][h1]. Please see the bug bounty -page for more details on submissions and rewards. If you think the vulnerability -is eligible for a payout, **report on HackerOne first**. - -Vulnerabilities in services and their source codes (JavaScript, web page, Google -Workspace) are not in scope for the bug bounty program, but they are welcome to -be reported in GitHub. - -### Guidelines - -We require that all researchers: - -* Abide by this policy to disclose vulnerabilities, and avoid posting - vulnerability information in public places, including GitHub, Discord, - Telegram, and Twitter. -* Make every effort to avoid privacy violations, degradation of user experience, - disruption to production systems (including but not limited to the Cosmos - Hub), and destruction of data. -* Keep any information about vulnerabilities that you’ve discovered confidential - between yourself and the Cosmos engineering team until the issue has been - resolved and disclosed. -* Avoid posting personally identifiable information, privately or publicly. - -If you follow these guidelines when reporting an issue to us, we commit to: - -* Not pursue or support any legal action related to your research on this - vulnerability -* Work with you to understand, resolve and ultimately disclose the issue in a - timely fashion - -### More information - -* See [TIMELINE.md] for an example timeline of a disclosure. -* See [DISCLOSURE.md] to see more into the inner workings of the disclosure - process. -* See [EXAMPLES.md] for some of the examples that we are interested in for the - bug bounty program. - -[gh-private-advisory]: /../../security/advisories/new -[h1]: https://hackerone.com/cosmos -[TIMELINE.md]: https://github.com/cosmos/security/blob/main/TIMELINE.md -[DISCLOSURE.md]: https://github.com/cosmos/security/blob/main/DISCLOSURE.md -[EXAMPLES.md]: https://github.com/cosmos/security/blob/main/EXAMPLES.md +The policy hosted on HackerOne is the official Coordinated Vulnerability +Disclosure policy and Safe Harbor for the Interchain Stack, and the teams and +infrastructure it supports, and it supersedes previous security policies that +have been used in the past by individual teams and projects with targets in +scope of the program. From bf575e045df290f9c72b6bc16a43bbba7b19433d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Nov 2023 20:13:21 +0100 Subject: [PATCH 09/19] build(deps): bump bufbuild/buf-setup-action from 1.28.0 to 1.28.1 (#1438) Bumps [bufbuild/buf-setup-action](https://github.com/bufbuild/buf-setup-action) from 1.28.0 to 1.28.1. - [Release notes](https://github.com/bufbuild/buf-setup-action/releases) - [Commits](https://github.com/bufbuild/buf-setup-action/compare/v1.28.0...v1.28.1) --- updated-dependencies: - dependency-name: bufbuild/buf-setup-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/proto-registry.yml | 2 +- .github/workflows/proto.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/proto-registry.yml b/.github/workflows/proto-registry.yml index 783f4f5172..41d76f8e55 100644 --- a/.github/workflows/proto-registry.yml +++ b/.github/workflows/proto-registry.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: bufbuild/buf-setup-action@v1.28.0 + - uses: bufbuild/buf-setup-action@v1.28.1 - uses: bufbuild/buf-push-action@v1 with: input: "proto" diff --git a/.github/workflows/proto.yml b/.github/workflows/proto.yml index e57848eeca..f4188838c6 100644 --- a/.github/workflows/proto.yml +++ b/.github/workflows/proto.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: bufbuild/buf-setup-action@v1.28.0 + - uses: bufbuild/buf-setup-action@v1.28.1 - uses: bufbuild/buf-breaking-action@v1 with: input: "proto" From 81331b4415f880f3f3fa3c54e0a3f776efd8667c Mon Sep 17 00:00:00 2001 From: Marius Poke Date: Tue, 21 Nov 2023 10:15:57 +0100 Subject: [PATCH 10/19] docs: backport v2 changelog sections to main (#1433) backport v2 changelog sections to main --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9aef588734..35c926aab1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,17 @@ Interchain Security v3 uses SDK 0.47 and IBC 7. * `[x/ccv/provider]` (fix) [#977](https://github.com/cosmos/interchain-security/pull/977) Avoids panicking the provider when an unbonding delegation was removed through a `CancelUnbondingDelegation` message. * `[x/ccv/democracy]` (feat) [#1019](https://github.com/cosmos/interchain-security/pull/1019) Whitelisting non-legacy params in the "democracy module" require the entire module to be whitelisted. +## v2.3.0-provider-lsm + +*November 15, 2023* + +* (fix!) [#1422](https://github.com/cosmos/interchain-security/pull/1422) Fix the misbehaviour handling by verifying the signatures of byzantine validators. + +## v2.2.0-provider-lsm + +### Cryptographic verification of equivocation +* New feature enabling the provider chain to verify equivocation evidence on its own instead of trusting consumer chains, see [EPIC](https://github.com/cosmos/interchain-security/issues/732). + ## v2.1.0-provider-lsm Date: September 15th, 2023 From 1e8512a0b62d364b3d62615413cadfbc0aed33c4 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Tue, 21 Nov 2023 16:37:31 +0100 Subject: [PATCH 11/19] feat!: add cryptographic equivocation (#1340) * docs: cleanup changelog for v2.0.0 on release/v2.0.x (#987) * Update CHANGELOG.md * Update CHANGELOG.md * comment * chore: Hardcode golangci-lint version (backport #990) (#1013) chore: Hardcode golangci-lint version (#990) * Hardcode golangci-lint version * Hardcode version in CI config (cherry picked from commit 9920121274605ee5bf79984a687d7b4586fdec71) Co-authored-by: Philip Offtermatt <57488781+p-offtermatt@users.noreply.github.com> * fix: proper consumer key prefix ordering (backport #991) (#1011) fix: proper consumer key prefix ordering (#991) * Update keys.go * tests * fix another bug * fix comments (cherry picked from commit a1e18d0cfe4cfc105e6bbb0730471075ee1edc28) Co-authored-by: Shawn <44221603+smarshall-spitzbart@users.noreply.github.com> * feat: Remove consumer genesis migration on provider (backport #997) (#1012) feat: Remove consumer genesis migration on provider (#997) * Update keys.go * tests * fix another bug * remove consumer genesis deletion, link to test * remove unused bond denom method * Revert "remove unused bond denom method" This reverts commit f930eca428bade49a05368fe5dae2d96842659cc. * remove test too * update changelog (cherry picked from commit e2ac9743e9a309b29e6e92ab64be2e4e79503f75) Co-authored-by: Shawn <44221603+smarshall-spitzbart@users.noreply.github.com> * fix: limit vsc matured packets handled per endblocker (backport #1004) (#1015) fix: limit vsc matured packets handled per endblocker (#1004) * initial implementation, still need tests * UTs * integration test * linter * Update CHANGELOG.md * make vsc matured handled this block a var * comment (cherry picked from commit 8c2fc562dedecee7799ac69bd72734c37a356638) Co-authored-by: Shawn <44221603+smarshall-spitzbart@users.noreply.github.com> * feat: integrate cometmock (backport #989) (#1030) feat: integrate cometmock (#989) * Add gorelayer and CometMock to Dockerfile * Add option to start with cometmock in start-chain script * Start adding support for rly * Adjust relayer start action * Add entrypoint for short happy path steps * Add . nosec G204 and waiting for blocks * Adjust rly config: Gas is free * Remove optout steps from short happy path * Use separate redelegate step for short happy path * Wait for blocks after unbonding * Make naming more descriptive and add comments * Add comment to chain name sorting and improve comments * Update start-chain.sh Address comments form joint review session with @MSalopek * Fix typo (cherry picked from commit 07be71aa678a0ad204c62a6eb6033a526c0bef27) Co-authored-by: Philip Offtermatt <57488781+p-offtermatt@users.noreply.github.com> * feat!: Add DistributionTransmissionChannel to ConsumerAdditionProposal (manual backport #965) (#1031) feat!: Add DistributionTransmissionChannel to ConsumerAdditionProposal (#965) * update proto * remove transfer_channel_id from consumer genesis * ConsumerAdditionProposal: transfer_channel_id -> distribution_transmission_channel * send updated ConsumerAdditionProposal * validate consumer genesis param * remove StandaloneTransferChannelID from store * fix TestOnChanOpenAck * remove state breaking change * finalize merge and fix issues * chore: update docs and changelog * chore: regenerate protos * re-add integrationt tests around changeover * mv entry in changelog * test: add sovereign to consumer changeover e2e (#1025) * tests: add sovereign to consumer e2e test * rm unused bash scripts * partially address review comments * apply remaining review comments * chore: apply formatting rules --------- Co-authored-by: Marius Poke Co-authored-by: MSalopek * refactor: log when constructing IBC err ack (backport #1090) (#1094) refactor: log when constructing IBC err ack (#1090) * log with err ack * linter (cherry picked from commit 07302ffb6692fe849f5f5d393de5db6395e74a53) Co-authored-by: Shawn <44221603+smarshall-spitzbart@users.noreply.github.com> * fix: `AttributeDistributionTotal` in event emit (backport #1097) (#1114) * fix: `AttributeDistributionTotal` in event emit (#1097) * fix: emitted distribution events * docs: update changelog * fix: lint --------- Co-authored-by: MSalopek (cherry picked from commit d16c7669b4f23380a50c8dedfaf6d25f50ca0edd) # Conflicts: # CHANGELOG.md # x/ccv/consumer/keeper/distribution.go * resolve conflicts * docs: remove v3 changelog from v2 release line --------- Co-authored-by: yaruwangway <69694322+yaruwangway@users.noreply.github.com> Co-authored-by: Yaru Wang * docs: update broken md links (backport #1130) (#1142) * docs: update broken md links (#1130) (cherry picked from commit fd76f45b726f4ef65f817e8032c4f87f986f2d71) # Conflicts: # docs/docs/validators/joining-testnet.md * Update joining-testnet.md --------- Co-authored-by: MSalopek Co-authored-by: Shawn <44221603+smarshall-spitzbart@users.noreply.github.com> * feat!: add ICS misbehaviour handling (#826) * define msg to submit misbehaviour to provider implement msg handling logic e2e test msg handling logic * wip: get byzantine validators in misbehavioiur handling * add tx handler * format HandleConsumerMisbehaviour * add tx handler * add debugging stuff * Add misbehaviour handler * create message for consumer double voting evidence * add DRAFT double vote handler * Add cli cmd for submit consumer double voting * Add double-vote handler * add last update * fix jailing * pass first jailing integration test * format tests * doc * save * update e2e tests' * fix typo and improve docs * remove unwanted tm evidence protofile * fix typos * update submit-consumer-misbehaviour cli description * check that header1 and header2 have the same TrustedValidators * 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 * update to handle only equivocations * improve doc * update doc * update E2E tests comment * optimize signatures check * doc * update e2e tests * linter * remove todo * 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 * Update x/ccv/provider/client/cli/tx.go Co-authored-by: Anca Zamfir * Update x/ccv/provider/client/cli/tx.go Co-authored-by: Anca Zamfir * add attributes to EventTypeSubmitConsumerMisbehaviour * Update x/ccv/provider/keeper/misbehaviour.go Co-authored-by: Anca Zamfir * Update x/ccv/provider/keeper/misbehaviour.go Co-authored-by: Anca Zamfir * apply review suggestions * fix docstring * Update x/ccv/provider/keeper/misbehaviour.go Co-authored-by: Anca Zamfir * fix link * apply review suggestions * update docstring --------- Co-authored-by: Anca Zamfir Co-authored-by: Marius Poke * deps!: support cosmos-sdk-v45-ics-lsm integration (#1120) * tests: check cosmos-sdk-v45-ics-lsm integration * bump cosmos-sdk to inqlusion latest * support new method signatuers * fix: bump v0.45.16-ics-lsm to latest [fixes difftests] * fix: update democracy tests representative registration * chore: bump iqlusion:cosmos-sdk to latest * chore: bump iqlusion:cosmos-sdk to latest * chore: bump iqlusion:cosmos-sdk to latest * fix!: avoid panicking on CancelUnbondingDelegation (#977) * handle CancelUnbondingDelegation message * tests: add cancel-unbond e2e test * fix: appease linter * chore: Hardcode golangci-lint version (#990) * Hardcode golangci-lint version * Hardcode version in CI config --------- Co-authored-by: MSalopek Co-authored-by: Philip Offtermatt <57488781+p-offtermatt@users.noreply.github.com> * tests: fix broken cancelUnbond e2e after cherry pick * chore: regenerate mocks * chore: run make proto-gen * fix: complete concel-unbond handling in hooks * chore: bump iqlusion:cosmos-sdk to latest * chore: fix brokene gov prop submit * chore: fix brokene gov prop submit * use SDK 0.45.16-ics-lsm-rc0 * add changelog entry --------- Co-authored-by: Marius Poke Co-authored-by: Philip Offtermatt <57488781+p-offtermatt@users.noreply.github.com> * docs: add v2.0.0-lsm changelog section (#1210) add changelog section * deps: bump SDK to v0.45.16-ics-lsm (#1212) bump SDK to v0.45.16-ics-lsm * feat: improve ICS misbehaviour E2E testing coverage (#1225) * update e2e tests * update the chain halt assertion * refactor: address comments of ICS Misbehaviour PRs #826 and #1148 (#1223) * remove interface * improve comment * update godoc * address last comments * feat: add handler for consumer double voting (#1232) * create new endpoint for consumer double voting * add first draft handling logic * first iteration of double voting * draft first mem test * error handling * refactor * add unit test of double voting verification * remove evidence age checks * document * doc * protogen * reformat double voting handling * logger nit * nits * check evidence age duration * move verify double voting evidence to ut * fix nit * nits * fix e2e tests * improve double vote testing coverage * remove TODO * lint * add UT for JailAndTombstoneValidator * nits * nits * remove tombstoning and evidence age check * lint * typo * improve godoc * fix: tiny bug in `NewSubmitConsumerDoubleVotingCmd` (#1247) * fix double voting cli * fix bug double signing handler * godoc * nits * revert wrong push of lasts commits * fix: make `HandleConsumerDoubleVoting` works with provider pubkeys (#1254) * fix double voting cli * fix bug double signing handler * godoc * nits * lint * nit * fix: verify equivocation using validator pubkey in `SubmitConsumerDoubleVoting` msg (#1264) * verify dv evidence using malicious validator pubkey in infraction block header * nits * nits * refactor: update the E2E tests to work with Hermes relayer v1.6.0 (#1278) * save changes * fix hermes config * fist successful run * nit * nits * nits * doc and nits * lint * test: add E2E tests for double voting evidence handling (#1256) * fix double voting cli * add double-signing e2e test * refortmat e2e double voting test * godoc, revert unwanted changes * nit * verify dv evidence using malicious validator pubkey in infraction block header * save changes * fix hermes config * fist successful run * nit * nits * nits * doc and nits * lint * refactor * typo * change hermes docker image * nits * Update tests/e2e/steps.go Co-authored-by: Philip Offtermatt <57488781+p-offtermatt@users.noreply.github.com> * address PR comments * nits --------- Co-authored-by: Philip Offtermatt <57488781+p-offtermatt@users.noreply.github.com> * feat!: provider proposal for changing reward denoms (backport #1280) (#1291) * feat!: provider proposal for changing reward denoms (#1280) * new provider prop type * add methods and tests for new prop, update docs * remove old tx, fix tests * e2e handling * fix command type * boilerplate * fix e2e tests * Update CHANGELOG.md * lint * validate denoms * Update proposal.go * rm msg string * fix tests * rm chain in change denom action * lint * test for invalid denom * events for both add and remove * Update proposal_test.go (cherry picked from commit 48a21869e9a56cafff53b5c51077a96d1e3c4926) # Conflicts: # CHANGELOG.md # app/provider/app.go # proto/interchain_security/ccv/provider/v1/provider.proto # proto/interchain_security/ccv/provider/v1/tx.proto # tests/e2e/actions.go # tests/integration/distribution.go # x/ccv/provider/client/cli/tx.go # x/ccv/provider/client/proposal_handler.go # x/ccv/provider/keeper/distribution.go # x/ccv/provider/keeper/distribution_test.go # x/ccv/provider/proposal_handler_test.go # x/ccv/provider/types/codec.go # x/ccv/provider/types/proposal.go # x/ccv/provider/types/provider.pb.go # x/ccv/provider/types/tx.pb.go * fix conflicts * fix rest handler * Update CHANGELOG.md * rm uneeded tx proto --------- Co-authored-by: Shawn <44221603+smarshall-spitzbart@users.noreply.github.com> * save * fix nits * update changelog and fix nits * feat: implement slashing functionality on the provider chain (ADR-013) (#1275) Implementing the slashing functionality, as described in ADDR, on the provider chain. * fix e2e happy-path-short test * make consumer misbehaviour and double signing tests pass * currently debugging democracy-reward * fix e2e democ test * refactor: remove equivocation proposal (#1294) * remove equivocation proposal * bring back evidencekeeper * go.sum added * rebase and fix lint issues * fix mocks * clean up protos & delete unecessary file * fix E2E test * fix Dockerfile * more fixes * increase wait attempt * increase wait attempt * wait 1 block in gov proposal * fix numbers --------- Co-authored-by: Karolos Antoniadis * add equivo removal failing tests * fix democ e2e tests * make mem tests pass * lint * fix Dockerfile * update changelog * fix nits * update rapid test * fix democ tests * update changelog * fix CHANGELOG.md * nits * Update docs/docs/features/slashing.md Co-authored-by: insumity * Update proto/interchain_security/ccv/provider/v1/tx.proto Co-authored-by: insumity * Update x/ccv/provider/client/cli/tx.go Co-authored-by: insumity * Update x/ccv/provider/client/cli/tx.go Co-authored-by: insumity * Update x/ccv/provider/keeper/misbehaviour.go Co-authored-by: insumity * Update x/ccv/provider/keeper/punish_validator.go Co-authored-by: insumity * Update x/ccv/provider/keeper/punish_validator.go Co-authored-by: insumity * Update x/ccv/provider/keeper/punish_validator.go Co-authored-by: insumity * check verifying pubkey against validator address * add tests * update checkMisbehaviour tests * save * update all misbehaviour memory tests * fix misb verify valset sigs test * revert misb check fix for ibc 7 * nit fix * fix nit * fix: add equivocation proposal message back in protos (#1394) * add depreacted equiv prop msg def * add codec and content for equivo proposal msg * doc * proto * fix!: verify the signatures of byzantine validators in misbehaviour handling (#1422) * update byzantine validators extraction * nits * Update x/ccv/provider/keeper/misbehaviour.go Co-authored-by: insumity * last changes * udpdate changelog --------- Co-authored-by: insumity * feat: update misbehaviour handling using IBC-Go 7 (#1401) * nit fix * improve doc * super picky nit * fix!: drop nil votes in misbehaviour handling (#1404) * update CHANGELOG for final release * save * update test to extract byzantine validators * improve testing * nits * nits * Update tests/integration/misbehaviour.go Co-authored-by: insumity * Update testutil/crypto/evidence.go Co-authored-by: insumity * update util func * doc * check misb client ID * Update x/ccv/provider/keeper/misbehaviour.go Co-authored-by: insumity * nits --------- Co-authored-by: insumity * fix comments * update changelog * Update x/ccv/provider/keeper/misbehaviour.go Co-authored-by: insumity * improve tests * lint * udpate traces * silly bug --------- Co-authored-by: insumity * nit * Update x/ccv/provider/keeper/double_vote.go Co-authored-by: insumity * Update x/ccv/provider/keeper/double_vote.go Co-authored-by: insumity * Update x/ccv/provider/keeper/misbehaviour.go Co-authored-by: insumity * Update x/ccv/provider/keeper/misbehaviour.go Co-authored-by: insumity * Update tests/integration/double_vote.go Co-authored-by: insumity * Update x/ccv/provider/client/cli/tx.go Co-authored-by: insumity * Update x/ccv/provider/client/cli/tx.go Co-authored-by: insumity * address comments * address comments * Update x/ccv/provider/types/proposal.go Co-authored-by: Philip Offtermatt <57488781+p-offtermatt@users.noreply.github.com> * Update tests/integration/double_vote.go Co-authored-by: Philip Offtermatt <57488781+p-offtermatt@users.noreply.github.com> * Port: (feat!) [#1435](https://github.com/cosmos/interchain-security/pull/1435) Add height-base filter for consumer equivocation evidence. * fix: update consumer double vote cmd (#1439) add cmd fix * update CHANGELOG * update changelog * update changelog entries * nits --------- Co-authored-by: Shawn <44221603+smarshall-spitzbart@users.noreply.github.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> Co-authored-by: Philip Offtermatt <57488781+p-offtermatt@users.noreply.github.com> Co-authored-by: Marius Poke Co-authored-by: MSalopek Co-authored-by: yaruwangway <69694322+yaruwangway@users.noreply.github.com> Co-authored-by: Yaru Wang Co-authored-by: Anca Zamfir Co-authored-by: insumity Co-authored-by: Karolos Antoniadis --- ...ic-verification-of-equivocation-feature.md | 2 + ...1340-cryptographic-equivocation-feature.md | 4 + ...1340-cryptographic-equivocation-feature.md | 4 + CHANGELOG.md | 1 + Dockerfile | 2 +- app/provider/app.go | 20 +- docs/docs/features/proposals.md | 58 -- docs/docs/features/slashing.md | 17 +- docs/docs/introduction/overview.md | 4 - docs/docs/validators/overview.md | 2 +- .../ccv/provider/v1/provider.proto | 5 +- .../ccv/provider/v1/tx.proto | 40 +- tests/e2e/action_rapid_test.go | 49 +- tests/e2e/actions.go | 128 +-- tests/e2e/actions_consumer_misbehaviour.go | 98 ++ tests/e2e/config.go | 86 ++ tests/e2e/json_utils.go | 30 +- tests/e2e/main.go | 37 +- tests/e2e/state.go | 108 ++- tests/e2e/state_rapid_test.go | 13 - tests/e2e/steps.go | 32 +- tests/e2e/steps_consumer_misbehaviour.go | 287 ++++++ tests/e2e/steps_double_sign.go | 83 +- tests/e2e/steps_light_client_attack.go | 1 - .../e2e/steps_submit_equivocation_proposal.go | 150 --- tests/e2e/testnet-scripts/fork-consumer.sh | 114 +++ tests/e2e/testnet-scripts/hermes-config.toml | 18 +- tests/e2e/testnet-scripts/start-chain.sh | 8 +- tests/e2e/trace_handlers_test.go | 23 +- .../e2e/tracehandler_testdata/changeover.json | 19 +- .../consumer-double-sign.json | 452 +++++++++ .../consumer-misbehaviour.json | 424 +++++++++ .../e2e/tracehandler_testdata/democracy.json | 38 +- .../democracyRewardsSteps.json | 30 +- .../e2e/tracehandler_testdata/happyPath.json | 434 ++------- .../multipleConsumers.json | 99 +- .../e2e/tracehandler_testdata/shorthappy.json | 417 +------- .../tracehandler_testdata/slashThrottle.json | 30 +- tests/integration/double_vote.go | 395 ++++++++ tests/integration/misbehaviour.go | 551 +++++++++++ testutil/crypto/evidence.go | 135 +++ testutil/integration/debug_test.go | 27 + testutil/keeper/expectations.go | 63 ++ testutil/keeper/mocks.go | 217 +++-- testutil/keeper/unit_test_helpers.go | 3 - x/ccv/provider/client/cli/tx.go | 127 +++ x/ccv/provider/client/proposal_handler.go | 126 --- x/ccv/provider/handler.go | 6 + .../provider/keeper/consumer_equivocation.go | 480 ++++++++++ .../keeper/consumer_equivocation_test.go | 790 ++++++++++++++++ x/ccv/provider/keeper/hooks.go | 4 + x/ccv/provider/keeper/keeper.go | 17 +- x/ccv/provider/keeper/msg_server.go | 73 +- x/ccv/provider/keeper/proposal.go | 12 - x/ccv/provider/keeper/proposal_test.go | 61 -- x/ccv/provider/proposal_handler.go | 4 +- x/ccv/provider/proposal_handler_test.go | 26 +- x/ccv/provider/types/codec.go | 9 +- x/ccv/provider/types/keys.go | 10 + x/ccv/provider/types/keys_test.go | 2 + x/ccv/provider/types/msg.go | 118 ++- x/ccv/provider/types/proposal.go | 6 +- x/ccv/provider/types/proposal_test.go | 57 -- x/ccv/provider/types/provider.pb.go | 212 +++-- x/ccv/provider/types/tx.pb.go | 890 +++++++++++++++++- x/ccv/types/errors.go | 26 +- x/ccv/types/events.go | 48 +- x/ccv/types/expected_keepers.go | 15 +- 68 files changed, 6128 insertions(+), 1749 deletions(-) create mode 100644 .changelog/unreleased/api-breaking/provider/1340-add-cryptographic-verification-of-equivocation-feature.md create mode 100644 .changelog/unreleased/features/provider/1340-cryptographic-equivocation-feature.md create mode 100644 .changelog/unreleased/state-breaking/provider/1340-cryptographic-equivocation-feature.md create mode 100644 tests/e2e/actions_consumer_misbehaviour.go create mode 100644 tests/e2e/steps_consumer_misbehaviour.go delete mode 100644 tests/e2e/steps_submit_equivocation_proposal.go create mode 100644 tests/e2e/testnet-scripts/fork-consumer.sh create mode 100644 tests/e2e/tracehandler_testdata/consumer-double-sign.json create mode 100644 tests/e2e/tracehandler_testdata/consumer-misbehaviour.json create mode 100644 tests/integration/double_vote.go create mode 100644 tests/integration/misbehaviour.go create mode 100644 testutil/crypto/evidence.go create mode 100644 x/ccv/provider/keeper/consumer_equivocation.go create mode 100644 x/ccv/provider/keeper/consumer_equivocation_test.go diff --git a/.changelog/unreleased/api-breaking/provider/1340-add-cryptographic-verification-of-equivocation-feature.md b/.changelog/unreleased/api-breaking/provider/1340-add-cryptographic-verification-of-equivocation-feature.md new file mode 100644 index 0000000000..c50662be72 --- /dev/null +++ b/.changelog/unreleased/api-breaking/provider/1340-add-cryptographic-verification-of-equivocation-feature.md @@ -0,0 +1,2 @@ +- Deprecate equivocation proposals. +([\#1340](https://github.com/cosmos/interchain-security/pull/1340)) \ No newline at end of file diff --git a/.changelog/unreleased/features/provider/1340-cryptographic-equivocation-feature.md b/.changelog/unreleased/features/provider/1340-cryptographic-equivocation-feature.md new file mode 100644 index 0000000000..4a90b46488 --- /dev/null +++ b/.changelog/unreleased/features/provider/1340-cryptographic-equivocation-feature.md @@ -0,0 +1,4 @@ +- Introduce the cryptographic verification of equivocation feature to the provider + (cf. [ADR-005](/docs/docs/adrs/adr-005-cryptographic-equivocation-verification.md) + & [ADR-013](/docs/docs/adrs/adr-013-equivocation-slashing.md)). + ([\#1340](https://github.com/cosmos/interchain-security/pull/1340)) \ No newline at end of file diff --git a/.changelog/unreleased/state-breaking/provider/1340-cryptographic-equivocation-feature.md b/.changelog/unreleased/state-breaking/provider/1340-cryptographic-equivocation-feature.md new file mode 100644 index 0000000000..4a90b46488 --- /dev/null +++ b/.changelog/unreleased/state-breaking/provider/1340-cryptographic-equivocation-feature.md @@ -0,0 +1,4 @@ +- Introduce the cryptographic verification of equivocation feature to the provider + (cf. [ADR-005](/docs/docs/adrs/adr-005-cryptographic-equivocation-verification.md) + & [ADR-013](/docs/docs/adrs/adr-013-equivocation-slashing.md)). + ([\#1340](https://github.com/cosmos/interchain-security/pull/1340)) \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 35c926aab1..046a01c16e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -186,3 +186,4 @@ In addition, RS has the following features: - **Key Assignment**: Enables validator operators to use different consensus keys for each consumer chain validator node that they operate. - **Jail Throttling**: Enables the provider to slow down a "worst case scenario" attack where a malicious consumer binary attempts to jail a significant amount (> 2/3) of the voting power, effectively taking control of the provider. + diff --git a/Dockerfile b/Dockerfile index 7c45187034..2e4ea3c334 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,7 +29,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:evidence-cmd AS hermes-builder # Get CometMock FROM ghcr.io/informalsystems/cometmock:v0.37.x as cometmock-builder diff --git a/app/provider/app.go b/app/provider/app.go index 4550ec77d2..497158449a 100644 --- a/app/provider/app.go +++ b/app/provider/app.go @@ -140,7 +140,6 @@ var ( ibcclientclient.UpgradeProposalHandler, ibcproviderclient.ConsumerAdditionProposalHandler, ibcproviderclient.ConsumerRemovalProposalHandler, - ibcproviderclient.EquivocationProposalHandler, ibcproviderclient.ChangeRewardDenomsProposalHandler, }, ), @@ -404,14 +403,6 @@ func New( scopedIBCKeeper, ) - // create evidence keeper with router - app.EvidenceKeeper = *evidencekeeper.NewKeeper( - appCodec, - keys[evidencetypes.StoreKey], - app.StakingKeeper, - app.SlashingKeeper, - ) - app.ProviderKeeper = ibcproviderkeeper.NewKeeper( appCodec, keys[providertypes.StoreKey], @@ -424,7 +415,6 @@ func New( app.StakingKeeper, app.SlashingKeeper, app.AccountKeeper, - app.EvidenceKeeper, app.DistrKeeper, app.BankKeeper, authtypes.FeeCollectorName, @@ -477,6 +467,16 @@ func New( ibcRouter.AddRoute(providertypes.ModuleName, providerModule) app.IBCKeeper.SetRouter(ibcRouter) + // create evidence keeper with router + evidenceKeeper := evidencekeeper.NewKeeper( + appCodec, + keys[evidencetypes.StoreKey], + app.StakingKeeper, + app.SlashingKeeper, + ) + + app.EvidenceKeeper = *evidenceKeeper + skipGenesisInvariants := cast.ToBool(appOpts.Get(crisis.FlagSkipGenesisInvariants)) // NOTE: Any module instantiated in the module manager that is later modified diff --git a/docs/docs/features/proposals.md b/docs/docs/features/proposals.md index de4f2dc421..70077944ea 100644 --- a/docs/docs/features/proposals.md +++ b/docs/docs/features/proposals.md @@ -74,37 +74,6 @@ Minimal example: } ``` -## `EquivocationProposal` -:::tip -`EquivocationProposal` will only be accepted on the provider chain if at least one of the consumer chains submits equivocation evidence to the provider. -Sending equivocation evidence to the provider is handled automatically by the interchain security protocol when an equivocation infraction is detected on the consumer chain. -::: - -Proposal type used to suggest slashing a validator for double signing on consumer chain. -When proposals of this type are passed, the validator in question will be slashed for equivocation on the provider chain. - -:::warning -Take note that an equivocation slash causes a validator to be tombstoned (can never re-enter the active set). -Tombstoning a validator on the provider chain will remove the validator from the validator set of all consumer chains. -::: - -Minimal example: -```js -{ - "title": "Validator-1 double signed on consumerchain-1", - "description": "Here is more information about the infraction so you can verify it yourself", - // the list of equivocations that will be processed - "equivocations": [ - { - "height": 14444680, - "time": "2023-02-28T20:40:00.000000Z", - "power": 5500000, - "consensus_address": "" - } - ] -} -``` - ## ChangeRewardDenomProposal :::tip `ChangeRewardDenomProposal` will only be accepted on the provider chain if at least one of the denomsToAdd or denomsToRemove fields is populated with at least one denom. Also, a denom cannot be repeated in both sets. @@ -121,30 +90,3 @@ Minimal example: "denomsToRemove": [] } ``` - -### Notes -When submitting equivocation evidence through an `EquivocationProposal` please take note that you need to use the consensus address (`valcons`) of the offending validator on the **provider chain**. -Besides that, the `height` and the `time` fields should be mapped to the **provider chain** to avoid your evidence being rejected. - -Before submitting the proposal please check that the evidence is not outdated by comparing the infraction height with the `max_age_duration` and `max_age_num_blocks` consensus parameters of the **provider chain**. - -### Gaia example: -``` -➜ ~ cat genesis.json | jq ".consensus_params" -{ - "block": { - ... - }, - "evidence": { - "max_age_duration": "172800000000000", - "max_age_num_blocks": "1000000", - "max_bytes": "50000" - }, - "validator": { - ... - }, - "version": {} -} -``` - -Any `EquivocationProposal` transactions that submit evidence with `height` older than `max_age_num_blocks` and `time` older than `max_age_duration` will be considered invalid. diff --git a/docs/docs/features/slashing.md b/docs/docs/features/slashing.md index 4c74153b15..cdf166da8c 100644 --- a/docs/docs/features/slashing.md +++ b/docs/docs/features/slashing.md @@ -20,17 +20,10 @@ Instead of slashing, the provider will only jail offending validator for the dur Slash throttling (sometimes called jail throttling) mechanism ensures that only a fraction of the validator set can be jailed at any one time to prevent malicious consumer chains from harming the provider. ::: -## Double-signing (equivocation) -infractions are not acted upon immediately. +# Cryptographic verification of equivocation and slashing +The Cryptographic verification of equivocation allows external agents to submit evidences of light client and double signing attacks observed on a consumer chain. When valid evidence is received, the malicious validators will be slashed, jailed, and tombstoned on the provider. -Upon receiving double signing evidence, the provider chain will take note of the evidence and allow for `EquivocationProposal` to be submitted to slash the offending validator. -Any `EquivocationProposal`s to slash a validator that has not double signed on any of the consumer chains will be automatically rejected by the provider chain. +The feature is outlined in [ADR-005](../adrs/adr-005-cryptographic-equivocation-verification.md) and [ADR-013](../adrs/adr-013-equivocation-slashing.md). -:::info -The offending validator will only be slashed (and tombstoned) if an `EquivocationProposal` is accepted and passed through governance. - -The offending validator will effectively get slashed and tombstoned on all consumer chains. -::: - - -You can find instructions on creating `EquivocationProposal`s [here](./proposals#equivocationproposal). +By sending a `MsgSubmitConsumerMisbehaviour` or a `MsgSubmitConsumerDoubleVoting` transaction, the provider will + verify the reported equivocation and, if successful, slash, jail, and tombstone the malicious validator. \ No newline at end of file diff --git a/docs/docs/introduction/overview.md b/docs/docs/introduction/overview.md index f61fe4d209..aba31751e1 100644 --- a/docs/docs/introduction/overview.md +++ b/docs/docs/introduction/overview.md @@ -30,10 +30,6 @@ To ensure the security of the consumer chain, provider delegators cannot unbond If downtime is initiated by a validator on a consumer chain, a downtime packet will be relayed to the provider to jail that validator for a set amount of time. The validator who committed downtime will then miss out on staking rewards for the configured jailing period. -### Equivocation (Double Sign) Slashing - -Evidence of equivocation must be submitted to provider governance and be voted on. This behavior is an extra safeguard before a validator is slashed, and may be replaced by a more automated system in the future. - ### Tokenomics and Rewards Consumer chains are free to create their own native token which can be used for fees, and can be created on the consumer chain in the form of inflationary rewards. These rewards can be used to incentivize user behavior, for example, LPing or staking. A portion of these fees and rewards will be sent to provider chain stakers, but that proportion is completely customizable by the developers, and subject to governance. diff --git a/docs/docs/validators/overview.md b/docs/docs/validators/overview.md index 35ce56cdb3..4d764703be 100644 --- a/docs/docs/validators/overview.md +++ b/docs/docs/validators/overview.md @@ -91,7 +91,7 @@ More information is available in [Downtime Slashing documentation](../features/s ::: ## Double-signing Infractions -To learn more about equivocation handling in replicated security check out the [Slashing](../features/slashing.md#double-signing-equivocation) and [EquivocationProposal](../features/proposals.md#equivocationproposal) documentation sections +To learn more about equivocation handling in replicated security check out the [Slashing](../features/slashing.md) documentation section. ## Key assignment Validators can use different consensus keys on the provider and each of the consumer chains. The consumer chain consensus key must be registered on the provider before use. diff --git a/proto/interchain_security/ccv/provider/v1/provider.proto b/proto/interchain_security/ccv/provider/v1/provider.proto index f085ba472d..f871a90e25 100644 --- a/proto/interchain_security/ccv/provider/v1/provider.proto +++ b/proto/interchain_security/ccv/provider/v1/provider.proto @@ -106,10 +106,13 @@ message ConsumerRemovalProposal { // punish a validator for equivocation on a consumer chain. // // This type is only used internally to the consumer CCV module. +// WARNING: This message is deprecated now that equivocations can be submitted +// and verified automatically on the provider. (see SubmitConsumerDoubleVoting in proto/interchain-security/ccv/provider/v1/tx.proto). message EquivocationProposal { + option deprecated = true; // the title of the proposal string title = 1; - // the description of the proposal +// the description of the proposal string description = 2; // the list of equivocations that will be processed repeated cosmos.evidence.v1beta1.Equivocation equivocations = 3; diff --git a/proto/interchain_security/ccv/provider/v1/tx.proto b/proto/interchain_security/ccv/provider/v1/tx.proto index 1fb97e4132..ada2bffde8 100644 --- a/proto/interchain_security/ccv/provider/v1/tx.proto +++ b/proto/interchain_security/ccv/provider/v1/tx.proto @@ -4,11 +4,17 @@ package interchain_security.ccv.provider.v1; option go_package = "github.com/cosmos/interchain-security/v3/x/ccv/provider/types"; 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 { - rpc AssignConsumerKey(MsgAssignConsumerKey) - returns (MsgAssignConsumerKeyResponse); + rpc AssignConsumerKey(MsgAssignConsumerKey) returns (MsgAssignConsumerKeyResponse); + rpc SubmitConsumerMisbehaviour(MsgSubmitConsumerMisbehaviour) returns (MsgSubmitConsumerMisbehaviourResponse); + rpc SubmitConsumerDoubleVoting(MsgSubmitConsumerDoubleVoting) returns (MsgSubmitConsumerDoubleVotingResponse); } message MsgAssignConsumerKey { @@ -25,3 +31,33 @@ message MsgAssignConsumerKey { } message MsgAssignConsumerKeyResponse {} + + +// MsgSubmitConsumerMisbehaviour defines a message that reports a light client attack, +// also known as a misbehaviour, observed on a consumer chain +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 = 2; +} + +message MsgSubmitConsumerMisbehaviourResponse {} + + +// MsgSubmitConsumerDoubleVoting defines a message that reports +// a double signing infraction observed on a consumer chain +message MsgSubmitConsumerDoubleVoting { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + string submitter = 1; + // The equivocation of the consumer chain wrapping + // an evidence of a validator that signed two conflicting votes + tendermint.types.DuplicateVoteEvidence duplicate_vote_evidence = 2; + // The light client header of the infraction block + ibc.lightclients.tendermint.v1.Header infraction_block_header = 3; +} + +message MsgSubmitConsumerDoubleVotingResponse {} diff --git a/tests/e2e/action_rapid_test.go b/tests/e2e/action_rapid_test.go index 09d28abacf..871b78dc24 100644 --- a/tests/e2e/action_rapid_test.go +++ b/tests/e2e/action_rapid_test.go @@ -66,7 +66,6 @@ func GetActionGen() *rapid.Generator[any] { GetSubmitConsumerAdditionProposalActionGen().AsAny(), GetSubmitConsumerRemovalProposalActionGen().AsAny(), GetSubmitParamChangeProposalActionGen().AsAny(), - GetSubmitEquivocationProposalActionGen().AsAny(), GetVoteGovProposalActionGen().AsAny(), GetStartConsumerChainActionGen().AsAny(), GetAddChainToRelayerActionGen().AsAny(), @@ -91,6 +90,9 @@ func GetActionGen() *rapid.Generator[any] { CreateLightClientEquivocationAttackActionGen().AsAny(), CreateLightClientAmnesiaAttackActionGen().AsAny(), CreateLightClientLunaticAttackActionGen().AsAny(), + GetStartConsumerEvidenceDetectorActionGen().AsAny(), + GetForkConsumerChainActionGen().AsAny(), + GetUpdateLightClientActionGen().AsAny(), ) } @@ -209,7 +211,7 @@ func GetStartChainActionGen() *rapid.Generator[StartChainAction] { Chain: GetChainIDGen().Draw(t, "Chain"), Validators: GetStartChainValidatorsGen().Draw(t, "Validators"), GenesisChanges: rapid.String().Draw(t, "GenesisChanges"), - SkipGentx: rapid.Bool().Draw(t, "SkipGentx"), + IsConsumer: rapid.Bool().Draw(t, "IsConsumer"), } }) } @@ -280,19 +282,6 @@ func GetSubmitParamChangeProposalActionGen() *rapid.Generator[submitParamChangeL }) } -func GetSubmitEquivocationProposalActionGen() *rapid.Generator[submitEquivocationProposalAction] { - return rapid.Custom(func(t *rapid.T) submitEquivocationProposalAction { - return submitEquivocationProposalAction{ - Chain: GetChainIDGen().Draw(t, "Chain"), - From: GetValidatorIDGen().Draw(t, "From"), - Deposit: rapid.Uint().Draw(t, "Deposit"), - Height: rapid.Int64().Draw(t, "Height"), - Time: GetTimeGen().Draw(t, "Time"), - Power: rapid.Int64().Draw(t, "Power"), - } - }) -} - func TestMarshalAndUnmarshalTime(t *testing.T) { rapid.Check(t, func(t *rapid.T) { time1 := GetTimeGen().Draw(t, "time") @@ -506,3 +495,33 @@ func GetWaitTimeAction() *rapid.Generator[waitTimeAction] { } }) } + +func GetForkConsumerChainActionGen() *rapid.Generator[forkConsumerChainAction] { + return rapid.Custom(func(t *rapid.T) forkConsumerChainAction { + return forkConsumerChainAction{ + ConsumerChain: GetChainIDGen().Draw(t, "ConsumerChain"), + ProviderChain: GetChainIDGen().Draw(t, "ProviderChain"), + Validator: GetValidatorIDGen().Draw(t, "Validator"), + RelayerConfig: rapid.String().Draw(t, "RelayerConfig"), + } + }) +} + +func GetStartConsumerEvidenceDetectorActionGen() *rapid.Generator[startConsumerEvidenceDetectorAction] { + return rapid.Custom(func(t *rapid.T) startConsumerEvidenceDetectorAction { + return startConsumerEvidenceDetectorAction{ + Chain: GetChainIDGen().Draw(t, "Chain"), + } + }) +} + +func GetUpdateLightClientActionGen() *rapid.Generator[updateLightClientAction] { + return rapid.Custom(func(t *rapid.T) updateLightClientAction { + return updateLightClientAction{ + Chain: GetChainIDGen().Draw(t, "Chain"), + HostChain: GetChainIDGen().Draw(t, "HostChain"), + RelayerConfig: rapid.String().Draw(t, "RelayerConfig"), + ClientID: rapid.String().Draw(t, "ClientID"), + } + }) +} diff --git a/tests/e2e/actions.go b/tests/e2e/actions.go index 63070289d6..b95be90d12 100644 --- a/tests/e2e/actions.go +++ b/tests/e2e/actions.go @@ -15,8 +15,6 @@ import ( clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" "github.com/tidwall/gjson" - evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" - "github.com/cosmos/interchain-security/v3/x/ccv/provider/client" "github.com/cosmos/interchain-security/v3/x/ccv/provider/types" ccvtypes "github.com/cosmos/interchain-security/v3/x/ccv/types" @@ -67,7 +65,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 { @@ -137,7 +135,7 @@ func (tr *TestConfig) 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 @@ -172,8 +170,9 @@ func (tr *TestConfig) startChain( } tr.addChainToRelayer(addChainToRelayerAction{ - Chain: action.Chain, - Validator: action.Validators[0].Id, + Chain: action.Chain, + Validator: action.Validators[0].Id, + IsConsumer: action.IsConsumer, }, verbose) // store the fact that we started the chain @@ -431,78 +430,6 @@ func (tr TestConfig) submitParamChangeProposal( tr.waitBlocks(action.Chain, 2, 60*time.Second) } -type submitEquivocationProposalAction struct { - Chain ChainID - Height int64 - Time time.Time - Power int64 - Validator ValidatorID - Deposit uint - From ValidatorID -} - -func (tr TestConfig) submitEquivocationProposal(action submitEquivocationProposalAction, verbose bool) { - val := tr.validatorConfigs[action.Validator] - providerChain := tr.chainConfigs[ChainID("provi")] - - prop := client.EquivocationProposalJSON{ - Summary: "Validator equivocation!", - EquivocationProposal: types.EquivocationProposal{ - Title: "Validator equivocation!", - Description: fmt.Sprintf("Validator: %s has committed an equivocation infraction on ChainID: %s", action.Validator, action.Chain), - Equivocations: []*evidencetypes.Equivocation{ - { - Height: action.Height, - Time: action.Time, - Power: action.Power, - ConsensusAddress: val.ValconsAddress, - }, - }, - }, - Deposit: fmt.Sprint(action.Deposit) + `stake`, - } - - bz, err := json.Marshal(prop) - if err != nil { - log.Fatal(err) - } - - jsonStr := string(bz) - if strings.Contains(jsonStr, "'") { - log.Fatal("prop json contains single quote") - } - - //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. - bz, err = exec.Command("docker", "exec", tr.containerConfig.InstanceName, - "/bin/bash", "-c", fmt.Sprintf(`echo '%s' > %s`, jsonStr, "/equivocation-proposal.json")).CombinedOutput() - - if err != nil { - log.Fatal(err, "\n", string(bz)) - } - - //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. - // EQUIVOCATION PROPOSAL - bz, err = exec.Command("docker", "exec", tr.containerConfig.InstanceName, providerChain.BinaryName, - - "tx", "gov", "submit-legacy-proposal", "equivocation", "/equivocation-proposal.json", - - `--from`, `validator`+fmt.Sprint(action.From), - `--chain-id`, string(providerChain.ChainId), - `--home`, tr.getValidatorHome(providerChain.ChainId, action.From), - `--node`, tr.getValidatorNode(providerChain.ChainId, action.From), - `--gas`, "9000000", - `--keyring-backend`, `test`, - `-y`, - ).CombinedOutput() - - if err != nil { - log.Fatal(err, "\n", string(bz)) - } - - // wait for inclusion in a block -> '--broadcast-mode block' is deprecated - tr.waitBlocks(ChainID("provi"), 2, 30*time.Second) -} - type voteGovProposalAction struct { Chain ChainID From []ValidatorID @@ -586,7 +513,7 @@ func (tr *TestConfig) startConsumerChain( Chain: action.ConsumerChain, Validators: action.Validators, GenesisChanges: consumerGenesis, - SkipGentx: true, + IsConsumer: true, }, verbose) } @@ -718,8 +645,9 @@ func (tr TestConfig) startChangeover( } type addChainToRelayerAction struct { - Chain ChainID - Validator ValidatorID + Chain ChainID + Validator ValidatorID + IsConsumer bool } const hermesChainConfigTemplate = ` @@ -736,7 +664,8 @@ rpc_addr = "%s" rpc_timeout = "10s" store_prefix = "ibc" trusting_period = "14days" -websocket_addr = "%s" +event_source = { mode = "push", url = "%s", batch_delay = "50ms" } +ccv_consumer_chain = %v [chains.gas_price] denom = "stake" @@ -835,7 +764,7 @@ func (tr TestConfig) addChainToHermes( keyName, rpcAddr, wsAddr, - // action.consumer, + action.IsConsumer, ) bashCommand := fmt.Sprintf(`echo '%s' >> %s`, chainConfig, "/root/.hermes/config.toml") @@ -849,7 +778,15 @@ func (tr TestConfig) 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.IsConsumer { + 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) //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. bz, err = exec.Command("docker", "exec", tr.containerConfig.InstanceName, "bash", "-c", saveMnemonicCommand, @@ -2107,6 +2044,29 @@ func (tr TestConfig) GetPathNameForGorelayer(chainA, chainB ChainID) string { return pathName } +// Run an instance of the Hermes relayer using the "evidence" command, +// which detects evidences committed to the blocks of a consumer chain. +// Each infraction detected is reported to the provider chain using +// either a SubmitConsumerDoubleVoting or a SubmitConsumerMisbehaviour message. +type startConsumerEvidenceDetectorAction struct { + Chain ChainID +} + +func (tc TestConfig) startConsumerEvidenceDetector( + action startConsumerEvidenceDetectorAction, + verbose bool, +) { + chainConfig := tc.chainConfigs[action.Chain] + // run in detached mode so it will keep running in the background + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + bz, err := exec.Command("docker", "exec", "-d", tc.containerConfig.InstanceName, + "hermes", "evidence", "--chain", string(chainConfig.ChainId)).CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + tc.waitBlocks("provi", 10, 2*time.Minute) +} + // WaitTime waits for the given duration. // To make sure that the new timestamp is visible on-chain, it also waits until at least one block has been // produced on each chain after waiting. diff --git a/tests/e2e/actions_consumer_misbehaviour.go b/tests/e2e/actions_consumer_misbehaviour.go new file mode 100644 index 0000000000..7d71d58d1d --- /dev/null +++ b/tests/e2e/actions_consumer_misbehaviour.go @@ -0,0 +1,98 @@ +package main + +import ( + "bufio" + "log" + "os/exec" + "strconv" + "time" +) + +// forkConsumerChainAction forks the consumer chain by cloning of a validator node +// Note that the chain fork is running in an different network +type forkConsumerChainAction struct { + ConsumerChain ChainID + ProviderChain ChainID + Validator ValidatorID + RelayerConfig string +} + +func (tc TestConfig) forkConsumerChain(action forkConsumerChainAction, verbose bool) { + valCfg := tc.validatorConfigs[action.Validator] + + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + configureNodeCmd := exec.Command("docker", "exec", tc.containerConfig.InstanceName, "/bin/bash", + "/testnet-scripts/fork-consumer.sh", tc.chainConfigs[action.ConsumerChain].BinaryName, + string(action.Validator), string(action.ConsumerChain), + tc.chainConfigs[action.ConsumerChain].IpPrefix, + tc.chainConfigs[action.ProviderChain].IpPrefix, + valCfg.Mnemonic, + action.RelayerConfig, + ) + + if verbose { + log.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 { + log.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 { + Chain ChainID + HostChain ChainID + RelayerConfig string + ClientID string +} + +func (tc TestConfig) updateLightClient( + action updateLightClientAction, + verbose bool, +) { + // retrieve a trusted height of the consumer light client + trustedHeight := tc.getTrustedHeight(action.HostChain, action.ClientID, 2) + + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + cmd := exec.Command("docker", "exec", tc.containerConfig.InstanceName, "hermes", + "--config", action.RelayerConfig, + "update", + "client", + "--client", action.ClientID, + "--host-chain", string(action.HostChain), + "--trusted-height", strconv.Itoa(int(trustedHeight.RevisionHeight)), + ) + if verbose { + log.Println("updateLightClientAction cmd:", cmd.String()) + } + + bz, err := cmd.CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + tc.waitBlocks(action.HostChain, 5, 30*time.Second) +} diff --git a/tests/e2e/config.go b/tests/e2e/config.go index 462d6e4a49..6eacdfe3c9 100644 --- a/tests/e2e/config.go +++ b/tests/e2e/config.go @@ -397,6 +397,92 @@ func ChangeoverTestConfig() TestConfig { return tr } +func ConsumerMisbehaviourTestConfig() TestConfig { + tc := TestConfig{ + name: "consumer-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.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 = \"60s\" | " + + ".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.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 = \"60s\" | " + + ".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/block_sync = true/block_sync = false/;`, + } + tc.Initialize() + return tc +} + func (s *TestConfig) SetDockerConfig(localSdkPath string, useGaia bool, gaiaTag string) { if localSdkPath != "" { fmt.Println("USING LOCAL SDK", localSdkPath) diff --git a/tests/e2e/json_utils.go b/tests/e2e/json_utils.go index 3d5867d42a..3155bba069 100644 --- a/tests/e2e/json_utils.go +++ b/tests/e2e/json_utils.go @@ -95,12 +95,6 @@ func UnmarshalMapToActionType(rawAction json.RawMessage, actionTypeString string if err == nil { return a, nil } - case "main.submitEquivocationProposalAction": - var a submitEquivocationProposalAction - err := json.Unmarshal(rawAction, &a) - if err == nil { - return a, nil - } case "main.submitParamChangeLegacyProposalAction": var a submitParamChangeLegacyProposalAction err := json.Unmarshal(rawAction, &a) @@ -281,6 +275,24 @@ func UnmarshalMapToActionType(rawAction json.RawMessage, actionTypeString string if err == nil { return a, nil } + case "main.forkConsumerChainAction": + var a forkConsumerChainAction + err := json.Unmarshal(rawAction, &a) + if err == nil { + return a, nil + } + case "main.startConsumerEvidenceDetectorAction": + var a startConsumerEvidenceDetectorAction + err := json.Unmarshal(rawAction, &a) + if err == nil { + return a, nil + } + case "main.updateLightClientAction": + var a updateLightClientAction + err := json.Unmarshal(rawAction, &a) + if err == nil { + return a, nil + } default: return nil, fmt.Errorf("unknown action type: %s", actionTypeString) } @@ -360,12 +372,6 @@ func UnmarshalProposalWithType(inputMap json.RawMessage, proposalType string) (P if err == nil { return prop, nil } - case "main.EquivocationProposal": - prop := EquivocationProposal{} - err := json.Unmarshal(inputMap, &prop) - if err == nil { - return prop, nil - } case "main.ParamsProposal": prop := ParamsProposal{} err := json.Unmarshal(inputMap, &prop) diff --git a/tests/e2e/main.go b/tests/e2e/main.go index 005ea18566..7261dd2ea6 100644 --- a/tests/e2e/main.go +++ b/tests/e2e/main.go @@ -54,12 +54,14 @@ var ( // map the test config names to their structs to allow for easy selection of test configs, // and also to programatically set parameters, i.e. see DemocracyTestConfig testConfigs = map[string]TestConfig{ - "default": DefaultTestConfig(), - "changeover": ChangeoverTestConfig(), - "democracy": DemocracyTestConfig(false), - "democracy-reward": DemocracyTestConfig(true), - "slash-throttle": SlashThrottleTestConfig(), - "multiconsumer": MultiConsumerTestConfig(), + "default": DefaultTestConfig(), + "changeover": ChangeoverTestConfig(), + "democracy": DemocracyTestConfig(false), + "democracy-reward": DemocracyTestConfig(true), + "slash-throttle": SlashThrottleTestConfig(), + "multiconsumer": MultiConsumerTestConfig(), + "consumer-misbehaviour": ConsumerMisbehaviourTestConfig(), + "consumer-double-sign": DefaultTestConfig(), } ) @@ -114,6 +116,18 @@ var stepChoices = map[string]StepChoice{ description: "multi consumer tests", testConfig: MultiConsumerTestConfig(), }, + "consumer-misbehaviour": { + name: "consumer-misbehaviour", + steps: consumerMisbehaviourSteps, + description: "consumer light client misbehaviour tests", + testConfig: ConsumerMisbehaviourTestConfig(), + }, + "consumer-double-sign": { + name: "consumer-double-sign", + steps: consumerDoubleSignSteps, + description: "consumer double signing tests", + testConfig: DefaultTestConfig(), + }, } func executeTests(tests []testStepsWithConfig) (err error) { @@ -216,7 +230,8 @@ func getTestCases(selectedPredefinedTests, selectedTestFiles TestSet) (tests []t if len(selectedPredefinedTests) == 0 && len(selectedTestFiles) == 0 { selectedPredefinedTests = TestSet{ "changeover", "happy-path", - "democracy-reward", "democracy", "slash-throttle", + "democracy-reward", "democracy", + "slash-throttle", "consumer-double-sign", "consumer-misbehaviour", } if includeMultiConsumer != nil && *includeMultiConsumer { selectedPredefinedTests = append(selectedPredefinedTests, "multiconsumer") @@ -331,8 +346,6 @@ func (tr *TestConfig) runStep(step Step, verbose bool) { tr.submitConsumerAdditionProposal(action, verbose) case submitConsumerRemovalProposalAction: tr.submitConsumerRemovalProposal(action, verbose) - case submitEquivocationProposalAction: - tr.submitEquivocationProposal(action, verbose) case submitParamChangeLegacyProposalAction: tr.submitParamChangeProposal(action, verbose) case voteGovProposalAction: @@ -383,6 +396,12 @@ func (tr *TestConfig) runStep(step Step, verbose bool) { tr.waitForTime(action, verbose) case startRelayerAction: tr.startRelayer(action, verbose) + case forkConsumerChainAction: + tr.forkConsumerChain(action, verbose) + case updateLightClientAction: + tr.updateLightClient(action, verbose) + case startConsumerEvidenceDetectorAction: + tr.startConsumerEvidenceDetector(action, verbose) case submitChangeRewardDenomsProposalAction: tr.submitChangeRewardDenomsProposal(action, verbose) default: diff --git a/tests/e2e/state.go b/tests/e2e/state.go index 93566c1f05..1671236104 100644 --- a/tests/e2e/state.go +++ b/tests/e2e/state.go @@ -1,6 +1,7 @@ package main import ( + "bufio" "fmt" "log" "os/exec" @@ -28,6 +29,7 @@ type ChainState struct { ProviderKeys *map[ValidatorID]string // validatorID: validator provider key ConsumerPendingPacketQueueSize *uint // Only relevant to consumer chains RegisteredConsumerRewardDenoms *[]string + ClientsFrozenHeights *map[string]clienttypes.Height } type Proposal interface { @@ -72,16 +74,6 @@ type ConsumerRemovalProposal struct { func (p ConsumerRemovalProposal) isProposal() {} -type EquivocationProposal struct { - Height uint - Power uint - ConsensusAddress string - Deposit uint - Status string -} - -func (p EquivocationProposal) isProposal() {} - type Rewards struct { IsRewarded map[ValidatorID]bool // if true it will calculate if the validator/delegator is rewarded between 2 successive blocks, @@ -172,6 +164,14 @@ func (tr TestConfig) getChainState(chain ChainID, modelState ChainState) ChainSt 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 + } + if modelState.ConsumerPendingPacketQueueSize != nil { pendingPacketQueueSize := tr.getPendingPacketQueueSize(chain) chainState.ConsumerPendingPacketQueueSize = &pendingPacketQueueSize @@ -314,6 +314,7 @@ func (tr TestConfig) getReward(chain ChainID, validator ValidatorID, blockHeight if chain != ChainID("provi") && tr.validatorConfigs[validator].UseConsumerKey { delAddresss = tr.validatorConfigs[validator].ConsumerDelAddress } + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. bz, err := exec.Command("docker", "exec", tr.containerConfig.InstanceName, tr.chainConfigs[chain].BinaryName, @@ -450,16 +451,6 @@ func (tr TestConfig) getProposal(chain ChainID, proposal uint) Proposal { Chain: chain, StopTime: int(stopTime.Milliseconds()), } - - case "/interchain_security.ccv.provider.v1.EquivocationProposal": - return EquivocationProposal{ - Deposit: uint(deposit), - Status: status, - Height: uint(gjson.Get(string(bz), `messages.0.content.equivocations.0.height`).Uint()), - Power: uint(gjson.Get(string(bz), `messages.0.content.equivocations.0.power`).Uint()), - ConsensusAddress: gjson.Get(string(bz), `messages.0.content.equivocations.0.consensus_address`).String(), - } - case "/cosmos.params.v1beta1.ParameterChangeProposal": return ParamsProposal{ Deposit: uint(deposit), @@ -768,6 +759,83 @@ func (tr TestConfig) curlJsonRPCRequest(method, params, address string) { executeCommandWithVerbosity(cmd, "curlJsonRPCRequest", verbosity) } +// getClientFrozenHeight returns the frozen height for a client with the given client ID +// by querying the hosting chain with the given chainID +func (tc TestConfig) getClientFrozenHeight(chain ChainID, clientID string) clienttypes.Height { + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + cmd := exec.Command("docker", "exec", tc.containerConfig.InstanceName, tc.chainConfigs[ChainID("provi")].BinaryName, + "query", "ibc", "client", "state", clientID, + `--node`, tc.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)} +} + +func (tc TestConfig) getTrustedHeight( + chain ChainID, + clientID string, + index int, +) clienttypes.Height { + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + configureNodeCmd := exec.Command("docker", "exec", tc.containerConfig.InstanceName, "hermes", + "--json", "query", "client", "consensus", "--chain", string(chain), + `--client`, clientID, + ) + + cmdReader, err := configureNodeCmd.StdoutPipe() + if err != nil { + log.Fatal(err) + } + + configureNodeCmd.Stderr = configureNodeCmd.Stdout + + if err := configureNodeCmd.Start(); err != nil { + log.Fatal(err) + } + + scanner := bufio.NewScanner(cmdReader) + + var trustedHeight gjson.Result + // iterate on the relayer's response + // and parse the the command "result" + for scanner.Scan() { + out := scanner.Text() + if len(gjson.Get(out, "result").Array()) > 0 { + trustedHeight = gjson.Get(out, "result").Array()[index] + break + } + } + + revHeight, err := strconv.Atoi(trustedHeight.Get("revision_height").String()) + if err != nil { + log.Fatal(err) + } + + revNumber, err := strconv.Atoi(trustedHeight.Get("revision_number").String()) + if err != nil { + log.Fatal(err) + } + return clienttypes.Height{RevisionHeight: uint64(revHeight), RevisionNumber: uint64(revNumber)} +} + func uintPtr(i uint) *uint { return &i } diff --git a/tests/e2e/state_rapid_test.go b/tests/e2e/state_rapid_test.go index b6ac12f111..3192662d27 100644 --- a/tests/e2e/state_rapid_test.go +++ b/tests/e2e/state_rapid_test.go @@ -172,7 +172,6 @@ func GetProposalGen() *rapid.Generator[Proposal] { gen := rapid.OneOf( GetConsumerAdditionProposalGen().AsAny(), GetConsumerRemovalProposalGen().AsAny(), - GetEquivocationProposalGen().AsAny(), GetTextProposalGen().AsAny(), GetParamsProposalGen().AsAny(), ) @@ -203,18 +202,6 @@ func GetConsumerRemovalProposalGen() *rapid.Generator[ConsumerRemovalProposal] { }) } -func GetEquivocationProposalGen() *rapid.Generator[EquivocationProposal] { - return rapid.Custom(func(t *rapid.T) EquivocationProposal { - return EquivocationProposal{ - Power: rapid.Uint().Draw(t, "Power"), - Height: rapid.Uint().Draw(t, "Height"), - ConsensusAddress: rapid.String().Draw(t, "ConesnsuAddress"), - Deposit: rapid.Uint().Draw(t, "Deposit"), - Status: rapid.String().Draw(t, "Status"), - } - }) -} - func GetTextProposalGen() *rapid.Generator[TextProposal] { return rapid.Custom(func(t *rapid.T) TextProposal { return TextProposal{ diff --git a/tests/e2e/steps.go b/tests/e2e/steps.go index 21db55e3b7..783d976a3a 100644 --- a/tests/e2e/steps.go +++ b/tests/e2e/steps.go @@ -23,12 +23,10 @@ var happyPathSteps = concatSteps( stepsDowntimeWithOptOut("consu"), stepsRedelegate("consu"), stepsDowntime("consu"), - stepsRejectEquivocationProposal("consu", 2), // prop to tombstone bob is rejected - stepsDoubleSignOnProviderAndConsumer("consu"), // carol double signs on provider, bob double signs on consumer - stepsSubmitEquivocationProposal("consu", 2), // now prop to tombstone bob is submitted and accepted + stepsDoubleSignOnProvider("consu"), // carol double signs on provider stepsStartRelayer(), - stepsConsumerRemovalPropNotPassing("consu", 3), // submit removal prop but vote no on it - chain should stay - stepsStopChain("consu", 4), // stop chain + stepsConsumerRemovalPropNotPassing("consu", 2), // submit removal prop but vote no on it - chain should stay + stepsStopChain("consu", 3), // stop chain ) var shortHappyPathSteps = concatSteps( @@ -37,12 +35,10 @@ var shortHappyPathSteps = concatSteps( stepsUnbond("consu"), stepsRedelegateShort("consu"), stepsDowntime("consu"), - stepsRejectEquivocationProposal("consu", 2), // prop to tombstone bob is rejected - stepsDoubleSignOnProviderAndConsumer("consu"), // carol double signs on provider, bob double signs on consumer - stepsSubmitEquivocationProposal("consu", 2), // now prop to tombstone bob is submitted and accepted + stepsDoubleSignOnProvider("consu"), // carol double signs on provider stepsStartRelayer(), - stepsConsumerRemovalPropNotPassing("consu", 3), // submit removal prop but vote no on it - chain should stay - stepsStopChain("consu", 4), // stop chain + stepsConsumerRemovalPropNotPassing("consu", 2), // submit removal prop but vote no on it - chain should stay + stepsStopChain("consu", 3), // stop chain ) var lightClientAttackSteps = concatSteps( @@ -51,9 +47,7 @@ var lightClientAttackSteps = concatSteps( stepsUnbond("consu"), stepsRedelegateShort("consu"), stepsDowntime("consu"), - stepsRejectEquivocationProposal("consu", 2), // prop to tombstone bob is rejected stepsLightClientAttackOnProviderAndConsumer("consu"), // carol double signs on provider, bob double signs on consumer - stepsSubmitEquivocationProposal("consu", 2), // now prop to tombstone bob is submitted and accepted stepsStartRelayer(), stepsConsumerRemovalPropNotPassing("consu", 3), // submit removal prop but vote no on it - chain should stay stepsStopChain("consu", 4), // stop chain @@ -106,3 +100,17 @@ var changeoverSteps = concatSteps( stepsPostChangeoverDelegate("sover"), ) + +var consumerMisbehaviourSteps = concatSteps( + // start provider and consumer chain + stepsStartChainsWithSoftOptOut("consu"), + // make a consumer validator to misbehave and get jailed + stepsCauseConsumerMisbehaviour("consu"), +) + +var consumerDoubleSignSteps = concatSteps( + // start provider and consumer chain + stepsStartChains([]string{"consu"}, false), + // make a consumer validator double sign and get jailed + stepsCauseDoubleSignOnConsumer("consu", "provi"), +) diff --git a/tests/e2e/steps_consumer_misbehaviour.go b/tests/e2e/steps_consumer_misbehaviour.go new file mode 100644 index 0000000000..01d05f658d --- /dev/null +++ b/tests/e2e/steps_consumer_misbehaviour.go @@ -0,0 +1,287 @@ +package main + +import ( + clienttypes "github.com/cosmos/ibc-go/v7/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 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{ + { + // fork the consumer chain by cloning the alice validator node + Action: forkConsumerChainAction{ + ConsumerChain: ChainID(consumerName), + ProviderChain: ChainID("provi"), + Validator: ValidatorID("alice"), + RelayerConfig: forkRelayerConfig, + }, + State: State{}, + }, + // start relayer to detect IBC misbehaviour + { + Action: startRelayerAction{}, + State: State{}, + }, + // run Hermes relayer instance to detect the ICS misbehaviour + // and jail alice on the provider + { + Action: startConsumerEvidenceDetectorAction{ + Chain: ChainID(consumerName), + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 511, + ValidatorID("bob"): 20, + }, + StakedTokens: &map[ValidatorID]uint{ + ValidatorID("alice"): 511000000, + ValidatorID("bob"): 20000000, + }, + }, + ChainID(consumerName): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 511, + ValidatorID("bob"): 20, + }, + }, + }, + }, + { + // update the fork consumer client to create a light client attack + // which should trigger a ICS misbehaviour message + Action: updateLightClientAction{ + Chain: ChainID(consumerName), + ClientID: consumerClientID, + HostChain: ChainID("provi"), + RelayerConfig: forkRelayerConfig, // this relayer config uses the "forked" consumer + }, + State: State{ + ChainID("provi"): ChainState{ + // alice should be jailed on the provider + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 0, + ValidatorID("bob"): 20, + }, + // "alice" should be slashed on the provider, hence representative + // power is 511000000 - 0.05 * 511000000 = 485450000 + StakedTokens: &map[ValidatorID]uint{ + ValidatorID("alice"): 485450000, + ValidatorID("bob"): 20000000, + }, + // The consumer light client should be frozen on the provider + ClientsFrozenHeights: &map[string]clienttypes.Height{ + consumerClientID: { + RevisionNumber: 0, + RevisionHeight: 1, + }, + }, + }, + ChainID(consumerName): ChainState{ + // consumer should not have learned the jailing of alice + // since its light client is frozen on the provider + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 511, + ValidatorID("bob"): 20, + }, + }, + }, + }, + } +} diff --git a/tests/e2e/steps_double_sign.go b/tests/e2e/steps_double_sign.go index 4d69569b29..8039098f36 100644 --- a/tests/e2e/steps_double_sign.go +++ b/tests/e2e/steps_double_sign.go @@ -1,7 +1,7 @@ package main -// Steps that make carol double sign on the provider, and bob double sign on a single consumer -func stepsDoubleSignOnProviderAndConsumer(consumerName string) []Step { +// Steps that make carol double sign on the provider, and this power change propagates to consumer chain `consumerName` +func stepsDoubleSignOnProvider(consumerName string) []Step { return []Step{ { // provider double sign @@ -52,76 +52,95 @@ func stepsDoubleSignOnProviderAndConsumer(consumerName string) []Step { }, }, }, + } +} + +// Steps that make bob double sign on the consumer +func stepsCauseDoubleSignOnConsumer(consumerName, providerName string) []Step { + return []Step{ { - // consumer double sign - // provider will only log the double sign slash - // stepsSubmitEquivocationProposal will cause the double sign slash to be executed Action: doublesignSlashAction{ - Chain: ChainID("consu"), + Chain: ChainID(consumerName), Validator: ValidatorID("bob"), }, State: State{ - ChainID("provi"): ChainState{ + ChainID(providerName): ChainState{ ValPowers: &map[ValidatorID]uint{ - ValidatorID("alice"): 509, + ValidatorID("alice"): 500, ValidatorID("bob"): 500, - ValidatorID("carol"): 0, + ValidatorID("carol"): 500, + }, + StakedTokens: &map[ValidatorID]uint{ + ValidatorID("alice"): 500000000, + ValidatorID("bob"): 500000000, + ValidatorID("carol"): 500000000, }, }, ChainID(consumerName): ChainState{ ValPowers: &map[ValidatorID]uint{ - ValidatorID("alice"): 509, + ValidatorID("alice"): 500, ValidatorID("bob"): 500, - ValidatorID("carol"): 0, + ValidatorID("carol"): 500, }, }, }, }, + // detect the double voting infraction + // and jail and slashing of bob on the provider { - Action: relayPacketsAction{ - ChainA: ChainID("provi"), - ChainB: ChainID(consumerName), - Port: "provider", - Channel: 0, + Action: startConsumerEvidenceDetectorAction{ + Chain: ChainID(consumerName), }, State: State{ - ChainID("provi"): ChainState{ + ChainID(providerName): ChainState{ ValPowers: &map[ValidatorID]uint{ - ValidatorID("alice"): 509, - ValidatorID("bob"): 500, // not tombstoned - ValidatorID("carol"): 0, + ValidatorID("alice"): 500, + ValidatorID("bob"): 0, + ValidatorID("carol"): 500, + }, + // "bob" gets slashed on the provider chain, hence representative + // power is 500000000 - 0.05 * 500000000 = 475000000 + StakedTokens: &map[ValidatorID]uint{ + ValidatorID("alice"): 500000000, + ValidatorID("bob"): 475000000, + ValidatorID("carol"): 500000000, }, }, ChainID(consumerName): ChainState{ ValPowers: &map[ValidatorID]uint{ - ValidatorID("alice"): 509, - ValidatorID("bob"): 500, // not tombstoned - ValidatorID("carol"): 0, + ValidatorID("alice"): 500, + ValidatorID("bob"): 500, + ValidatorID("carol"): 500, }, }, }, }, + // consumer learns about the jailing { - // consumer learns about the double sign Action: relayPacketsAction{ - ChainA: ChainID("provi"), + ChainA: ChainID(providerName), ChainB: ChainID(consumerName), Port: "provider", Channel: 0, }, State: State{ - ChainID("provi"): ChainState{ + ChainID(providerName): ChainState{ ValPowers: &map[ValidatorID]uint{ - ValidatorID("alice"): 509, - ValidatorID("bob"): 500, - ValidatorID("carol"): 0, + ValidatorID("alice"): 500, + ValidatorID("bob"): 0, + ValidatorID("carol"): 500, + }, + StakedTokens: &map[ValidatorID]uint{ + ValidatorID("alice"): 500000000, + ValidatorID("bob"): 475000000, + ValidatorID("carol"): 500000000, }, }, ChainID(consumerName): ChainState{ ValPowers: &map[ValidatorID]uint{ - ValidatorID("alice"): 509, - ValidatorID("bob"): 500, // not tombstoned - ValidatorID("carol"): 0, + ValidatorID("alice"): 500, + ValidatorID("bob"): 0, + ValidatorID("carol"): 500, }, }, }, diff --git a/tests/e2e/steps_light_client_attack.go b/tests/e2e/steps_light_client_attack.go index 284b3fafea..1d9f484b5c 100644 --- a/tests/e2e/steps_light_client_attack.go +++ b/tests/e2e/steps_light_client_attack.go @@ -55,7 +55,6 @@ func stepsLightClientAttackOnProviderAndConsumer(consumerName string) []Step { { // Consumer double sign // Provider will only log the double sign slash - // stepsSubmitEquivocationProposal will cause the double sign slash to be executed Action: lightClientEquivocationAttackAction{ Chain: ChainID(consumerName), Validator: ValidatorID("bob"), diff --git a/tests/e2e/steps_submit_equivocation_proposal.go b/tests/e2e/steps_submit_equivocation_proposal.go deleted file mode 100644 index 243c01f04d..0000000000 --- a/tests/e2e/steps_submit_equivocation_proposal.go +++ /dev/null @@ -1,150 +0,0 @@ -package main - -import "time" - -// submits an invalid equivocation proposal which should be rejected -func stepsRejectEquivocationProposal(consumerName string, propNumber uint) []Step { - return []Step{ - { - // bob submits a proposal to slash himself - Action: submitEquivocationProposalAction{ - Chain: ChainID("consu"), - From: ValidatorID("bob"), - Deposit: 10000001, - Height: 10, - Time: time.Now(), - Power: 500, - Validator: ValidatorID("bob"), - }, - State: State{ - ChainID("provi"): ChainState{ - ValPowers: &map[ValidatorID]uint{ - ValidatorID("alice"): 509, - ValidatorID("bob"): 500, - ValidatorID("carol"): 495, - }, - ValBalances: &map[ValidatorID]uint{ - ValidatorID("bob"): 9500000000, - }, - Proposals: &map[uint]Proposal{ - // proposal does not exist - propNumber: TextProposal{}, - }, - }, - ChainID(consumerName): ChainState{ - ValPowers: &map[ValidatorID]uint{ - ValidatorID("alice"): 509, - ValidatorID("bob"): 500, - ValidatorID("carol"): 495, - }, - }, - }, - }, - } -} - -// submits an equivocation proposal, votes on it, and tomstones the equivocating validator -func stepsSubmitEquivocationProposal(consumerName string, propNumber uint) []Step { - s := []Step{ - { - // bob submits a proposal to slash himself - Action: submitEquivocationProposalAction{ - Chain: ChainID("consu"), - From: ValidatorID("bob"), - Deposit: 10000001, - Height: 10, - Time: time.Now(), // not sure what time in equivocations means - Power: 500, - Validator: ValidatorID("bob"), - }, - State: State{ - ChainID("provi"): ChainState{ - ValPowers: &map[ValidatorID]uint{ - ValidatorID("alice"): 509, - ValidatorID("bob"): 500, - ValidatorID("carol"): 0, - }, - ValBalances: &map[ValidatorID]uint{ - ValidatorID("bob"): 9489999999, - }, - Proposals: &map[uint]Proposal{ - propNumber: EquivocationProposal{ - Deposit: 10000001, - Status: "PROPOSAL_STATUS_VOTING_PERIOD", - ConsensusAddress: "cosmosvalcons1nx7n5uh0ztxsynn4sje6eyq2ud6rc6klc96w39", - Power: 500, - Height: 10, - }, - }, - }, - ChainID(consumerName): ChainState{ - ValPowers: &map[ValidatorID]uint{ - ValidatorID("alice"): 509, - ValidatorID("bob"): 500, - ValidatorID("carol"): 0, - }, - }, - }, - }, - { - Action: voteGovProposalAction{ - Chain: ChainID("provi"), - From: []ValidatorID{ValidatorID("alice"), ValidatorID("bob"), ValidatorID("carol")}, - Vote: []string{"yes", "yes", "yes"}, - PropNumber: propNumber, - }, - State: State{ - ChainID("provi"): ChainState{ - ValPowers: &map[ValidatorID]uint{ - ValidatorID("alice"): 509, - ValidatorID("bob"): 0, // bob is tombstoned after proposal passes - ValidatorID("carol"): 0, - }, - Proposals: &map[uint]Proposal{ - propNumber: EquivocationProposal{ - Deposit: 10000001, - Status: "PROPOSAL_STATUS_PASSED", - ConsensusAddress: "cosmosvalcons1nx7n5uh0ztxsynn4sje6eyq2ud6rc6klc96w39", - Power: 500, - Height: 10, - }, - }, - }, - ChainID(consumerName): ChainState{ - ValPowers: &map[ValidatorID]uint{ - ValidatorID("alice"): 509, - ValidatorID("bob"): 500, // slash not reflected in consumer chain - ValidatorID("carol"): 0, - }, - }, - }, - }, - { - // relay power change to consumer1 - Action: relayPacketsAction{ - ChainA: ChainID("provi"), - ChainB: ChainID(consumerName), - Port: "provider", - Channel: 0, - }, - State: State{ - ChainID("provi"): ChainState{ - ValPowers: &map[ValidatorID]uint{ - ValidatorID("alice"): 509, - ValidatorID("bob"): 0, - ValidatorID("carol"): 0, - }, - }, - ChainID(consumerName): ChainState{ - ValPowers: &map[ValidatorID]uint{ - ValidatorID("alice"): 509, - ValidatorID("bob"): 0, // slash relayed to consumer chain - ValidatorID("carol"): 0, - }, - }, - }, - }, - } - - return s -} diff --git a/tests/e2e/testnet-scripts/fork-consumer.sh b/tests/e2e/testnet-scripts/fork-consumer.sh new file mode 100644 index 0000000000..7c12438b71 --- /dev/null +++ b/tests/e2e/testnet-scripts/fork-consumer.sh @@ -0,0 +1,114 @@ +#!/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 88c8455e51..e3e6b6054f 100644 --- a/tests/e2e/testnet-scripts/start-chain.sh +++ b/tests/e2e/testnet-scripts/start-chain.sh @@ -257,8 +257,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/tests/e2e/trace_handlers_test.go b/tests/e2e/trace_handlers_test.go index 725496c51f..024daae450 100644 --- a/tests/e2e/trace_handlers_test.go +++ b/tests/e2e/trace_handlers_test.go @@ -82,6 +82,8 @@ func TestWriteExamples(t *testing.T) { "shorthappy": {shortHappyPathSteps}, "democracyRewardsSteps": {democracyRewardsSteps}, "changeover": {changeoverSteps}, + "consumer-misbehaviour": {consumerMisbehaviourSteps}, + "consumer-double-sign": {consumerDoubleSignSteps}, } dir := "tracehandler_testdata" @@ -131,7 +133,7 @@ func TestMarshalAndUnmarshalChainState(t *testing.T) { }, }, }}, - "consuemr removal proposal": {ChainState{ + "consumer removal proposal": {ChainState{ Proposals: &map[uint]Proposal{ 5: ConsumerRemovalProposal{ Deposit: 10000001, @@ -159,25 +161,6 @@ func TestMarshalAndUnmarshalChainState(t *testing.T) { 10: TextProposal{}, }, }}, - "equivocation-proposal": {ChainState{ - ValPowers: &map[ValidatorID]uint{ - ValidatorID("alice"): 509, - ValidatorID("bob"): 500, - ValidatorID("carol"): 0, - }, - ValBalances: &map[ValidatorID]uint{ - ValidatorID("bob"): 9489999999, - }, - Proposals: &map[uint]Proposal{ - 5: EquivocationProposal{ - Deposit: 10000001, - Status: "PROPOSAL_STATUS_VOTING_PERIOD", - ConsensusAddress: "cosmosvalcons1nx7n5uh0ztxsynn4sje6eyq2ud6rc6klc96w39", - Power: 500, - Height: 10, - }, - }, - }}, } for name, tc := range tests { diff --git a/tests/e2e/tracehandler_testdata/changeover.json b/tests/e2e/tracehandler_testdata/changeover.json index 8f90a114e3..cafe8aa69e 100644 --- a/tests/e2e/tracehandler_testdata/changeover.json +++ b/tests/e2e/tracehandler_testdata/changeover.json @@ -26,6 +26,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -54,6 +55,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -80,7 +82,7 @@ } ], "GenesisChanges": "", - "SkipGentx": false + "IsConsumer": false }, "State": { "provi": { @@ -98,6 +100,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -143,6 +146,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { "1": { "RawProposal": { @@ -183,6 +187,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { "1": { "RawProposal": { @@ -236,6 +241,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { "1": { "RawProposal": { @@ -284,6 +290,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { "1": { "RawProposal": { @@ -341,6 +348,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "sover": { @@ -358,6 +366,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -407,6 +416,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -435,6 +445,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "sover": { @@ -452,6 +463,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -480,6 +492,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -506,6 +519,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -534,6 +548,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "sover": { @@ -551,6 +566,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -579,6 +595,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } diff --git a/tests/e2e/tracehandler_testdata/consumer-double-sign.json b/tests/e2e/tracehandler_testdata/consumer-double-sign.json new file mode 100644 index 0000000000..d6d1fc7086 --- /dev/null +++ b/tests/e2e/tracehandler_testdata/consumer-double-sign.json @@ -0,0 +1,452 @@ +[ + { + "ActionType": "main.StartChainAction", + "Action": { + "Chain": "provi", + "Validators": [ + { + "Id": "bob", + "Allocation": 10000000000, + "Stake": 500000000 + }, + { + "Id": "alice", + "Allocation": 10000000000, + "Stake": 500000000 + }, + { + "Id": "carol", + "Allocation": 10000000000, + "Stake": 500000000 + } + ], + "GenesisChanges": "", + "IsConsumer": false + }, + "State": { + "provi": { + "ValBalances": { + "alice": 9500000000, + "bob": 9500000000, + "carol": 9500000000 + }, + "ValPowers": null, + "StakedTokens": null, + "Params": null, + "Rewards": null, + "ConsumerChains": null, + "AssignedKeys": null, + "ProviderKeys": null, + "ConsumerPendingPacketQueueSize": null, + "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, + "Proposals": null + } + } + }, + { + "ActionType": "main.submitConsumerAdditionProposalAction", + "Action": { + "PreCCV": false, + "Chain": "provi", + "From": "alice", + "Deposit": 10000001, + "ConsumerChain": "consu", + "SpawnTime": 0, + "InitialHeight": { + "revision_height": 1 + }, + "DistributionChannel": "" + }, + "State": { + "provi": { + "ValBalances": { + "alice": 9489999999, + "bob": 9500000000 + }, + "ValPowers": null, + "StakedTokens": null, + "Params": null, + "Rewards": null, + "ConsumerChains": null, + "AssignedKeys": null, + "ProviderKeys": null, + "ConsumerPendingPacketQueueSize": null, + "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, + "Proposals": { + "1": { + "RawProposal": { + "Deposit": 10000001, + "Chain": "consu", + "SpawnTime": 0, + "InitialHeight": { + "revision_height": 1 + }, + "Status": "PROPOSAL_STATUS_VOTING_PERIOD" + }, + "Type": "main.ConsumerAdditionProposal" + } + } + } + } + }, + { + "ActionType": "main.assignConsumerPubKeyAction", + "Action": { + "Chain": "consu", + "Validator": "carol", + "ConsumerPubkey": "{\"@type\":\"/cosmos.crypto.ed25519.PubKey\",\"key\":\"Ui5Gf1+mtWUdH8u3xlmzdKID+F3PK0sfXZ73GZ6q6is=\"}", + "ReconfigureNode": false, + "ExpectError": false, + "ExpectedError": "" + }, + "State": { + "consu": { + "ValBalances": null, + "ValPowers": null, + "StakedTokens": null, + "Params": null, + "Rewards": null, + "ConsumerChains": null, + "AssignedKeys": { + "carol": "cosmosvalcons1kswr5sq599365kcjmhgufevfps9njf43e4lwdk" + }, + "ProviderKeys": { + "carol": "cosmosvalcons1ezyrq65s3gshhx5585w6mpusq3xsj3ayzf4uv6" + }, + "ConsumerPendingPacketQueueSize": null, + "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, + "Proposals": null + } + } + }, + { + "ActionType": "main.assignConsumerPubKeyAction", + "Action": { + "Chain": "consu", + "Validator": "carol", + "ConsumerPubkey": "{\"@type\":\"/cosmos.crypto.ed25519.PubKey\",\"key\":\"Ui5Gf1+mtWUdH8u3xlmzdKID+F3PK0sfXZ73GZ6q6is=\"}", + "ReconfigureNode": false, + "ExpectError": true, + "ExpectedError": "a validator has assigned the consumer key already: consumer key is already in use by a validator" + }, + "State": {} + }, + { + "ActionType": "main.assignConsumerPubKeyAction", + "Action": { + "Chain": "consu", + "Validator": "bob", + "ConsumerPubkey": "{\"@type\":\"/cosmos.crypto.ed25519.PubKey\",\"key\":\"Ui5Gf1+mtWUdH8u3xlmzdKID+F3PK0sfXZ73GZ6q6is=\"}", + "ReconfigureNode": false, + "ExpectError": true, + "ExpectedError": "a validator has assigned the consumer key already: consumer key is already in use by a validator" + }, + "State": { + "consu": { + "ValBalances": null, + "ValPowers": null, + "StakedTokens": null, + "Params": null, + "Rewards": null, + "ConsumerChains": null, + "AssignedKeys": { + "bob": "", + "carol": "cosmosvalcons1kswr5sq599365kcjmhgufevfps9njf43e4lwdk" + }, + "ProviderKeys": { + "carol": "cosmosvalcons1ezyrq65s3gshhx5585w6mpusq3xsj3ayzf4uv6" + }, + "ConsumerPendingPacketQueueSize": null, + "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, + "Proposals": null + } + } + }, + { + "ActionType": "main.voteGovProposalAction", + "Action": { + "Chain": "provi", + "From": [ + "alice", + "bob", + "carol" + ], + "Vote": [ + "yes", + "yes", + "yes" + ], + "PropNumber": 1 + }, + "State": { + "provi": { + "ValBalances": { + "alice": 9500000000, + "bob": 9500000000 + }, + "ValPowers": null, + "StakedTokens": null, + "Params": null, + "Rewards": null, + "ConsumerChains": null, + "AssignedKeys": null, + "ProviderKeys": null, + "ConsumerPendingPacketQueueSize": null, + "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, + "Proposals": { + "1": { + "RawProposal": { + "Deposit": 10000001, + "Chain": "consu", + "SpawnTime": 0, + "InitialHeight": { + "revision_height": 1 + }, + "Status": "PROPOSAL_STATUS_PASSED" + }, + "Type": "main.ConsumerAdditionProposal" + } + } + } + } + }, + { + "ActionType": "main.startConsumerChainAction", + "Action": { + "ConsumerChain": "consu", + "ProviderChain": "provi", + "Validators": [ + { + "Id": "bob", + "Allocation": 10000000000, + "Stake": 500000000 + }, + { + "Id": "alice", + "Allocation": 10000000000, + "Stake": 500000000 + }, + { + "Id": "carol", + "Allocation": 10000000000, + "Stake": 500000000 + } + ], + "GenesisChanges": ".app_state.ccvconsumer.params.soft_opt_out_threshold = \"0.05\"" + }, + "State": { + "consu": { + "ValBalances": { + "alice": 10000000000, + "bob": 10000000000, + "carol": 10000000000 + }, + "ValPowers": null, + "StakedTokens": null, + "Params": null, + "Rewards": null, + "ConsumerChains": null, + "AssignedKeys": null, + "ProviderKeys": null, + "ConsumerPendingPacketQueueSize": null, + "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, + "Proposals": null + }, + "provi": { + "ValBalances": { + "alice": 9500000000, + "bob": 9500000000, + "carol": 9500000000 + }, + "ValPowers": null, + "StakedTokens": null, + "Params": null, + "Rewards": null, + "ConsumerChains": null, + "AssignedKeys": null, + "ProviderKeys": null, + "ConsumerPendingPacketQueueSize": null, + "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, + "Proposals": null + } + } + }, + { + "ActionType": "main.addIbcConnectionAction", + "Action": { + "ChainA": "consu", + "ChainB": "provi", + "ClientA": 0, + "ClientB": 0 + }, + "State": {} + }, + { + "ActionType": "main.addIbcChannelAction", + "Action": { + "ChainA": "consu", + "ChainB": "provi", + "ConnectionA": 0, + "PortA": "consumer", + "PortB": "provider", + "Order": "ordered", + "Version": "" + }, + "State": {} + }, + { + "ActionType": "main.doublesignSlashAction", + "Action": { + "Validator": "bob", + "Chain": "consu" + }, + "State": { + "consu": { + "ValBalances": null, + "ValPowers": { + "alice": 500, + "bob": 500, + "carol": 500 + }, + "StakedTokens": null, + "Params": null, + "Rewards": null, + "ConsumerChains": null, + "AssignedKeys": null, + "ProviderKeys": null, + "ConsumerPendingPacketQueueSize": null, + "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, + "Proposals": null + }, + "provi": { + "ValBalances": null, + "ValPowers": { + "alice": 500, + "bob": 500, + "carol": 500 + }, + "StakedTokens": { + "alice": 500000000, + "bob": 500000000, + "carol": 500000000 + }, + "Params": null, + "Rewards": null, + "ConsumerChains": null, + "AssignedKeys": null, + "ProviderKeys": null, + "ConsumerPendingPacketQueueSize": null, + "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, + "Proposals": null + } + } + }, + { + "ActionType": "main.startConsumerEvidenceDetectorAction", + "Action": { + "Chain": "consu" + }, + "State": { + "consu": { + "ValBalances": null, + "ValPowers": { + "alice": 500, + "bob": 500, + "carol": 500 + }, + "StakedTokens": null, + "Params": null, + "Rewards": null, + "ConsumerChains": null, + "AssignedKeys": null, + "ProviderKeys": null, + "ConsumerPendingPacketQueueSize": null, + "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, + "Proposals": null + }, + "provi": { + "ValBalances": null, + "ValPowers": { + "alice": 500, + "bob": 0, + "carol": 500 + }, + "StakedTokens": { + "alice": 500000000, + "bob": 475000000, + "carol": 500000000 + }, + "Params": null, + "Rewards": null, + "ConsumerChains": null, + "AssignedKeys": null, + "ProviderKeys": null, + "ConsumerPendingPacketQueueSize": null, + "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, + "Proposals": null + } + } + }, + { + "ActionType": "main.relayPacketsAction", + "Action": { + "ChainA": "provi", + "ChainB": "consu", + "Port": "provider", + "Channel": 0 + }, + "State": { + "consu": { + "ValBalances": null, + "ValPowers": { + "alice": 500, + "bob": 0, + "carol": 500 + }, + "StakedTokens": null, + "Params": null, + "Rewards": null, + "ConsumerChains": null, + "AssignedKeys": null, + "ProviderKeys": null, + "ConsumerPendingPacketQueueSize": null, + "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, + "Proposals": null + }, + "provi": { + "ValBalances": null, + "ValPowers": { + "alice": 500, + "bob": 0, + "carol": 500 + }, + "StakedTokens": { + "alice": 500000000, + "bob": 475000000, + "carol": 500000000 + }, + "Params": null, + "Rewards": null, + "ConsumerChains": null, + "AssignedKeys": null, + "ProviderKeys": null, + "ConsumerPendingPacketQueueSize": null, + "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, + "Proposals": null + } + } + } +] \ No newline at end of file diff --git a/tests/e2e/tracehandler_testdata/consumer-misbehaviour.json b/tests/e2e/tracehandler_testdata/consumer-misbehaviour.json new file mode 100644 index 0000000000..028637934f --- /dev/null +++ b/tests/e2e/tracehandler_testdata/consumer-misbehaviour.json @@ -0,0 +1,424 @@ +[ + { + "ActionType": "main.StartChainAction", + "Action": { + "Chain": "provi", + "Validators": [ + { + "Id": "alice", + "Allocation": 10000000000, + "Stake": 500000000 + }, + { + "Id": "bob", + "Allocation": 10000000000, + "Stake": 20000000 + } + ], + "GenesisChanges": "", + "IsConsumer": false + }, + "State": { + "provi": { + "ValBalances": { + "alice": 9500000000, + "bob": 9980000000 + }, + "ValPowers": null, + "StakedTokens": null, + "Params": null, + "Rewards": null, + "ConsumerChains": null, + "AssignedKeys": null, + "ProviderKeys": null, + "ConsumerPendingPacketQueueSize": null, + "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, + "Proposals": null + } + } + }, + { + "ActionType": "main.submitConsumerAdditionProposalAction", + "Action": { + "PreCCV": false, + "Chain": "provi", + "From": "alice", + "Deposit": 10000001, + "ConsumerChain": "consu", + "SpawnTime": 0, + "InitialHeight": { + "revision_height": 1 + }, + "DistributionChannel": "" + }, + "State": { + "provi": { + "ValBalances": { + "alice": 9489999999, + "bob": 9980000000 + }, + "ValPowers": null, + "StakedTokens": null, + "Params": null, + "Rewards": null, + "ConsumerChains": null, + "AssignedKeys": null, + "ProviderKeys": null, + "ConsumerPendingPacketQueueSize": null, + "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, + "Proposals": { + "1": { + "RawProposal": { + "Deposit": 10000001, + "Chain": "consu", + "SpawnTime": 0, + "InitialHeight": { + "revision_height": 1 + }, + "Status": "PROPOSAL_STATUS_VOTING_PERIOD" + }, + "Type": "main.ConsumerAdditionProposal" + } + } + } + } + }, + { + "ActionType": "main.assignConsumerPubKeyAction", + "Action": { + "Chain": "consu", + "Validator": "alice", + "ConsumerPubkey": "{\"@type\":\"/cosmos.crypto.ed25519.PubKey\",\"key\":\"ujY14AgopV907IYgPAk/5x8c9267S4fQf89nyeCPTes=\"}", + "ReconfigureNode": false, + "ExpectError": false, + "ExpectedError": "" + }, + "State": { + "consu": { + "ValBalances": null, + "ValPowers": null, + "StakedTokens": null, + "Params": null, + "Rewards": null, + "ConsumerChains": null, + "AssignedKeys": { + "alice": "cosmosvalcons1muys5jyqk4xd27e208nym85kn0t4zjcfeu63fe" + }, + "ProviderKeys": { + "alice": "cosmosvalcons1qmq08eruchr5sf5s3rwz7djpr5a25f7xw4mceq" + }, + "ConsumerPendingPacketQueueSize": null, + "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, + "Proposals": null + } + } + }, + { + "ActionType": "main.voteGovProposalAction", + "Action": { + "Chain": "provi", + "From": [ + "alice", + "bob" + ], + "Vote": [ + "yes", + "yes" + ], + "PropNumber": 1 + }, + "State": { + "provi": { + "ValBalances": { + "alice": 9500000000, + "bob": 9980000000 + }, + "ValPowers": null, + "StakedTokens": null, + "Params": null, + "Rewards": null, + "ConsumerChains": null, + "AssignedKeys": null, + "ProviderKeys": null, + "ConsumerPendingPacketQueueSize": null, + "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, + "Proposals": { + "1": { + "RawProposal": { + "Deposit": 10000001, + "Chain": "consu", + "SpawnTime": 0, + "InitialHeight": { + "revision_height": 1 + }, + "Status": "PROPOSAL_STATUS_PASSED" + }, + "Type": "main.ConsumerAdditionProposal" + } + } + } + } + }, + { + "ActionType": "main.startConsumerChainAction", + "Action": { + "ConsumerChain": "consu", + "ProviderChain": "provi", + "Validators": [ + { + "Id": "alice", + "Allocation": 10000000000, + "Stake": 500000000 + } + ], + "GenesisChanges": ".app_state.ccvconsumer.params.soft_opt_out_threshold = \"0.05\"" + }, + "State": { + "consu": { + "ValBalances": { + "alice": 10000000000 + }, + "ValPowers": null, + "StakedTokens": null, + "Params": null, + "Rewards": null, + "ConsumerChains": null, + "AssignedKeys": null, + "ProviderKeys": null, + "ConsumerPendingPacketQueueSize": null, + "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, + "Proposals": null + }, + "provi": { + "ValBalances": { + "alice": 9500000000, + "bob": 9980000000 + }, + "ValPowers": null, + "StakedTokens": null, + "Params": null, + "Rewards": null, + "ConsumerChains": null, + "AssignedKeys": null, + "ProviderKeys": null, + "ConsumerPendingPacketQueueSize": null, + "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, + "Proposals": null + } + } + }, + { + "ActionType": "main.addIbcConnectionAction", + "Action": { + "ChainA": "consu", + "ChainB": "provi", + "ClientA": 0, + "ClientB": 0 + }, + "State": {} + }, + { + "ActionType": "main.addIbcChannelAction", + "Action": { + "ChainA": "consu", + "ChainB": "provi", + "ConnectionA": 0, + "PortA": "consumer", + "PortB": "provider", + "Order": "ordered", + "Version": "" + }, + "State": {} + }, + { + "ActionType": "main.delegateTokensAction", + "Action": { + "Chain": "provi", + "From": "alice", + "To": "alice", + "Amount": 11000000 + }, + "State": { + "consu": { + "ValBalances": null, + "ValPowers": { + "alice": 500, + "bob": 20 + }, + "StakedTokens": null, + "Params": null, + "Rewards": null, + "ConsumerChains": null, + "AssignedKeys": null, + "ProviderKeys": null, + "ConsumerPendingPacketQueueSize": null, + "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, + "Proposals": null + }, + "provi": { + "ValBalances": null, + "ValPowers": { + "alice": 511, + "bob": 20 + }, + "StakedTokens": null, + "Params": null, + "Rewards": null, + "ConsumerChains": null, + "AssignedKeys": null, + "ProviderKeys": null, + "ConsumerPendingPacketQueueSize": null, + "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, + "Proposals": null + } + } + }, + { + "ActionType": "main.relayPacketsAction", + "Action": { + "ChainA": "provi", + "ChainB": "consu", + "Port": "provider", + "Channel": 0 + }, + "State": { + "consu": { + "ValBalances": null, + "ValPowers": { + "alice": 511, + "bob": 20 + }, + "StakedTokens": null, + "Params": null, + "Rewards": null, + "ConsumerChains": null, + "AssignedKeys": null, + "ProviderKeys": null, + "ConsumerPendingPacketQueueSize": null, + "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, + "Proposals": null + } + } + }, + { + "ActionType": "main.forkConsumerChainAction", + "Action": { + "ConsumerChain": "consu", + "ProviderChain": "provi", + "Validator": "alice", + "RelayerConfig": "/root/.hermes/config_fork.toml" + }, + "State": {} + }, + { + "ActionType": "main.startRelayerAction", + "Action": {}, + "State": {} + }, + { + "ActionType": "main.startConsumerEvidenceDetectorAction", + "Action": { + "Chain": "consu" + }, + "State": { + "consu": { + "ValBalances": null, + "ValPowers": { + "alice": 511, + "bob": 20 + }, + "StakedTokens": null, + "Params": null, + "Rewards": null, + "ConsumerChains": null, + "AssignedKeys": null, + "ProviderKeys": null, + "ConsumerPendingPacketQueueSize": null, + "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, + "Proposals": null + }, + "provi": { + "ValBalances": null, + "ValPowers": { + "alice": 511, + "bob": 20 + }, + "StakedTokens": { + "alice": 511000000, + "bob": 20000000 + }, + "Params": null, + "Rewards": null, + "ConsumerChains": null, + "AssignedKeys": null, + "ProviderKeys": null, + "ConsumerPendingPacketQueueSize": null, + "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, + "Proposals": null + } + } + }, + { + "ActionType": "main.updateLightClientAction", + "Action": { + "Chain": "consu", + "HostChain": "provi", + "RelayerConfig": "/root/.hermes/config_fork.toml", + "ClientID": "07-tendermint-0" + }, + "State": { + "consu": { + "ValBalances": null, + "ValPowers": { + "alice": 511, + "bob": 20 + }, + "StakedTokens": null, + "Params": null, + "Rewards": null, + "ConsumerChains": null, + "AssignedKeys": null, + "ProviderKeys": null, + "ConsumerPendingPacketQueueSize": null, + "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, + "Proposals": null + }, + "provi": { + "ValBalances": null, + "ValPowers": { + "alice": 0, + "bob": 20 + }, + "StakedTokens": { + "alice": 485450000, + "bob": 20000000 + }, + "Params": null, + "Rewards": null, + "ConsumerChains": null, + "AssignedKeys": null, + "ProviderKeys": null, + "ConsumerPendingPacketQueueSize": null, + "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": { + "07-tendermint-0": { + "revision_height": 1 + } + }, + "Proposals": null + } + } + } +] \ No newline at end of file diff --git a/tests/e2e/tracehandler_testdata/democracy.json b/tests/e2e/tracehandler_testdata/democracy.json index a696258ab9..3a9ab84a96 100644 --- a/tests/e2e/tracehandler_testdata/democracy.json +++ b/tests/e2e/tracehandler_testdata/democracy.json @@ -21,7 +21,7 @@ } ], "GenesisChanges": "", - "SkipGentx": false + "IsConsumer": false }, "State": { "provi": { @@ -39,6 +39,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -72,6 +73,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { "1": { "RawProposal": { @@ -115,6 +117,7 @@ }, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -158,6 +161,7 @@ }, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -193,6 +197,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { "1": { "RawProposal": { @@ -250,6 +255,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -267,6 +273,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -332,6 +339,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -349,6 +357,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -376,6 +385,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -404,6 +414,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -431,6 +442,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -471,6 +483,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -510,6 +523,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -532,13 +546,20 @@ }, "ValPowers": null, "StakedTokens": null, - "Params": null, + "Params": [ + { + "Subspace": "transfer", + "Key": "SendEnabled", + "Value": "false" + } + ], "Rewards": null, "ConsumerChains": null, "AssignedKeys": null, "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { "1": { "RawProposal": { @@ -589,6 +610,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { "1": { "RawProposal": { @@ -632,6 +654,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": [], + "ClientsFrozenHeights": null, "Proposals": null } } @@ -655,6 +678,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": [], + "ClientsFrozenHeights": null, "Proposals": null } } @@ -689,6 +713,7 @@ "RegisteredConsumerRewardDenoms": [ "ibc/3C3D7B3BE4ECC85A0E5B52A3AEC3B7DFC2AA9CA47C37821E57020D6807043BE9" ], + "ClientsFrozenHeights": null, "Proposals": null } } @@ -721,6 +746,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -747,6 +773,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -764,6 +791,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -792,6 +820,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -809,6 +838,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -837,6 +867,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -863,6 +894,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -880,6 +912,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -911,6 +944,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } diff --git a/tests/e2e/tracehandler_testdata/democracyRewardsSteps.json b/tests/e2e/tracehandler_testdata/democracyRewardsSteps.json index aa79dcd5d8..74df5293b9 100644 --- a/tests/e2e/tracehandler_testdata/democracyRewardsSteps.json +++ b/tests/e2e/tracehandler_testdata/democracyRewardsSteps.json @@ -21,7 +21,7 @@ } ], "GenesisChanges": "", - "SkipGentx": false + "IsConsumer": false }, "State": { "provi": { @@ -39,6 +39,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -72,6 +73,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { "1": { "RawProposal": { @@ -115,6 +117,7 @@ }, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -158,6 +161,7 @@ }, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -193,6 +197,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { "1": { "RawProposal": { @@ -250,6 +255,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -267,6 +273,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -332,6 +339,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -349,6 +357,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -376,6 +385,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -404,6 +414,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -431,6 +442,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -471,6 +483,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -510,6 +523,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -545,6 +559,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { "1": { "RawProposal": { @@ -595,6 +610,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { "1": { "RawProposal": { @@ -638,6 +654,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": [], + "ClientsFrozenHeights": null, "Proposals": null } } @@ -661,6 +678,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": [], + "ClientsFrozenHeights": null, "Proposals": null } } @@ -695,6 +713,7 @@ "RegisteredConsumerRewardDenoms": [ "ibc/3C3D7B3BE4ECC85A0E5B52A3AEC3B7DFC2AA9CA47C37821E57020D6807043BE9" ], + "ClientsFrozenHeights": null, "Proposals": null } } @@ -727,6 +746,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -753,6 +773,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -770,6 +791,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -798,6 +820,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -815,6 +838,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -843,6 +867,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -869,6 +894,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -886,6 +912,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -917,6 +944,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } diff --git a/tests/e2e/tracehandler_testdata/happyPath.json b/tests/e2e/tracehandler_testdata/happyPath.json index c7b891777d..7c26f5c274 100644 --- a/tests/e2e/tracehandler_testdata/happyPath.json +++ b/tests/e2e/tracehandler_testdata/happyPath.json @@ -21,7 +21,7 @@ } ], "GenesisChanges": "", - "SkipGentx": false + "IsConsumer": false }, "State": { "provi": { @@ -39,6 +39,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -72,6 +73,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { "1": { "RawProposal": { @@ -115,6 +117,7 @@ }, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -158,6 +161,7 @@ }, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -193,6 +197,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { "1": { "RawProposal": { @@ -250,6 +255,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -267,6 +273,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -318,6 +325,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -335,6 +343,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -362,6 +371,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -390,6 +400,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -417,6 +428,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -453,6 +465,7 @@ }, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -470,6 +483,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -504,6 +518,7 @@ }, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -521,6 +536,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -549,6 +565,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -566,6 +583,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -594,6 +612,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -622,6 +641,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -639,6 +659,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -667,6 +688,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -695,6 +717,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -712,6 +735,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -740,6 +764,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -769,6 +794,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -786,6 +812,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -814,6 +841,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -840,6 +868,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -857,6 +886,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -885,6 +915,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -902,6 +933,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -931,6 +963,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -948,6 +981,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -976,6 +1010,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1002,6 +1037,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -1019,6 +1055,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1046,6 +1083,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -1063,6 +1101,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1091,6 +1130,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1117,6 +1157,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -1134,6 +1175,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1162,6 +1204,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1188,6 +1231,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -1205,6 +1249,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1233,6 +1278,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1259,6 +1305,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -1276,6 +1323,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1304,70 +1352,11 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } }, - { - "ActionType": "main.submitEquivocationProposalAction", - "Action": { - "Chain": "consu", - "Height": 10, - "Time": "2023-10-04T12:14:14.883367+02:00", - "Power": 500, - "Validator": "bob", - "Deposit": 10000001, - "From": "bob" - }, - "State": { - "consu": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 495 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - }, - "provi": { - "ValBalances": { - "bob": 9500000000 - }, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 495 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": { - "2": { - "RawProposal": { - "Title": "", - "Description": "", - "Deposit": 0, - "Status": "" - }, - "Type": "main.TextProposal" - } - } - } - } - }, { "ActionType": "main.doublesignSlashAction", "Action": { @@ -1390,6 +1379,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -1407,6 +1397,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1435,6 +1426,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -1452,309 +1444,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, - "Proposals": null - } - } - }, - { - "ActionType": "main.doublesignSlashAction", - "Action": { - "Validator": "bob", - "Chain": "consu" - }, - "State": { - "consu": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - }, - "provi": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - } - } - }, - { - "ActionType": "main.relayPacketsAction", - "Action": { - "ChainA": "provi", - "ChainB": "consu", - "Port": "provider", - "Channel": 0 - }, - "State": { - "consu": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - }, - "provi": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - } - } - }, - { - "ActionType": "main.relayPacketsAction", - "Action": { - "ChainA": "provi", - "ChainB": "consu", - "Port": "provider", - "Channel": 0 - }, - "State": { - "consu": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - }, - "provi": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - } - } - }, - { - "ActionType": "main.submitEquivocationProposalAction", - "Action": { - "Chain": "consu", - "Height": 10, - "Time": "2023-10-04T12:14:14.88337+02:00", - "Power": 500, - "Validator": "bob", - "Deposit": 10000001, - "From": "bob" - }, - "State": { - "consu": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - }, - "provi": { - "ValBalances": { - "bob": 9489999999 - }, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": { - "2": { - "RawProposal": { - "Height": 10, - "Power": 500, - "ConsensusAddress": "cosmosvalcons1nx7n5uh0ztxsynn4sje6eyq2ud6rc6klc96w39", - "Deposit": 10000001, - "Status": "PROPOSAL_STATUS_VOTING_PERIOD" - }, - "Type": "main.EquivocationProposal" - } - } - } - } - }, - { - "ActionType": "main.voteGovProposalAction", - "Action": { - "Chain": "provi", - "From": [ - "alice", - "bob", - "carol" - ], - "Vote": [ - "yes", - "yes", - "yes" - ], - "PropNumber": 2 - }, - "State": { - "consu": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - }, - "provi": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 0, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": { - "2": { - "RawProposal": { - "Height": 10, - "Power": 500, - "ConsensusAddress": "cosmosvalcons1nx7n5uh0ztxsynn4sje6eyq2ud6rc6klc96w39", - "Deposit": 10000001, - "Status": "PROPOSAL_STATUS_PASSED" - }, - "Type": "main.EquivocationProposal" - } - } - } - } - }, - { - "ActionType": "main.relayPacketsAction", - "Action": { - "ChainA": "provi", - "ChainB": "consu", - "Port": "provider", - "Channel": 0 - }, - "State": { - "consu": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 0, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - }, - "provi": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 0, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1789,8 +1479,9 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { - "3": { + "2": { "RawProposal": { "Deposit": 10000001, "Chain": "consu", @@ -1817,7 +1508,7 @@ "no", "no" ], - "PropNumber": 3 + "PropNumber": 2 }, "State": { "provi": { @@ -1835,8 +1526,9 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { - "3": { + "2": { "RawProposal": { "Deposit": 10000001, "Chain": "consu", @@ -1874,8 +1566,9 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { - "4": { + "3": { "RawProposal": { "Deposit": 10000001, "Chain": "consu", @@ -1902,7 +1595,7 @@ "yes", "yes" ], - "PropNumber": 4 + "PropNumber": 3 }, "State": { "provi": { @@ -1918,8 +1611,9 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { - "4": { + "3": { "RawProposal": { "Deposit": 10000001, "Chain": "consu", diff --git a/tests/e2e/tracehandler_testdata/multipleConsumers.json b/tests/e2e/tracehandler_testdata/multipleConsumers.json index 1cb46af06c..a6b31d15e8 100644 --- a/tests/e2e/tracehandler_testdata/multipleConsumers.json +++ b/tests/e2e/tracehandler_testdata/multipleConsumers.json @@ -21,7 +21,7 @@ } ], "GenesisChanges": "", - "SkipGentx": false + "IsConsumer": false }, "State": { "provi": { @@ -39,6 +39,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -72,6 +73,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { "1": { "RawProposal": { @@ -115,6 +117,7 @@ }, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -158,6 +161,7 @@ }, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -193,6 +197,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { "1": { "RawProposal": { @@ -250,6 +255,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -267,6 +273,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -323,6 +330,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { "2": { "RawProposal": { @@ -366,6 +374,7 @@ }, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -409,6 +418,7 @@ }, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -444,6 +454,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { "2": { "RawProposal": { @@ -501,6 +512,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -518,6 +530,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -569,6 +582,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -586,6 +600,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -603,6 +618,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -631,6 +647,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -648,6 +665,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -665,6 +683,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -693,6 +712,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -710,6 +730,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -727,6 +748,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -755,6 +777,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -772,6 +795,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -789,6 +813,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -817,6 +842,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -834,6 +860,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -851,6 +878,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -879,6 +907,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -896,6 +925,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -913,6 +943,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -942,6 +973,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -959,6 +991,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -976,6 +1009,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1004,6 +1038,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -1021,6 +1056,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -1038,6 +1074,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1066,6 +1103,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -1083,6 +1121,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -1100,6 +1139,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1126,6 +1166,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -1143,6 +1184,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -1160,6 +1202,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1188,6 +1231,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -1205,6 +1249,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -1222,6 +1267,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1250,6 +1296,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -1267,6 +1314,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1295,6 +1343,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -1312,6 +1361,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1338,6 +1388,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -1355,6 +1406,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -1372,6 +1424,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1400,6 +1453,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -1417,6 +1471,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -1434,6 +1489,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1462,6 +1518,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -1479,6 +1536,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -1496,6 +1554,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1522,6 +1581,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -1539,6 +1599,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -1556,6 +1617,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1584,6 +1646,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -1601,6 +1664,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -1618,6 +1682,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1646,6 +1711,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -1663,6 +1729,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -1680,6 +1747,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1706,6 +1774,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -1723,6 +1792,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -1740,6 +1810,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1768,6 +1839,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -1785,6 +1857,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -1802,6 +1875,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1830,6 +1904,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -1847,6 +1922,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -1864,6 +1940,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1890,6 +1967,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -1907,6 +1985,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -1924,6 +2003,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1952,6 +2032,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -1969,6 +2050,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -1986,6 +2068,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -2014,6 +2097,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -2031,6 +2115,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -2048,6 +2133,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -2074,6 +2160,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -2091,6 +2178,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -2108,6 +2196,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -2136,6 +2225,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -2153,6 +2243,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -2170,6 +2261,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -2198,6 +2290,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -2215,6 +2308,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -2232,6 +2326,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -2260,6 +2355,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "densu": { @@ -2277,6 +2373,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } diff --git a/tests/e2e/tracehandler_testdata/shorthappy.json b/tests/e2e/tracehandler_testdata/shorthappy.json index 70486add49..85e96aeed1 100644 --- a/tests/e2e/tracehandler_testdata/shorthappy.json +++ b/tests/e2e/tracehandler_testdata/shorthappy.json @@ -21,7 +21,7 @@ } ], "GenesisChanges": "", - "SkipGentx": false + "IsConsumer": false }, "State": { "provi": { @@ -39,6 +39,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -72,6 +73,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { "1": { "RawProposal": { @@ -115,6 +117,7 @@ }, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -158,6 +161,7 @@ }, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -193,6 +197,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { "1": { "RawProposal": { @@ -250,6 +255,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -267,6 +273,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -318,6 +325,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -335,6 +343,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -362,6 +371,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -390,6 +400,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -417,6 +428,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -445,6 +457,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -462,6 +475,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -490,6 +504,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -519,6 +534,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -536,6 +552,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -564,6 +581,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -590,6 +608,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -607,6 +626,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -634,6 +654,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -651,6 +672,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -679,6 +701,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -705,6 +728,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -722,6 +746,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -750,6 +775,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -776,6 +802,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -793,6 +820,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -821,6 +849,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -847,6 +876,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -864,6 +894,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -892,70 +923,11 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } }, - { - "ActionType": "main.submitEquivocationProposalAction", - "Action": { - "Chain": "consu", - "Height": 10, - "Time": "2023-10-04T12:14:14.883385+02:00", - "Power": 500, - "Validator": "bob", - "Deposit": 10000001, - "From": "bob" - }, - "State": { - "consu": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 495 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - }, - "provi": { - "ValBalances": { - "bob": 9500000000 - }, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 495 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": { - "2": { - "RawProposal": { - "Title": "", - "Description": "", - "Deposit": 0, - "Status": "" - }, - "Type": "main.TextProposal" - } - } - } - } - }, { "ActionType": "main.doublesignSlashAction", "Action": { @@ -978,6 +950,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -995,6 +968,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1023,6 +997,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -1040,309 +1015,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, - "Proposals": null - } - } - }, - { - "ActionType": "main.doublesignSlashAction", - "Action": { - "Validator": "bob", - "Chain": "consu" - }, - "State": { - "consu": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - }, - "provi": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - } - } - }, - { - "ActionType": "main.relayPacketsAction", - "Action": { - "ChainA": "provi", - "ChainB": "consu", - "Port": "provider", - "Channel": 0 - }, - "State": { - "consu": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - }, - "provi": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - } - } - }, - { - "ActionType": "main.relayPacketsAction", - "Action": { - "ChainA": "provi", - "ChainB": "consu", - "Port": "provider", - "Channel": 0 - }, - "State": { - "consu": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - }, - "provi": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - } - } - }, - { - "ActionType": "main.submitEquivocationProposalAction", - "Action": { - "Chain": "consu", - "Height": 10, - "Time": "2023-10-04T12:14:14.883388+02:00", - "Power": 500, - "Validator": "bob", - "Deposit": 10000001, - "From": "bob" - }, - "State": { - "consu": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - }, - "provi": { - "ValBalances": { - "bob": 9489999999 - }, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": { - "2": { - "RawProposal": { - "Height": 10, - "Power": 500, - "ConsensusAddress": "cosmosvalcons1nx7n5uh0ztxsynn4sje6eyq2ud6rc6klc96w39", - "Deposit": 10000001, - "Status": "PROPOSAL_STATUS_VOTING_PERIOD" - }, - "Type": "main.EquivocationProposal" - } - } - } - } - }, - { - "ActionType": "main.voteGovProposalAction", - "Action": { - "Chain": "provi", - "From": [ - "alice", - "bob", - "carol" - ], - "Vote": [ - "yes", - "yes", - "yes" - ], - "PropNumber": 2 - }, - "State": { - "consu": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 500, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - }, - "provi": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 0, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": { - "2": { - "RawProposal": { - "Height": 10, - "Power": 500, - "ConsensusAddress": "cosmosvalcons1nx7n5uh0ztxsynn4sje6eyq2ud6rc6klc96w39", - "Deposit": 10000001, - "Status": "PROPOSAL_STATUS_PASSED" - }, - "Type": "main.EquivocationProposal" - } - } - } - } - }, - { - "ActionType": "main.relayPacketsAction", - "Action": { - "ChainA": "provi", - "ChainB": "consu", - "Port": "provider", - "Channel": 0 - }, - "State": { - "consu": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 0, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, - "Proposals": null - }, - "provi": { - "ValBalances": null, - "ValPowers": { - "alice": 509, - "bob": 0, - "carol": 0 - }, - "StakedTokens": null, - "Params": null, - "Rewards": null, - "ConsumerChains": null, - "AssignedKeys": null, - "ProviderKeys": null, - "ConsumerPendingPacketQueueSize": null, - "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -1377,8 +1050,9 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { - "3": { + "2": { "RawProposal": { "Deposit": 10000001, "Chain": "consu", @@ -1405,7 +1079,7 @@ "no", "no" ], - "PropNumber": 3 + "PropNumber": 2 }, "State": { "provi": { @@ -1423,8 +1097,9 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { - "3": { + "2": { "RawProposal": { "Deposit": 10000001, "Chain": "consu", @@ -1462,8 +1137,9 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { - "4": { + "3": { "RawProposal": { "Deposit": 10000001, "Chain": "consu", @@ -1490,7 +1166,7 @@ "yes", "yes" ], - "PropNumber": 4 + "PropNumber": 3 }, "State": { "provi": { @@ -1506,8 +1182,9 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { - "4": { + "3": { "RawProposal": { "Deposit": 10000001, "Chain": "consu", diff --git a/tests/e2e/tracehandler_testdata/slashThrottle.json b/tests/e2e/tracehandler_testdata/slashThrottle.json index d0173d03a8..dbca351d49 100644 --- a/tests/e2e/tracehandler_testdata/slashThrottle.json +++ b/tests/e2e/tracehandler_testdata/slashThrottle.json @@ -21,7 +21,7 @@ } ], "GenesisChanges": "", - "SkipGentx": false + "IsConsumer": false }, "State": { "provi": { @@ -39,6 +39,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -72,6 +73,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { "1": { "RawProposal": { @@ -115,6 +117,7 @@ }, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -158,6 +161,7 @@ }, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -193,6 +197,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { "1": { "RawProposal": { @@ -250,6 +255,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -267,6 +273,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -318,6 +325,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -335,6 +343,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -362,6 +371,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -390,6 +400,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -417,6 +428,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -443,6 +455,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": 1, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -460,6 +473,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -488,6 +502,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": 0, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -505,6 +520,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -531,6 +547,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": 1, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -548,6 +565,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -576,6 +594,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": 1, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -593,6 +612,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -619,6 +639,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": 1, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -636,6 +657,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -657,6 +679,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": 1, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -674,6 +697,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -698,6 +722,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": 0, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null }, "provi": { @@ -715,6 +740,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": null } } @@ -744,6 +770,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { "2": { "RawProposal": { @@ -788,6 +815,7 @@ "ProviderKeys": null, "ConsumerPendingPacketQueueSize": null, "RegisteredConsumerRewardDenoms": null, + "ClientsFrozenHeights": null, "Proposals": { "2": { "RawProposal": { diff --git a/tests/integration/double_vote.go b/tests/integration/double_vote.go new file mode 100644 index 0000000000..af05c745df --- /dev/null +++ b/tests/integration/double_vote.go @@ -0,0 +1,395 @@ +package integration + +import ( + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + + tmcrypto "github.com/cometbft/cometbft/crypto" + tmtypes "github.com/cometbft/cometbft/types" + + testutil "github.com/cosmos/interchain-security/v3/testutil/crypto" + "github.com/cosmos/interchain-security/v3/x/ccv/provider/types" +) + +// TestHandleConsumerDoubleVoting verifies that handling a double voting evidence +// of a consumer chain results in the expected tombstoning, jailing, and slashing of the misbehaved validator +func (s *CCVTestSuite) TestHandleConsumerDoubleVoting() { + 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) + } + + consuValSet, err := tmtypes.ValidatorSetFromProto(s.consumerChain.LastHeader.ValidatorSet) + s.Require().NoError(err) + consuVal := consuValSet.Validators[0] + consuSigner := s.consumerChain.Signers[consuVal.Address.String()] + + provValSet, err := tmtypes.ValidatorSetFromProto(s.providerChain.LastHeader.ValidatorSet) + s.Require().NoError(err) + + provVal := provValSet.Validators[0] + provSigner := s.providerChain.Signers[provVal.Address.String()] + + blockID1 := testutil.MakeBlockID([]byte("blockhash"), 1000, []byte("partshash")) + blockID2 := testutil.MakeBlockID([]byte("blockhash2"), 1000, []byte("partshash")) + + // Set the equivocation evidence min height to the previous block height + equivocationEvidenceMinHeight := uint64(s.consumerCtx().BlockHeight() - 1) + s.providerApp.GetProviderKeeper().SetEquivocationEvidenceMinHeight( + s.providerCtx(), + s.consumerChain.ChainID, + equivocationEvidenceMinHeight, + ) + + // Note that votes are signed along with the chain ID + // see VoteSignBytes in https://github.com/cometbft/cometbft/blob/v0.37.2/types/vote.go#L93 + + // create two votes using the consumer validator key + consuVote := testutil.MakeAndSignVote( + blockID1, + s.consumerCtx().BlockHeight(), + s.consumerCtx().BlockTime(), + consuValSet, + consuSigner, + s.consumerChain.ChainID, + ) + + consuBadVote := testutil.MakeAndSignVote( + blockID2, + s.consumerCtx().BlockHeight(), + s.consumerCtx().BlockTime(), + consuValSet, + consuSigner, + s.consumerChain.ChainID, + ) + + // create two votes using the provider validator key + provVote := testutil.MakeAndSignVote( + blockID1, + s.consumerCtx().BlockHeight(), + s.consumerCtx().BlockTime(), + provValSet, + provSigner, + s.consumerChain.ChainID, + ) + + provBadVote := testutil.MakeAndSignVote( + blockID2, + s.consumerCtx().BlockHeight(), + s.consumerCtx().BlockTime(), + provValSet, + provSigner, + s.consumerChain.ChainID, + ) + + // create a vote using the consumer validator key + // with block height that is smaller than the equivocation evidence min height + consuVoteOld := testutil.MakeAndSignVote( + blockID1, + int64(equivocationEvidenceMinHeight-1), + s.consumerCtx().BlockTime(), + consuValSet, + consuSigner, + s.consumerChain.ChainID, + ) + + testCases := []struct { + name string + ev *tmtypes.DuplicateVoteEvidence + chainID string + pubkey tmcrypto.PubKey + expPass bool + }{ + { + "cannot find consumer chain for the given chain ID - shouldn't pass", + &tmtypes.DuplicateVoteEvidence{ + VoteA: consuVote, + VoteB: consuBadVote, + ValidatorPower: consuVal.VotingPower, + TotalVotingPower: consuVal.VotingPower, + Timestamp: s.consumerCtx().BlockTime(), + }, + "chainID", + consuVal.PubKey, + false, + }, + { + "evidence is older than equivocation evidence min height - shouldn't pass", + &tmtypes.DuplicateVoteEvidence{ + VoteA: consuVoteOld, + VoteB: consuBadVote, + ValidatorPower: consuVal.VotingPower, + TotalVotingPower: consuVal.VotingPower, + Timestamp: s.consumerCtx().BlockTime(), + }, + s.consumerChain.ChainID, + consuVal.PubKey, + false, + }, + { + "the votes in the evidence are for different height - shouldn't pass", + &tmtypes.DuplicateVoteEvidence{ + VoteA: consuVote, + VoteB: consuVoteOld, + ValidatorPower: consuVal.VotingPower, + TotalVotingPower: consuVal.VotingPower, + Timestamp: s.consumerCtx().BlockTime(), + }, + s.consumerChain.ChainID, + consuVal.PubKey, + false, + }, + { + "wrong public key - shouldn't pass", + &tmtypes.DuplicateVoteEvidence{ + VoteA: consuVote, + VoteB: consuVote, + ValidatorPower: consuVal.VotingPower, + TotalVotingPower: consuVal.VotingPower, + Timestamp: s.consumerCtx().BlockTime(), + }, + s.consumerChain.ChainID, + provVal.PubKey, + false, + }, + { + // create an invalid evidence containing two identical votes + "invalid double voting evidence with identical votes - shouldn't pass", + &tmtypes.DuplicateVoteEvidence{ + VoteA: consuVote, + VoteB: consuVote, + ValidatorPower: consuVal.VotingPower, + TotalVotingPower: consuVal.VotingPower, + Timestamp: s.consumerCtx().BlockTime(), + }, + s.consumerChain.ChainID, + consuVal.PubKey, + false, + }, + { + // In order to create an evidence for a consumer chain, + // we create two votes that only differ by their Block IDs and + // signed them using the same validator private key and chain ID + // of the consumer chain + "valid double voting evidence 1 - should pass", + &tmtypes.DuplicateVoteEvidence{ + VoteA: consuVote, + VoteB: consuBadVote, + ValidatorPower: consuVal.VotingPower, + TotalVotingPower: consuVal.VotingPower, + Timestamp: s.consumerCtx().BlockTime(), + }, + s.consumerChain.ChainID, + consuVal.PubKey, + true, + }, + { + // create a double voting evidence using the provider validator key + "valid double voting evidence 2 - should pass", + &tmtypes.DuplicateVoteEvidence{ + VoteA: provVote, + VoteB: provBadVote, + ValidatorPower: consuVal.VotingPower, + TotalVotingPower: consuVal.VotingPower, + Timestamp: s.consumerCtx().BlockTime(), + }, + s.consumerChain.ChainID, + provVal.PubKey, + true, + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + consuAddr := types.NewConsumerConsAddress(sdk.ConsAddress(tc.ev.VoteA.ValidatorAddress.Bytes())) + provAddr := s.providerApp.GetProviderKeeper().GetProviderAddrFromConsumerAddr(s.providerCtx(), s.consumerChain.ChainID, consuAddr) + + validator, _ := s.providerApp.GetTestStakingKeeper().GetValidator(s.providerCtx(), provAddr.ToSdkConsAddr().Bytes()) + initialTokens := sdk.NewDecFromInt(validator.GetTokens()) + + // reset context for each run + provCtx, _ := s.providerCtx().CacheContext() + + // if the evidence was built using the validator provider address and key, + // we remove the consumer key assigned to the validator otherwise + // HandleConsumerDoubleVoting uses the consumer key to verify the signature + if tc.ev.VoteA.ValidatorAddress.String() != consuVal.Address.String() { + s.providerApp.GetProviderKeeper().DeleteKeyAssignments(provCtx, s.consumerChain.ChainID) + } + + // convert validator public key + pk, err := cryptocodec.FromTmPubKeyInterface(tc.pubkey) + s.Require().NoError(err) + + err = s.providerApp.GetProviderKeeper().HandleConsumerDoubleVoting( + provCtx, + tc.ev, + tc.chainID, + pk, + ) + + if tc.expPass { + s.Require().NoError(err) + + // verifies that the jailing and tombstoning has occurred + s.Require().True(s.providerApp.GetTestStakingKeeper().IsValidatorJailed(provCtx, provAddr.ToSdkConsAddr())) + s.Require().True(s.providerApp.GetTestSlashingKeeper().IsTombstoned(provCtx, provAddr.ToSdkConsAddr())) + + // verifies that the val gets slashed and has fewer tokens after the slashing + val, _ := s.providerApp.GetTestStakingKeeper().GetValidator(provCtx, provAddr.ToSdkConsAddr().Bytes()) + slashFraction := s.providerApp.GetTestSlashingKeeper().SlashFractionDoubleSign(provCtx) + actualTokens := sdk.NewDecFromInt(val.GetTokens()) + s.Require().True(initialTokens.Sub(initialTokens.Mul(slashFraction)).Equal(actualTokens)) + } else { + s.Require().Error(err) + + // verifies that no jailing and no tombstoning has occurred + s.Require().False(s.providerApp.GetTestStakingKeeper().IsValidatorJailed(provCtx, provAddr.ToSdkConsAddr())) + s.Require().False(s.providerApp.GetTestSlashingKeeper().IsTombstoned(provCtx, provAddr.ToSdkConsAddr())) + } + }) + } +} + +// TestHandleConsumerDoubleVotingSlashesUndelegationsAndRelegations verifies that handling a successful double voting +// evidence of a consumer chain results in the expected slashing of the misbehave validator undelegations +func (s *CCVTestSuite) TestHandleConsumerDoubleVotingSlashesUndelegationsAndRelegations() { + 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) + } + + consuValSet, err := tmtypes.ValidatorSetFromProto(s.consumerChain.LastHeader.ValidatorSet) + s.Require().NoError(err) + consuVal := consuValSet.Validators[0] + consuVal2 := consuValSet.Validators[1] + consuSigner := s.consumerChain.Signers[consuVal.Address.String()] + + blockID1 := testutil.MakeBlockID([]byte("blockhash"), 1000, []byte("partshash")) + blockID2 := testutil.MakeBlockID([]byte("blockhash2"), 1000, []byte("partshash")) + + // create two votes using the consumer validator key + consuVote := testutil.MakeAndSignVote( + blockID1, + s.consumerCtx().BlockHeight(), + s.consumerCtx().BlockTime(), + consuValSet, + consuSigner, + s.consumerChain.ChainID, + ) + + consuBadVote := testutil.MakeAndSignVote( + blockID2, + s.consumerCtx().BlockHeight(), + s.consumerCtx().BlockTime(), + consuValSet, + consuSigner, + s.consumerChain.ChainID, + ) + + // In order to create an evidence for a consumer chain, + // we create two votes that only differ by their Block IDs and + // signed them using the same validator private key and chain ID + // of the consumer chain + evidence := &tmtypes.DuplicateVoteEvidence{ + VoteA: consuVote, + VoteB: consuBadVote, + ValidatorPower: consuVal.VotingPower, + TotalVotingPower: consuVal.VotingPower, + Timestamp: s.consumerCtx().BlockTime(), + } + + chainID := s.consumerChain.ChainID + pubKey := consuVal.PubKey + + consuAddr := types.NewConsumerConsAddress(sdk.ConsAddress(consuVal.Address.Bytes())) + provAddr := s.providerApp.GetProviderKeeper().GetProviderAddrFromConsumerAddr(s.providerCtx(), s.consumerChain.ChainID, consuAddr) + + consuAddr2 := types.NewConsumerConsAddress(sdk.ConsAddress(consuVal2.Address.Bytes())) + provAddr2 := s.providerApp.GetProviderKeeper().GetProviderAddrFromConsumerAddr(s.providerCtx(), s.consumerChain.ChainID, consuAddr2) + + validator, found := s.providerApp.GetTestStakingKeeper().GetValidator(s.providerCtx(), provAddr.ToSdkConsAddr().Bytes()) + s.Require().True(found) + + validator2, found := s.providerApp.GetTestStakingKeeper().GetValidator(s.providerCtx(), provAddr2.ToSdkConsAddr().Bytes()) + s.Require().True(found) + + s.Run("slash undelegations and redelegations when getting double voting evidence", func() { + // convert validator public key + pk, err := cryptocodec.FromTmPubKeyInterface(pubKey) + s.Require().NoError(err) + + // perform a delegation and an undelegation of the whole amount + bondAmt := sdk.NewInt(10000000) + delAddr := s.providerChain.SenderAccount.GetAddress() + + // in order to perform a delegation we need to know the validator's `idx` (that might not be 0) + // loop through all validators to find the right `idx` + idx := 0 + for i := 0; i <= len(s.providerChain.Vals.Validators); i++ { + _, valAddr := s.getValByIdx(i) + if validator.OperatorAddress == valAddr.String() { + idx = i + break + } + } + + // delegate bond amount + _, shares, _ := delegateByIdx(s, delAddr, bondAmt, idx) + s.Require().NotZero(shares) + + // undelegate 1/2 of the bound amount + undelegate(s, delAddr, validator.GetOperator(), shares.Quo(sdk.NewDec(4))) + undelegate(s, delAddr, validator.GetOperator(), shares.Quo(sdk.NewDec(4))) + + // check that undelegations were successful + ubds, _ := s.providerApp.GetTestStakingKeeper().GetUnbondingDelegation(s.providerCtx(), delAddr, validator.GetOperator()) + // should have a single entry since undelegations are merged + s.Require().Len(ubds.Entries, 1) + + // save the delegation shares of the validator to redelegate to + // Note this shares should not be slashed! + delShares := s.providerApp.GetTestStakingKeeper().Delegation(s.providerCtx(), delAddr, validator2.GetOperator()).GetShares() + + // redelegate 1/2 of the bound amount + redelegate(s, delAddr, validator.GetOperator(), validator2.GetOperator(), shares.Quo(sdk.NewDec(4))) + redelegate(s, delAddr, validator.GetOperator(), validator2.GetOperator(), shares.Quo(sdk.NewDec(4))) + + // check that redelegation was successful + rdel := s.providerApp.GetTestStakingKeeper().GetRedelegations(s.providerCtx(), delAddr, uint16(10)) + s.Require().Len(rdel[0].Entries, 2) + + redelShares := rdel[0].Entries[0].SharesDst.Add(rdel[0].Entries[1].SharesDst) + + // cause double voting + err = s.providerApp.GetProviderKeeper().HandleConsumerDoubleVoting( + s.providerCtx(), + evidence, + chainID, + pk, + ) + s.Require().NoError(err) + + slashFraction := s.providerApp.GetTestSlashingKeeper().SlashFractionDoubleSign(s.providerCtx()) + + // check undelegations are slashed + ubds, _ = s.providerApp.GetTestStakingKeeper().GetUnbondingDelegation(s.providerCtx(), delAddr, validator.GetOperator()) + s.Require().True(len(ubds.Entries) > 0) + for _, unb := range ubds.Entries { + initialBalance := sdk.NewDecFromInt(unb.InitialBalance) + currentBalance := sdk.NewDecFromInt(unb.Balance) + s.Require().True(initialBalance.Sub(initialBalance.Mul(slashFraction)).Equal(currentBalance)) + } + // check that redelegations are slashed + delegations := s.providerApp.GetTestStakingKeeper().Delegation(s.providerCtx(), delAddr, validator2.GetOperator()) + s.Require().Equal(delegations.GetShares(), delShares.Add(redelShares).Sub(redelShares.Mul(slashFraction))) + }) +} diff --git a/tests/integration/misbehaviour.go b/tests/integration/misbehaviour.go new file mode 100644 index 0000000000..c0f2cf10b9 --- /dev/null +++ b/tests/integration/misbehaviour.go @@ -0,0 +1,551 @@ +package integration + +import ( + "time" + + ibctmtypes "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint" + + sdk "github.com/cosmos/cosmos-sdk/types" + + tmtypes "github.com/cometbft/cometbft/types" + + testutil "github.com/cosmos/interchain-security/v3/testutil/crypto" + "github.com/cosmos/interchain-security/v3/x/ccv/provider/types" +) + +// TestHandleConsumerMisbehaviour tests that handling a valid misbehaviour, +// with conflicting headers forming an equivocation, results in the jailing of the validators +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) + } + + altTime := s.providerCtx().BlockTime().Add(time.Minute) + + clientHeight := s.consumerChain.LastHeader.TrustedHeight + clientTMValset := tmtypes.NewValidatorSet(s.consumerChain.Vals.Validators) + clientSigners := s.consumerChain.Signers + + 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, + ), + // 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.Add(10*time.Second), + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + } + + // we assume that all validators have the same number of initial tokens + validator, _ := s.getValByIdx(0) + initialTokens := sdk.NewDecFromInt(validator.GetTokens()) + + err := s.providerApp.GetProviderKeeper().HandleConsumerMisbehaviour(s.providerCtx(), *misb) + s.NoError(err) + + // verify that validators are jailed, tombstoned, and slashed + 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) + s.Require().True(ok) + s.Require().True(val.Jailed) + s.Require().True(s.providerApp.GetTestSlashingKeeper().IsTombstoned(s.providerCtx(), provAddr.ToSdkConsAddr())) + + validator, _ := s.providerApp.GetTestStakingKeeper().GetValidator(s.providerCtx(), provAddr.ToSdkConsAddr().Bytes()) + slashFraction := s.providerApp.GetTestSlashingKeeper().SlashFractionDoubleSign(s.providerCtx()) + actualTokens := sdk.NewDecFromInt(validator.GetTokens()) + s.Require().True(initialTokens.Sub(initialTokens.Mul(slashFraction)).Equal(actualTokens)) + } +} + +func (s *CCVTestSuite) TestGetByzantineValidators() { + s.SetupCCVChannel(s.path) + // required to have the consumer client revision height greater than 0 + s.SendEmptyVSCPacket() + + altTime := s.providerCtx().BlockTime().Add(time.Minute) + + // Get the consumer client validator set + clientHeight := s.consumerChain.LastHeader.TrustedHeight + clientTMValset := tmtypes.NewValidatorSet(s.consumerChain.Vals.Validators) + clientSigners := s.consumerChain.Signers + + // Create a subset of the consumer client validator set + altValset := tmtypes.NewValidatorSet(s.consumerChain.Vals.Validators[0:3]) + altSigners := make(map[string]tmtypes.PrivValidator, 3) + 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[clientTMValset.Validators[2].Address.String()] = clientSigners[clientTMValset.Validators[2].Address.String()] + + // create a consumer client header + clientHeader := s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime, + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ) + + testCases := []struct { + name string + getMisbehaviour func() *ibctmtypes.Misbehaviour + expByzantineValidators []*tmtypes.Validator + expPass bool + }{ + { + "invalid misbehaviour - Header1 is empty", + func() *ibctmtypes.Misbehaviour { + return &ibctmtypes.Misbehaviour{ + Header1: &ibctmtypes.Header{}, + Header2: clientHeader, + } + }, + nil, + false, + }, + { + "invalid headers - Header2 is empty", + func() *ibctmtypes.Misbehaviour { + return &ibctmtypes.Misbehaviour{ + Header1: clientHeader, + Header2: &ibctmtypes.Header{}, + } + }, + nil, + false, + }, + { + "incorrect valset - shouldn't pass", + func() *ibctmtypes.Misbehaviour { + clientHeader := s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime.Add(time.Minute), + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ) + + clientHeaderWithCorruptedValset := s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime.Add(time.Hour), + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ) + + // change a validator public key in one the second header + testutil.CorruptValidatorPubkeyInHeader(clientHeaderWithCorruptedValset, clientTMValset.Validators[0].Address) + + return &ibctmtypes.Misbehaviour{ + ClientId: s.path.EndpointA.ClientID, + Header1: clientHeader, + Header2: clientHeaderWithCorruptedValset, + } + }, + []*tmtypes.Validator{}, + false, + }, + { + "incorrect valset 2 - shouldn't pass", + func() *ibctmtypes.Misbehaviour { + clientHeader := s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime.Add(time.Minute), + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ) + + clientHeaderWithCorruptedSigs := s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime.Add(time.Hour), + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ) + + // change the valset in the header + vs, _ := altValset.ToProto() + clientHeader.ValidatorSet.Validators = vs.Validators[:3] + clientHeaderWithCorruptedSigs.ValidatorSet.Validators = vs.Validators[:3] + + return &ibctmtypes.Misbehaviour{ + ClientId: s.path.EndpointA.ClientID, + Header1: clientHeader, + Header2: clientHeaderWithCorruptedSigs, + } + }, + []*tmtypes.Validator{}, + false, + }, + { + "incorrect signatures - shouldn't pass", + func() *ibctmtypes.Misbehaviour { + clientHeader := s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime.Add(time.Minute), + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ) + + clientHeaderWithCorruptedSigs := s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime.Add(time.Hour), + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ) + + // change the signature of one of the validator in the header + testutil.CorruptCommitSigsInHeader(clientHeaderWithCorruptedSigs, clientTMValset.Validators[0].Address) + + return &ibctmtypes.Misbehaviour{ + ClientId: s.path.EndpointA.ClientID, + Header1: clientHeader, + Header2: clientHeaderWithCorruptedSigs, + } + }, + []*tmtypes.Validator{}, + false, + }, + { + "light client attack - lunatic attack", + func() *ibctmtypes.Misbehaviour { + return &ibctmtypes.Misbehaviour{ + ClientId: s.path.EndpointA.ClientID, + Header1: clientHeader, + // the resulting header contains invalid fields + // i.e. ValidatorsHash, NextValidatorsHash. + Header2: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime, + altValset, + altValset, + clientTMValset, + altSigners, + ), + } + }, + // Expect to get only the validators + // who signed both headers + altValset.Validators, + true, + }, + { + "light client attack - equivocation", + func() *ibctmtypes.Misbehaviour { + return &ibctmtypes.Misbehaviour{ + ClientId: s.path.EndpointA.ClientID, + Header1: clientHeader, + // the resulting header contains a different BlockID + Header2: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime.Add(time.Minute), + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ), + } + }, + // Expect to get the entire valset since + // all validators double-signed + clientTMValset.Validators, + true, + }, + { + "light client attack - amnesia", + func() *ibctmtypes.Misbehaviour { + // create a valid header with a different hash + // and commit round + amnesiaHeader := s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + altTime.Add(time.Minute), + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ) + amnesiaHeader.Commit.Round = 2 + + return &ibctmtypes.Misbehaviour{ + ClientId: s.path.EndpointA.ClientID, + Header1: clientHeader, + Header2: amnesiaHeader, + } + }, + // Expect no validators + // since amnesia attacks are dropped + []*tmtypes.Validator{}, + true, + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + byzantineValidators, err := s.providerApp.GetProviderKeeper().GetByzantineValidators( + s.providerCtx(), + *tc.getMisbehaviour(), + ) + if tc.expPass { + s.NoError(err) + s.Equal(len(tc.expByzantineValidators), len(byzantineValidators)) + + // For both lunatic and equivocation attacks, all the validators + // who signed both headers + if len(tc.expByzantineValidators) > 0 { + equivocatingVals := tc.getMisbehaviour().Header2.ValidatorSet + s.Equal(len(equivocatingVals.Validators), len(byzantineValidators)) + + vs, err := tmtypes.ValidatorSetFromProto(equivocatingVals) + s.NoError(err) + + for _, v := range tc.expByzantineValidators { + idx, _ := vs.GetByAddress(v.Address) + s.True(idx >= 0) + } + } + } else { + s.Error(err) + } + }) + } +} + +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 a valid client header + clientHeader := s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + headerTs, + clientTMValset, + clientTMValset, + clientTMValset, + clientSigners, + ) + + // 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, 2) + altSigners[clientTMValset.Validators[0].Address.String()] = clientSigners[clientTMValset.Validators[0].Address.String()] + altSigners[clientTMValset.Validators[1].Address.String()] = clientSigners[clientTMValset.Validators[1].Address.String()] + + // create a conflicting client with different block ID using + // to alternative validator set + clientHeaderWithDiffBlockID := s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + headerTs, + altValset, + altValset, + clientTMValset, // trusted valset stays the same + altSigners, + ) + + // create an alternative validator set using less than 1/3 of the trusted validator set + altValset2 := tmtypes.NewValidatorSet(s.consumerChain.Vals.Validators[0:1]) + altSigners2 := make(map[string]tmtypes.PrivValidator, 1) + altSigners2[clientTMValset.Validators[0].Address.String()] = clientSigners[clientTMValset.Validators[0].Address.String()] + + // create a conflicting client header with insufficient voting power + clientHeaderWithInsufficientVotingPower := s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+1), + clientHeight, + // use a different block time to change the header BlockID + headerTs.Add(time.Hour), + altValset2, + altValset2, + clientTMValset, + altSigners2, + ) + + // Set the equivocation evidence min height to the previous block height + equivocationEvidenceMinHeight := clientHeight.RevisionHeight + 1 + s.providerApp.GetProviderKeeper().SetEquivocationEvidenceMinHeight( + s.providerCtx(), + s.consumerChain.ChainID, + equivocationEvidenceMinHeight, + ) + + testCases := []struct { + name string + misbehaviour *ibctmtypes.Misbehaviour + expPass bool + }{ + { + "identical headers - shouldn't pass", + &ibctmtypes.Misbehaviour{ + ClientId: s.path.EndpointA.ClientID, + Header1: clientHeader, + Header2: clientHeader, + }, + false, + }, + { + "misbehaviour isn't for a consumer chain - shouldn't pass", + &ibctmtypes.Misbehaviour{ + ClientId: s.path.EndpointA.ClientID, + Header1: s.consumerChain.CreateTMClientHeader( + "aChainID", + int64(clientHeight.RevisionHeight+1), + clientHeight, + headerTs, + altValset, + altValset, + clientTMValset, + altSigners, + ), + Header2: clientHeader, + }, + false, + }, + { + "client ID doesn't correspond to the client ID of consumer chain - shouldn't pass", + &ibctmtypes.Misbehaviour{ + ClientId: "clientID", + Header1: clientHeader, + Header2: clientHeaderWithDiffBlockID, + }, + false, + }, + { + "invalid misbehaviour with different header height - shouldn't pass", + &ibctmtypes.Misbehaviour{ + ClientId: s.path.EndpointA.ClientID, + Header1: clientHeader, + Header2: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(clientHeight.RevisionHeight+2), + clientHeight, + headerTs, + altValset, + altValset, + clientTMValset, + altSigners, + ), + }, + false, + }, + { + "invalid misbehaviour older than the min equivocation evidence height - shouldn't pass", + &ibctmtypes.Misbehaviour{ + ClientId: s.path.EndpointA.ClientID, + Header1: s.consumerChain.CreateTMClientHeader( + s.consumerChain.ChainID, + int64(equivocationEvidenceMinHeight-1), + clientHeight, + headerTs, + altValset, + altValset, + clientTMValset, + altSigners, + ), + Header2: clientHeader, + }, + false, + }, + { + "one header of the misbehaviour has insufficient voting power - shouldn't pass", + &ibctmtypes.Misbehaviour{ + ClientId: s.path.EndpointA.ClientID, + Header1: clientHeader, + Header2: clientHeaderWithInsufficientVotingPower, + }, + false, + }, + { + "valid misbehaviour - should pass", + &ibctmtypes.Misbehaviour{ + ClientId: s.path.EndpointA.ClientID, + Header1: clientHeader, + // create header using a different validator set + Header2: clientHeaderWithDiffBlockID, + }, + 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/crypto/evidence.go b/testutil/crypto/evidence.go new file mode 100644 index 0000000000..653f20824e --- /dev/null +++ b/testutil/crypto/evidence.go @@ -0,0 +1,135 @@ +package crypto + +import ( + "time" + + ibctmtypes "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint" + + "github.com/cometbft/cometbft/crypto/tmhash" + "github.com/cometbft/cometbft/libs/bytes" + tmtypes "github.com/cometbft/cometbft/types" +) + +// utility function duplicated from CometBFT +// see https://github.com/cometbft/cometbft/blob/main/evidence/verify_test.go#L554 +func MakeBlockID(hash []byte, partSetSize uint32, partSetHash []byte) tmtypes.BlockID { + var ( + h = make([]byte, tmhash.Size) + psH = make([]byte, tmhash.Size) + ) + copy(h, hash) + copy(psH, partSetHash) + return tmtypes.BlockID{ + Hash: h, + PartSetHeader: tmtypes.PartSetHeader{ + Total: partSetSize, + Hash: psH, + }, + } +} + +func MakeAndSignVote( + blockID tmtypes.BlockID, + blockHeight int64, + blockTime time.Time, + valSet *tmtypes.ValidatorSet, + signer tmtypes.PrivValidator, + chainID string, +) *tmtypes.Vote { + vote, err := tmtypes.MakeVote( + blockHeight, + blockID, + valSet, + signer, + chainID, + blockTime, + ) + if err != nil { + panic(err) + } + + v := vote.ToProto() + err = signer.SignVote(chainID, v) + if err != nil { + panic(err) + } + + vote.Signature = v.Signature + return vote +} + +// MakeAndSignVoteWithForgedValAddress makes and signs a vote using two different keys: +// one to derive the validator address in the vote and a second to sign it. +func MakeAndSignVoteWithForgedValAddress( + blockID tmtypes.BlockID, + blockHeight int64, + blockTime time.Time, + valSet *tmtypes.ValidatorSet, + signer tmtypes.PrivValidator, + valAddressSigner tmtypes.PrivValidator, + chainID string, +) *tmtypes.Vote { + // create the vote using a different key than the signing key + vote, err := tmtypes.MakeVote( + blockHeight, + blockID, + valSet, + valAddressSigner, + chainID, + blockTime, + ) + if err != nil { + panic(err) + } + + // sign vote using the given private key + v := vote.ToProto() + err = signer.SignVote(chainID, v) + if err != nil { + panic(err) + } + + vote.Signature = v.Signature + return vote +} + +// CorruptCommitSigsInHeader corrupts the header by changing the value +// of the commit signature for given validator address. +// Note that this method is solely used for testing purposes +func CorruptCommitSigsInHeader(header *ibctmtypes.Header, valAddress bytes.HexBytes) { + commit, err := tmtypes.CommitFromProto(header.Commit) + if err != nil { + panic(err) + } + + for idx, sig := range commit.Signatures { + if sig.ValidatorAddress.String() == valAddress.String() { + sig.Signature = []byte("randomsig") + commit.Signatures[idx] = sig + } + } + // update the commit in client the header + header.SignedHeader.Commit = commit.ToProto() +} + +// CorruptValidatorPubkeyInHeader corrupts the header by changing the validator pubkey +// of the given validator address in the validator set. +// Note that this method is solely used for testing purposes +func CorruptValidatorPubkeyInHeader(header *ibctmtypes.Header, valAddress bytes.HexBytes) { + valset, err := tmtypes.ValidatorSetFromProto(header.ValidatorSet) + if err != nil { + panic(err) + } + + for _, v := range valset.Validators { + if v.Address.String() == valAddress.String() { + v.PubKey = tmtypes.NewMockPV().PrivKey.PubKey() + } + } + + vs, err := valset.ToProto() + if err != nil { + panic(err) + } + header.ValidatorSet = vs +} diff --git a/testutil/integration/debug_test.go b/testutil/integration/debug_test.go index ab1c78af7f..dc1ae22543 100644 --- a/testutil/integration/debug_test.go +++ b/testutil/integration/debug_test.go @@ -242,6 +242,33 @@ func TestRecycleTransferChannel(t *testing.T) { } // +// Misbehaviour tests +// + +func TestHandleConsumerMisbehaviour(t *testing.T) { + runCCVTestByName(t, "TestHandleConsumerMisbehaviour") +} + +func TestGetByzantineValidators(t *testing.T) { + runCCVTestByName(t, "TestGetByzantineValidators") +} + +func TestCheckMisbehaviour(t *testing.T) { + runCCVTestByName(t, "TestCheckMisbehaviour") +} + +// +// Consumer Equivocation test +// + +func TestHandleConsumerDoubleVoting(t *testing.T) { + runCCVTestByName(t, "TestHandleConsumerDoubleVoting") +} + +func TestHandleConsumerDoubleVotingSlashesUndelegationsAndRelegations(t *testing.T) { + runCCVTestByName(t, "TestHandleConsumerDoubleVotingSlashesUndelegationsAndRelegations") +} + // Throttle retry tests // diff --git a/testutil/keeper/expectations.go b/testutil/keeper/expectations.go index 7814fe0fcf..9494b66936 100644 --- a/testutil/keeper/expectations.go +++ b/testutil/keeper/expectations.go @@ -11,6 +11,8 @@ import ( "github.com/golang/mock/gomock" extra "github.com/oxyno-zeta/gomock-extra-matcher" + math "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" @@ -156,3 +158,64 @@ func GetMocksForSendIBCPacket(ctx sdk.Context, mocks MockedKeepers, channelID st ).Return(uint64(888), nil).Times(times), } } + +func GetMocksForSlashValidator( + ctx sdk.Context, + mocks MockedKeepers, + validator stakingtypes.Validator, + consAddr sdk.ConsAddress, + undelegations []stakingtypes.UnbondingDelegation, + redelegations []stakingtypes.Redelegation, + powerReduction math.Int, + slashFraction math.LegacyDec, + currentPower, + expectedInfractionHeight, + expectedSlashPower int64, +) []*gomock.Call { + return []*gomock.Call{ + mocks.MockStakingKeeper.EXPECT(). + GetUnbondingDelegationsFromValidator(ctx, validator.GetOperator()). + Return(undelegations), + mocks.MockStakingKeeper.EXPECT(). + GetRedelegationsFromSrcValidator(ctx, validator.GetOperator()). + Return(redelegations), + mocks.MockStakingKeeper.EXPECT(). + GetLastValidatorPower(ctx, validator.GetOperator()). + Return(currentPower), + mocks.MockStakingKeeper.EXPECT(). + PowerReduction(ctx). + Return(powerReduction), + mocks.MockStakingKeeper.EXPECT(). + SlashUnbondingDelegation(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + DoAndReturn( + func(_ sdk.Context, undelegation stakingtypes.UnbondingDelegation, _ int64, _ sdk.Dec) math.Int { + sum := sdk.NewInt(0) + for _, r := range undelegation.Entries { + if r.IsMature(ctx.BlockTime()) { + continue + } + sum = sum.Add(sdk.NewInt(r.InitialBalance.Int64())) + } + return sum + }).AnyTimes(), + mocks.MockStakingKeeper.EXPECT(). + SlashRedelegation(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + DoAndReturn( + func(_ sdk.Context, _ stakingtypes.Validator, redelegation stakingtypes.Redelegation, _ int64, _ sdk.Dec) math.Int { + sum := sdk.NewInt(0) + for _, r := range redelegation.Entries { + if r.IsMature(ctx.BlockTime()) { + continue + } + sum = sum.Add(sdk.NewInt(r.InitialBalance.Int64())) + } + return sum + }).AnyTimes(), + mocks.MockSlashingKeeper.EXPECT(). + SlashFractionDoubleSign(ctx). + Return(slashFraction), + mocks.MockStakingKeeper.EXPECT(). + SlashWithInfractionReason(ctx, consAddr, expectedInfractionHeight, expectedSlashPower, slashFraction, stakingtypes.Infraction_INFRACTION_DOUBLE_SIGN). + Times(1), + } +} diff --git a/testutil/keeper/mocks.go b/testutil/keeper/mocks.go index b075819cc4..b00678a50a 100644 --- a/testutil/keeper/mocks.go +++ b/testutil/keeper/mocks.go @@ -14,13 +14,12 @@ import ( types0 "github.com/cosmos/cosmos-sdk/types" types1 "github.com/cosmos/cosmos-sdk/x/auth/types" types2 "github.com/cosmos/cosmos-sdk/x/capability/types" - types3 "github.com/cosmos/cosmos-sdk/x/evidence/types" - types4 "github.com/cosmos/cosmos-sdk/x/slashing/types" - types5 "github.com/cosmos/cosmos-sdk/x/staking/types" - types6 "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" - types7 "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" - types8 "github.com/cosmos/ibc-go/v7/modules/core/03-connection/types" - types9 "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" + types3 "github.com/cosmos/cosmos-sdk/x/slashing/types" + types4 "github.com/cosmos/cosmos-sdk/x/staking/types" + types5 "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" + types6 "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" + types7 "github.com/cosmos/ibc-go/v7/modules/core/03-connection/types" + types8 "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" exported "github.com/cosmos/ibc-go/v7/modules/core/exported" gomock "github.com/golang/mock/gomock" ) @@ -63,10 +62,10 @@ func (mr *MockStakingKeeperMockRecorder) BondDenom(ctx interface{}) *gomock.Call } // Delegation mocks base method. -func (m *MockStakingKeeper) Delegation(ctx types0.Context, addr types0.AccAddress, valAddr types0.ValAddress) types5.DelegationI { +func (m *MockStakingKeeper) Delegation(ctx types0.Context, addr types0.AccAddress, valAddr types0.ValAddress) types4.DelegationI { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Delegation", ctx, addr, valAddr) - ret0, _ := ret[0].(types5.DelegationI) + ret0, _ := ret[0].(types4.DelegationI) return ret0 } @@ -105,10 +104,10 @@ func (mr *MockStakingKeeperMockRecorder) GetLastValidatorPower(ctx, operator int } // GetLastValidators mocks base method. -func (m *MockStakingKeeper) GetLastValidators(ctx types0.Context) []types5.Validator { +func (m *MockStakingKeeper) GetLastValidators(ctx types0.Context) []types4.Validator { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetLastValidators", ctx) - ret0, _ := ret[0].([]types5.Validator) + ret0, _ := ret[0].([]types4.Validator) return ret0 } @@ -118,11 +117,39 @@ func (mr *MockStakingKeeperMockRecorder) GetLastValidators(ctx interface{}) *gom return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLastValidators", reflect.TypeOf((*MockStakingKeeper)(nil).GetLastValidators), ctx) } +// GetRedelegationsFromSrcValidator mocks base method. +func (m *MockStakingKeeper) GetRedelegationsFromSrcValidator(ctx types0.Context, valAddr types0.ValAddress) []types4.Redelegation { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetRedelegationsFromSrcValidator", ctx, valAddr) + ret0, _ := ret[0].([]types4.Redelegation) + return ret0 +} + +// GetRedelegationsFromSrcValidator indicates an expected call of GetRedelegationsFromSrcValidator. +func (mr *MockStakingKeeperMockRecorder) GetRedelegationsFromSrcValidator(ctx, valAddr interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRedelegationsFromSrcValidator", reflect.TypeOf((*MockStakingKeeper)(nil).GetRedelegationsFromSrcValidator), ctx, valAddr) +} + +// GetUnbondingDelegationsFromValidator mocks base method. +func (m *MockStakingKeeper) GetUnbondingDelegationsFromValidator(ctx types0.Context, valAddr types0.ValAddress) []types4.UnbondingDelegation { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetUnbondingDelegationsFromValidator", ctx, valAddr) + ret0, _ := ret[0].([]types4.UnbondingDelegation) + return ret0 +} + +// GetUnbondingDelegationsFromValidator indicates an expected call of GetUnbondingDelegationsFromValidator. +func (mr *MockStakingKeeperMockRecorder) GetUnbondingDelegationsFromValidator(ctx, valAddr interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUnbondingDelegationsFromValidator", reflect.TypeOf((*MockStakingKeeper)(nil).GetUnbondingDelegationsFromValidator), ctx, valAddr) +} + // GetUnbondingType mocks base method. -func (m *MockStakingKeeper) GetUnbondingType(ctx types0.Context, id uint64) (types5.UnbondingType, bool) { +func (m *MockStakingKeeper) GetUnbondingType(ctx types0.Context, id uint64) (types4.UnbondingType, bool) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetUnbondingType", ctx, id) - ret0, _ := ret[0].(types5.UnbondingType) + ret0, _ := ret[0].(types4.UnbondingType) ret1, _ := ret[1].(bool) return ret0, ret1 } @@ -134,10 +161,10 @@ func (mr *MockStakingKeeperMockRecorder) GetUnbondingType(ctx, id interface{}) * } // GetValidator mocks base method. -func (m *MockStakingKeeper) GetValidator(ctx types0.Context, addr types0.ValAddress) (types5.Validator, bool) { +func (m *MockStakingKeeper) GetValidator(ctx types0.Context, addr types0.ValAddress) (types4.Validator, bool) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetValidator", ctx, addr) - ret0, _ := ret[0].(types5.Validator) + ret0, _ := ret[0].(types4.Validator) ret1, _ := ret[1].(bool) return ret0, ret1 } @@ -149,10 +176,10 @@ func (mr *MockStakingKeeperMockRecorder) GetValidator(ctx, addr interface{}) *go } // GetValidatorByConsAddr mocks base method. -func (m *MockStakingKeeper) GetValidatorByConsAddr(ctx types0.Context, consAddr types0.ConsAddress) (types5.Validator, bool) { +func (m *MockStakingKeeper) GetValidatorByConsAddr(ctx types0.Context, consAddr types0.ConsAddress) (types4.Validator, bool) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetValidatorByConsAddr", ctx, consAddr) - ret0, _ := ret[0].(types5.Validator) + ret0, _ := ret[0].(types4.Validator) ret1, _ := ret[1].(bool) return ret0, ret1 } @@ -204,7 +231,7 @@ func (mr *MockStakingKeeperMockRecorder) IterateLastValidatorPowers(ctx, cb inte } // IterateValidators mocks base method. -func (m *MockStakingKeeper) IterateValidators(ctx types0.Context, f func(int64, types5.ValidatorI) bool) { +func (m *MockStakingKeeper) IterateValidators(ctx types0.Context, f func(int64, types4.ValidatorI) bool) { m.ctrl.T.Helper() m.ctrl.Call(m, "IterateValidators", ctx, f) } @@ -283,8 +310,36 @@ func (mr *MockStakingKeeperMockRecorder) Slash(arg0, arg1, arg2, arg3, arg4 inte return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Slash", reflect.TypeOf((*MockStakingKeeper)(nil).Slash), arg0, arg1, arg2, arg3, arg4) } +// SlashRedelegation mocks base method. +func (m *MockStakingKeeper) SlashRedelegation(arg0 types0.Context, arg1 types4.Validator, arg2 types4.Redelegation, arg3 int64, arg4 types0.Dec) math.Int { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SlashRedelegation", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(math.Int) + return ret0 +} + +// SlashRedelegation indicates an expected call of SlashRedelegation. +func (mr *MockStakingKeeperMockRecorder) SlashRedelegation(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SlashRedelegation", reflect.TypeOf((*MockStakingKeeper)(nil).SlashRedelegation), arg0, arg1, arg2, arg3, arg4) +} + +// SlashUnbondingDelegation mocks base method. +func (m *MockStakingKeeper) SlashUnbondingDelegation(arg0 types0.Context, arg1 types4.UnbondingDelegation, arg2 int64, arg3 types0.Dec) math.Int { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SlashUnbondingDelegation", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(math.Int) + return ret0 +} + +// SlashUnbondingDelegation indicates an expected call of SlashUnbondingDelegation. +func (mr *MockStakingKeeperMockRecorder) SlashUnbondingDelegation(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SlashUnbondingDelegation", reflect.TypeOf((*MockStakingKeeper)(nil).SlashUnbondingDelegation), arg0, arg1, arg2, arg3) +} + // SlashWithInfractionReason mocks base method. -func (m *MockStakingKeeper) SlashWithInfractionReason(arg0 types0.Context, arg1 types0.ConsAddress, arg2, arg3 int64, arg4 types0.Dec, arg5 types5.Infraction) math.Int { +func (m *MockStakingKeeper) SlashWithInfractionReason(arg0 types0.Context, arg1 types0.ConsAddress, arg2, arg3 int64, arg4 types0.Dec, arg5 types4.Infraction) math.Int { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SlashWithInfractionReason", arg0, arg1, arg2, arg3, arg4, arg5) ret0, _ := ret[0].(math.Int) @@ -338,10 +393,10 @@ func (mr *MockStakingKeeperMockRecorder) Unjail(ctx, addr interface{}) *gomock.C } // Validator mocks base method. -func (m *MockStakingKeeper) Validator(ctx types0.Context, addr types0.ValAddress) types5.ValidatorI { +func (m *MockStakingKeeper) Validator(ctx types0.Context, addr types0.ValAddress) types4.ValidatorI { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Validator", ctx, addr) - ret0, _ := ret[0].(types5.ValidatorI) + ret0, _ := ret[0].(types4.ValidatorI) return ret0 } @@ -352,10 +407,10 @@ func (mr *MockStakingKeeperMockRecorder) Validator(ctx, addr interface{}) *gomoc } // ValidatorByConsAddr mocks base method. -func (m *MockStakingKeeper) ValidatorByConsAddr(ctx types0.Context, consAddr types0.ConsAddress) types5.ValidatorI { +func (m *MockStakingKeeper) ValidatorByConsAddr(ctx types0.Context, consAddr types0.ConsAddress) types4.ValidatorI { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ValidatorByConsAddr", ctx, consAddr) - ret0, _ := ret[0].(types5.ValidatorI) + ret0, _ := ret[0].(types4.ValidatorI) return ret0 } @@ -365,41 +420,6 @@ func (mr *MockStakingKeeperMockRecorder) ValidatorByConsAddr(ctx, consAddr inter return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidatorByConsAddr", reflect.TypeOf((*MockStakingKeeper)(nil).ValidatorByConsAddr), ctx, consAddr) } -// MockEvidenceKeeper is a mock of EvidenceKeeper interface. -type MockEvidenceKeeper struct { - ctrl *gomock.Controller - recorder *MockEvidenceKeeperMockRecorder -} - -// MockEvidenceKeeperMockRecorder is the mock recorder for MockEvidenceKeeper. -type MockEvidenceKeeperMockRecorder struct { - mock *MockEvidenceKeeper -} - -// NewMockEvidenceKeeper creates a new mock instance. -func NewMockEvidenceKeeper(ctrl *gomock.Controller) *MockEvidenceKeeper { - mock := &MockEvidenceKeeper{ctrl: ctrl} - mock.recorder = &MockEvidenceKeeperMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockEvidenceKeeper) EXPECT() *MockEvidenceKeeperMockRecorder { - return m.recorder -} - -// HandleEquivocationEvidence mocks base method. -func (m *MockEvidenceKeeper) HandleEquivocationEvidence(ctx types0.Context, evidence *types3.Equivocation) { - m.ctrl.T.Helper() - m.ctrl.Call(m, "HandleEquivocationEvidence", ctx, evidence) -} - -// HandleEquivocationEvidence indicates an expected call of HandleEquivocationEvidence. -func (mr *MockEvidenceKeeperMockRecorder) HandleEquivocationEvidence(ctx, evidence interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandleEquivocationEvidence", reflect.TypeOf((*MockEvidenceKeeper)(nil).HandleEquivocationEvidence), ctx, evidence) -} - // MockSlashingKeeper is a mock of SlashingKeeper interface. type MockSlashingKeeper struct { ctrl *gomock.Controller @@ -438,10 +458,10 @@ func (mr *MockSlashingKeeperMockRecorder) DowntimeJailDuration(arg0 interface{}) } // GetValidatorSigningInfo mocks base method. -func (m *MockSlashingKeeper) GetValidatorSigningInfo(ctx types0.Context, address types0.ConsAddress) (types4.ValidatorSigningInfo, bool) { +func (m *MockSlashingKeeper) GetValidatorSigningInfo(ctx types0.Context, address types0.ConsAddress) (types3.ValidatorSigningInfo, bool) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetValidatorSigningInfo", ctx, address) - ret0, _ := ret[0].(types4.ValidatorSigningInfo) + ret0, _ := ret[0].(types3.ValidatorSigningInfo) ret1, _ := ret[1].(bool) return ret0, ret1 } @@ -556,28 +576,34 @@ func (mr *MockChannelKeeperMockRecorder) ChanCloseInit(ctx, portID, channelID, c } // GetChannel mocks base method. -func (m *MockChannelKeeper) GetChannel(ctx types0.Context, srcPort, srcChan string) (types9.Channel, bool) { +func (m *MockChannelKeeper) GetChannel(ctx types0.Context, srcPort, srcChan string) (types8.Channel, bool) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetChannel", ctx, srcPort, srcChan) - ret0, _ := ret[0].(types9.Channel) + ret0, _ := ret[0].(types8.Channel) ret1, _ := ret[1].(bool) return ret0, ret1 } +// GetChannel indicates an expected call of GetChannel. +func (mr *MockChannelKeeperMockRecorder) GetChannel(ctx, srcPort, srcChan interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChannel", reflect.TypeOf((*MockChannelKeeper)(nil).GetChannel), ctx, srcPort, srcChan) +} + +// GetChannelConnection mocks base method. func (m *MockChannelKeeper) GetChannelConnection(ctx types0.Context, portID, channelID string) (string, exported.ConnectionI, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetChannelConnection", ctx, portID, channelID) ret0, _ := ret[0].(string) - ret1, _ := ret[0].(exported.ConnectionI) - ret2, _ := ret[1].(error) - + ret1, _ := ret[1].(exported.ConnectionI) + ret2, _ := ret[2].(error) return ret0, ret1, ret2 } -// GetChannel indicates an expected call of GetChannel. -func (mr *MockChannelKeeperMockRecorder) GetChannel(ctx, srcPort, srcChan interface{}) *gomock.Call { +// GetChannelConnection indicates an expected call of GetChannelConnection. +func (mr *MockChannelKeeperMockRecorder) GetChannelConnection(ctx, portID, channelID interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChannel", reflect.TypeOf((*MockChannelKeeper)(nil).GetChannel), ctx, srcPort, srcChan) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChannelConnection", reflect.TypeOf((*MockChannelKeeper)(nil).GetChannelConnection), ctx, portID, channelID) } // GetNextSequenceSend mocks base method. @@ -596,7 +622,7 @@ func (mr *MockChannelKeeperMockRecorder) GetNextSequenceSend(ctx, portID, channe } // SendPacket mocks base method. -func (m *MockChannelKeeper) SendPacket(ctx types0.Context, chanCap *types2.Capability, sourcePort, sourceChannel string, timeoutHeight types7.Height, timeoutTimestamp uint64, data []byte) (uint64, error) { +func (m *MockChannelKeeper) SendPacket(ctx types0.Context, chanCap *types2.Capability, sourcePort, sourceChannel string, timeoutHeight types6.Height, timeoutTimestamp uint64, data []byte) (uint64, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SendPacket", ctx, chanCap, sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, data) ret0, _ := ret[0].(uint64) @@ -685,10 +711,10 @@ func (m *MockConnectionKeeper) EXPECT() *MockConnectionKeeperMockRecorder { } // GetConnection mocks base method. -func (m *MockConnectionKeeper) GetConnection(ctx types0.Context, connectionID string) (types8.ConnectionEnd, bool) { +func (m *MockConnectionKeeper) GetConnection(ctx types0.Context, connectionID string) (types7.ConnectionEnd, bool) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetConnection", ctx, connectionID) - ret0, _ := ret[0].(types8.ConnectionEnd) + ret0, _ := ret[0].(types7.ConnectionEnd) ret1, _ := ret[1].(bool) return ret0, ret1 } @@ -722,6 +748,20 @@ func (m *MockClientKeeper) EXPECT() *MockClientKeeperMockRecorder { return m.recorder } +// ClientStore mocks base method. +func (m *MockClientKeeper) ClientStore(ctx types0.Context, clientID string) types0.KVStore { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientStore", ctx, clientID) + ret0, _ := ret[0].(types0.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 types0.Context, clientState exported.ClientState, consensusState exported.ConsensusState) (string, error) { m.ctrl.T.Helper() @@ -737,6 +777,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 types0.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 types0.Context, clientID string) (exported.ClientState, bool) { m.ctrl.T.Helper() @@ -782,6 +837,18 @@ func (mr *MockClientKeeperMockRecorder) GetSelfConsensusState(ctx, height interf return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSelfConsensusState", reflect.TypeOf((*MockClientKeeper)(nil).GetSelfConsensusState), ctx, height) } +// SetClientState mocks base method. +func (m *MockClientKeeper) SetClientState(ctx types0.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) +} + // MockDistributionKeeper is a mock of DistributionKeeper interface. type MockDistributionKeeper struct { ctrl *gomock.Controller @@ -982,10 +1049,10 @@ func (m *MockIBCTransferKeeper) EXPECT() *MockIBCTransferKeeperMockRecorder { } // Transfer mocks base method. -func (m *MockIBCTransferKeeper) Transfer(arg0 context.Context, arg1 *types6.MsgTransfer) (*types6.MsgTransferResponse, error) { +func (m *MockIBCTransferKeeper) Transfer(arg0 context.Context, arg1 *types5.MsgTransfer) (*types5.MsgTransferResponse, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Transfer", arg0, arg1) - ret0, _ := ret[0].(*types6.MsgTransferResponse) + ret0, _ := ret[0].(*types5.MsgTransferResponse) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -1020,10 +1087,10 @@ func (m *MockIBCCoreKeeper) EXPECT() *MockIBCCoreKeeperMockRecorder { } // ChannelOpenInit mocks base method. -func (m *MockIBCCoreKeeper) ChannelOpenInit(goCtx context.Context, msg *types9.MsgChannelOpenInit) (*types9.MsgChannelOpenInitResponse, error) { +func (m *MockIBCCoreKeeper) ChannelOpenInit(goCtx context.Context, msg *types8.MsgChannelOpenInit) (*types8.MsgChannelOpenInitResponse, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ChannelOpenInit", goCtx, msg) - ret0, _ := ret[0].(*types9.MsgChannelOpenInitResponse) + ret0, _ := ret[0].(*types8.MsgChannelOpenInitResponse) ret1, _ := ret[1].(error) return ret0, ret1 } diff --git a/testutil/keeper/unit_test_helpers.go b/testutil/keeper/unit_test_helpers.go index 77b4df9c10..557be11123 100644 --- a/testutil/keeper/unit_test_helpers.go +++ b/testutil/keeper/unit_test_helpers.go @@ -87,7 +87,6 @@ type MockedKeepers struct { *MockBankKeeper *MockIBCTransferKeeper *MockIBCCoreKeeper - *MockEvidenceKeeper *MockDistributionKeeper } @@ -105,7 +104,6 @@ func NewMockedKeepers(ctrl *gomock.Controller) MockedKeepers { MockBankKeeper: NewMockBankKeeper(ctrl), MockIBCTransferKeeper: NewMockIBCTransferKeeper(ctrl), MockIBCCoreKeeper: NewMockIBCCoreKeeper(ctrl), - MockEvidenceKeeper: NewMockEvidenceKeeper(ctrl), MockDistributionKeeper: NewMockDistributionKeeper(ctrl), } } @@ -124,7 +122,6 @@ func NewInMemProviderKeeper(params InMemKeeperParams, mocks MockedKeepers) provi mocks.MockStakingKeeper, mocks.MockSlashingKeeper, mocks.MockAccountKeeper, - mocks.MockEvidenceKeeper, mocks.MockDistributionKeeper, mocks.MockBankKeeper, authtypes.FeeCollectorName, diff --git a/x/ccv/provider/client/cli/tx.go b/x/ccv/provider/client/cli/tx.go index 0d37ef52a9..78fbaaf9df 100644 --- a/x/ccv/provider/client/cli/tx.go +++ b/x/ccv/provider/client/cli/tx.go @@ -1,14 +1,21 @@ package cli import ( + "encoding/json" "fmt" + "os" + "strings" + ibctmtypes "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint" "github.com/spf13/cobra" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/tx" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/version" + + tmproto "github.com/cometbft/cometbft/proto/tendermint/types" "github.com/cosmos/interchain-security/v3/x/ccv/provider/types" ) @@ -24,6 +31,8 @@ func GetTxCmd() *cobra.Command { } cmd.AddCommand(NewAssignConsumerKeyCmd()) + cmd.AddCommand(NewSubmitConsumerMisbehaviourCmd()) + cmd.AddCommand(NewSubmitConsumerDoubleVotingCmd()) return cmd } @@ -65,3 +74,121 @@ func NewAssignConsumerKeyCmd() *cobra.Command { return cmd } + +func NewSubmitConsumerMisbehaviourCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "submit-consumer-misbehaviour [misbehaviour]", + Short: "submit an IBC misbehaviour for a consumer chain", + Long: strings.TrimSpace( + 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. + +Example: +%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 { + return err + } + + txf, err := tx.NewFactoryCLI(clientCtx, cmd.Flags()) + if err != nil { + return err + } + txf = txf.WithTxConfig(clientCtx.TxConfig).WithAccountRetriever(clientCtx.AccountRetriever) + + submitter := clientCtx.GetFromAddress() + var misbehaviour ibctmtypes.Misbehaviour + if err := clientCtx.Codec.UnmarshalInterfaceJSON([]byte(args[1]), &misbehaviour); err != nil { + return err + } + + msg, err := types.NewMsgSubmitConsumerMisbehaviour(submitter, &misbehaviour) + 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 +} + +func NewSubmitConsumerDoubleVotingCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "submit-consumer-double-voting [evidence] [infraction_header]", + Short: "submit a double voting evidence for a consumer chain", + Long: strings.TrimSpace( + fmt.Sprintf(`Submit a Tendermint duplicate vote evidence detected on a consumer chain with + the IBC light client header for the infraction height. + The DuplicateVoteEvidence type definition can be found in the Tendermint messages, + see cometbft/proto/tendermint/types/evidence.proto and the IBC header + definition can be found in the IBC messages, see ibc-go/proto/ibc/lightclients/tendermint/v1/tendermint.proto. + +Example: +%s tx provider submit-consumer-double-voting [path/to/evidence.json] [path/to/infraction_header.json] --from node0 --home ../node0 --chain-id $CID +`, version.AppName)), + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + txf, err := tx.NewFactoryCLI(clientCtx, cmd.Flags()) + if err != nil { + return err + } + txf = txf.WithTxConfig(clientCtx.TxConfig).WithAccountRetriever(clientCtx.AccountRetriever) + + submitter := clientCtx.GetFromAddress() + + ev := tmproto.DuplicateVoteEvidence{} + evidenceJson, err := os.ReadFile(args[0]) + if err != nil { + return err + } + + if err := json.Unmarshal(evidenceJson, &ev); err != nil { + return fmt.Errorf("duplicate vote evidence unmarshalling failed: %s", err) + } + + headerRaw, err := os.ReadFile(args[1]) + if err != nil { + return err + } + + header := ibctmtypes.Header{} + if err := types.ModuleCdc.UnmarshalJSON(headerRaw, &header); err != nil { + return fmt.Errorf("infraction IBC header unmarshalling failed: %s", err) + } + + msg, err := types.NewMsgSubmitConsumerDoubleVoting(submitter, &ev, &header) + 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/client/proposal_handler.go b/x/ccv/provider/client/proposal_handler.go index 2b28c63466..bfd04af4c2 100644 --- a/x/ccv/provider/client/proposal_handler.go +++ b/x/ccv/provider/client/proposal_handler.go @@ -26,7 +26,6 @@ import ( var ( ConsumerAdditionProposalHandler = govclient.NewProposalHandler(SubmitConsumerAdditionPropTxCmd) ConsumerRemovalProposalHandler = govclient.NewProposalHandler(SubmitConsumerRemovalProposalTxCmd) - EquivocationProposalHandler = govclient.NewProposalHandler(SubmitEquivocationProposalTxCmd) ChangeRewardDenomsProposalHandler = govclient.NewProposalHandler(SubmitChangeRewardDenomsProposalTxCmd) ) @@ -167,69 +166,6 @@ Where proposal.json contains: } } -// SubmitEquivocationProposalTxCmd returns a CLI command handler for submitting -// a equivocation proposal via a transaction. -func SubmitEquivocationProposalTxCmd() *cobra.Command { - return &cobra.Command{ - Use: "equivocation [proposal-file]", - Args: cobra.ExactArgs(1), - Short: "Submit an equivocation proposal", - Long: fmt.Sprintf(`Submit an equivocation proposal along with an initial deposit. -The proposal details must be supplied via a JSON file. - -Example: -$ tx gov submit-legacy-proposal equivocation --from= - -Where proposal.json contains: -{ - "title": "Equivoque Foo validator", - "summary": "He double-signs on the Foobar consumer chain", - "equivocations": [ - { - "height": 10420042, - "time": "2023-01-27T15:59:50.121607-08:00", - "power": 10, - "consensus_address": "%s1s5afhd6gxevu37mkqcvvsj8qeylhn0rz46zdlq" - } - ], - "deposit": "10000stake" -} -`, sdk.GetConfig().GetBech32ConsensusAddrPrefix()), - RunE: func(cmd *cobra.Command, args []string) error { - clientCtx, err := client.GetClientTxContext(cmd) - if err != nil { - return err - } - - proposal, err := ParseEquivocationProposalJSON(args[0]) - if err != nil { - return err - } - - content := types.NewEquivocationProposal(proposal.Title, proposal.Summary, proposal.Equivocations) - - from := clientCtx.GetFromAddress() - - msgContent, err := govv1.NewLegacyContent(content, authtypes.NewModuleAddress(govtypes.ModuleName).String()) - if err != nil { - return err - } - - deposit, err := sdk.ParseCoinsNormalized(proposal.Deposit) - if err != nil { - return err - } - - msg, err := govv1.NewMsgSubmitProposal([]sdk.Msg{msgContent}, deposit, from.String(), "", content.GetTitle(), proposal.Summary) - if err != nil { - return err - } - - return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) - }, - } -} - // SubmitChangeRewardDenomsProposalTxCmd returns a CLI command handler for submitting // a change reward denoms proposal via a transaction. func SubmitChangeRewardDenomsProposalTxCmd() *cobra.Command { @@ -378,38 +314,6 @@ func ParseConsumerRemovalProposalJSON(proposalFile string) (ConsumerRemovalPropo return proposal, nil } -type EquivocationProposalJSON struct { - // evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" - Summary string `json:"summary"` - types.EquivocationProposal - - Deposit string `json:"deposit"` -} - -type EquivocationProposalReq struct { - Proposer sdk.AccAddress `json:"proposer"` - - // evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" - types.EquivocationProposal - - Deposit sdk.Coins `json:"deposit"` -} - -func ParseEquivocationProposalJSON(proposalFile string) (EquivocationProposalJSON, error) { - proposal := EquivocationProposalJSON{} - - contents, err := os.ReadFile(filepath.Clean(proposalFile)) - if err != nil { - return proposal, err - } - - if err := json.Unmarshal(contents, &proposal); err != nil { - return proposal, err - } - - return proposal, nil -} - type ChangeRewardDenomsProposalJSON struct { Summary string `json:"summary"` types.ChangeRewardDenomsProposal @@ -535,34 +439,4 @@ func postConsumerRemovalProposalHandlerFn(clientCtx client.Context) http.Handler tx.WriteGeneratedTxResponse(clientCtx, w, req.BaseReq, msg) } } - -func postEquivocationProposalHandlerFn(clientCtx client.Context) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - var req EquivocationProposalReq - if !rest.ReadRESTReq(w, r, clientCtx.LegacyAmino, &req) { - return - } - - req.BaseReq = req.BaseReq.Sanitize() - if !req.BaseReq.ValidateBasic(w) { - return - } - - content := types.NewEquivocationProposal(req.Title, req.Description, req.Equivocations) - - msg, err := govtypes.NewMsgSubmitProposal(content, req.Deposit, req.Proposer) - if rest.CheckBadRequestError(w, err) { - return - } - - if rest.CheckBadRequestError(w, msg.ValidateBasic()) { - return - } - - tx.WriteGeneratedTxResponse(clientCtx, w, req.BaseReq, msg) - } -} - - - */ diff --git a/x/ccv/provider/handler.go b/x/ccv/provider/handler.go index 067d896cda..69b18e9af1 100644 --- a/x/ccv/provider/handler.go +++ b/x/ccv/provider/handler.go @@ -20,6 +20,12 @@ func NewHandler(k *keeper.Keeper) sdk.Handler { case *types.MsgAssignConsumerKey: res, err := msgServer.AssignConsumerKey(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) + 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) } diff --git a/x/ccv/provider/keeper/consumer_equivocation.go b/x/ccv/provider/keeper/consumer_equivocation.go new file mode 100644 index 0000000000..53232b4205 --- /dev/null +++ b/x/ccv/provider/keeper/consumer_equivocation.go @@ -0,0 +1,480 @@ +package keeper + +import ( + "bytes" + "encoding/binary" + "fmt" + + ibcclienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" + ibctmtypes "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint" + + errorsmod "cosmossdk.io/errors" + "cosmossdk.io/math" + + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + sdk "github.com/cosmos/cosmos-sdk/types" + evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" + slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + + tmtypes "github.com/cometbft/cometbft/types" + + "github.com/cosmos/interchain-security/v3/x/ccv/provider/types" + ccvtypes "github.com/cosmos/interchain-security/v3/x/ccv/types" +) + +// +// Double Voting section +// + +// HandleConsumerDoubleVoting verifies a double voting evidence for a given a consumer chain ID +// and a public key and, if successful, executes the slashing, jailing, and tombstoning of the malicious validator. +func (k Keeper) HandleConsumerDoubleVoting( + ctx sdk.Context, + evidence *tmtypes.DuplicateVoteEvidence, + chainID string, + pubkey cryptotypes.PubKey, +) error { + // verifies the double voting evidence using the consumer chain public key + if err := k.VerifyDoubleVotingEvidence(*evidence, chainID, pubkey); err != nil { + return err + } + + // get the validator's consensus address on the provider + providerAddr := k.GetProviderAddrFromConsumerAddr( + ctx, + chainID, + types.NewConsumerConsAddress(sdk.ConsAddress(evidence.VoteA.ValidatorAddress.Bytes())), + ) + + if err := k.SlashValidator(ctx, providerAddr); err != nil { + return err + } + if err := k.JailAndTombstoneValidator(ctx, providerAddr); err != nil { + return err + } + + k.Logger(ctx).Info( + "confirmed equivocation", + "byzantine validator address", providerAddr.String(), + ) + + return nil +} + +// VerifyDoubleVotingEvidence verifies a double voting evidence +// for a given chain id and a validator public key +func (k Keeper) VerifyDoubleVotingEvidence( + evidence tmtypes.DuplicateVoteEvidence, + chainID string, + pubkey cryptotypes.PubKey, +) error { + if pubkey == nil { + return fmt.Errorf("validator public key cannot be empty") + } + + // check that the validator address in the evidence is derived from the provided public key + if !bytes.Equal(pubkey.Address(), evidence.VoteA.ValidatorAddress) { + return errorsmod.Wrapf( + ccvtypes.ErrInvalidDoubleVotingEvidence, + "public key %s doesn't correspond to the validator address %s in double vote evidence", + pubkey.String(), evidence.VoteA.ValidatorAddress.String(), + ) + } + + // Note the age of the evidence isn't checked. + + // height/round/type must be the same + if evidence.VoteA.Height != evidence.VoteB.Height || + evidence.VoteA.Round != evidence.VoteB.Round || + evidence.VoteA.Type != evidence.VoteB.Type { + return errorsmod.Wrapf( + ccvtypes.ErrInvalidDoubleVotingEvidence, + "height/round/type are not the same: %d/%d/%v vs %d/%d/%v", + evidence.VoteA.Height, evidence.VoteA.Round, evidence.VoteA.Type, + evidence.VoteB.Height, evidence.VoteB.Round, evidence.VoteB.Type) + } + + // Addresses must be the same + if !bytes.Equal(evidence.VoteA.ValidatorAddress, evidence.VoteB.ValidatorAddress) { + return errorsmod.Wrapf( + ccvtypes.ErrInvalidDoubleVotingEvidence, + "validator addresses do not match: %X vs %X", + evidence.VoteA.ValidatorAddress, + evidence.VoteB.ValidatorAddress, + ) + } + + // BlockIDs must be different + if evidence.VoteA.BlockID.Equals(evidence.VoteB.BlockID) { + return errorsmod.Wrapf( + ccvtypes.ErrInvalidDoubleVotingEvidence, + "block IDs are the same (%v) - not a real duplicate vote", + evidence.VoteA.BlockID, + ) + } + + va := evidence.VoteA.ToProto() + vb := evidence.VoteB.ToProto() + + // signatures must be valid + if !pubkey.VerifySignature(tmtypes.VoteSignBytes(chainID, va), evidence.VoteA.Signature) { + return fmt.Errorf("verifying VoteA: %w", tmtypes.ErrVoteInvalidSignature) + } + if !pubkey.VerifySignature(tmtypes.VoteSignBytes(chainID, vb), evidence.VoteB.Signature) { + return fmt.Errorf("verifying VoteB: %w", tmtypes.ErrVoteInvalidSignature) + } + + return nil +} + +// +// Light Client Attack (IBC misbehavior) section +// + +// HandleConsumerMisbehaviour checks if the given IBC misbehaviour corresponds to an equivocation light client attack, +// and in this case, slashes, jails, and tombstones +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 consensus states at trusted heights are within trusting period + if err := k.CheckMisbehaviour(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 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 + byzantineValidators, err := k.GetByzantineValidators(ctx, misbehaviour) + if err != nil { + return err + } + + provAddrs := make([]types.ProviderConsAddress, len(byzantineValidators)) + + // slash, jail, and tombstone the Byzantine validators + for _, v := range byzantineValidators { + providerAddr := k.GetProviderAddrFromConsumerAddr( + ctx, + misbehaviour.Header1.Header.ChainID, + types.NewConsumerConsAddress(sdk.ConsAddress(v.Address.Bytes())), + ) + err := k.SlashValidator(ctx, providerAddr) + if err != nil { + logger.Error("failed to slash validator: %s", err) + continue + } + err = k.JailAndTombstoneValidator(ctx, providerAddr) + // JailAndTombstoneValidator should never return an error if + // SlashValidator succeeded because both methods fail if the malicious + // validator is either or both !found, unbonded and tombstoned. + if err != nil { + panic(err) + } + + provAddrs = append(provAddrs, providerAddr) + } + + // Return an error if no validators were punished + if len(provAddrs) == 0 { + return fmt.Errorf("failed to slash, jail, or tombstone all validators: %v", byzantineValidators) + } + + logger.Info( + "confirmed equivocation light client attack", + "byzantine validators slashed, jailed and tombstoned", provAddrs, + ) + + return nil +} + +// 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) (validators []*tmtypes.Validator, err error) { + // construct the trusted and conflicted light blocks + lightBlock1, err := headerToLightBlock(*misbehaviour.Header1) + if err != nil { + return validators, err + } + lightBlock2, err := headerToLightBlock(*misbehaviour.Header2) + if err != nil { + return validators, err + } + + // Check if the misbehaviour corresponds to an Amnesia attack, + // meaning that the conflicting headers have both valid state transitions + // and different commit rounds. In this case, we return no validators as + // we can't identify the byzantine validators. + // + // Note that we cannot differentiate which of the headers is trusted or malicious, + if !headersStateTransitionsAreConflicting(*lightBlock1.Header, *lightBlock2.Header) && lightBlock1.Commit.Round != lightBlock2.Commit.Round { + return validators, nil + } + + // compare the signatures of the headers + // and return the intersection of validators who signed both + + // create a map with the validators' address that signed header1 + header1Signers := map[string]int{} + for idx, sign := range lightBlock1.Commit.Signatures { + if sign.Absent() { + continue + } + header1Signers[sign.ValidatorAddress.String()] = idx + } + + // iterate over the header2 signers and check if they signed header1 + for sigIdxHeader2, sign := range lightBlock2.Commit.Signatures { + if sign.Absent() { + continue + } + if sigIdxHeader1, ok := header1Signers[sign.ValidatorAddress.String()]; ok { + if err := verifyLightBlockCommitSig(*lightBlock1, sigIdxHeader1); err != nil { + return nil, err + } + + if err := verifyLightBlockCommitSig(*lightBlock2, sigIdxHeader2); err != nil { + return nil, err + } + + _, val := lightBlock1.ValidatorSet.GetByAddress(sign.ValidatorAddress) + validators = append(validators, val) + } + } + + return validators, nil +} + +// 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 { + return nil, err + } + + vs, err := tmtypes.ValidatorSetFromProto(h.ValidatorSet) + if err != nil { + return nil, err + } + + return &tmtypes.LightBlock{ + SignedHeader: sh, + ValidatorSet: vs, + }, nil +} + +// CheckMisbehaviour checks that headers in the given misbehaviour forms +// a valid light client attack on a light client that tracks an ICS consumer chain +func (k Keeper) CheckMisbehaviour(ctx sdk.Context, misbehaviour ibctmtypes.Misbehaviour) error { + // check that the misbehaviour is for an ICS consumer chain + clientId, found := k.GetConsumerClientId(ctx, misbehaviour.Header1.Header.ChainID) + if !found { + return fmt.Errorf("incorrect misbehaviour with conflicting headers from a non-existent consumer chain: %s", misbehaviour.Header1.Header.ChainID) + } else if misbehaviour.ClientId != clientId { + return fmt.Errorf("incorrect misbehaviour: expected client ID for consumer chain %s is %s got %s", + misbehaviour.Header1.Header.ChainID, + clientId, + misbehaviour.ClientId, + ) + } + + clientState, found := k.clientKeeper.GetClientState(ctx, clientId) + if !found { + return errorsmod.Wrapf(ibcclienttypes.ErrClientNotFound, "cannot find client state for client with ID %s", clientId) + } + + clientStore := k.clientKeeper.ClientStore(ctx, clientId) + + // 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 CheckForMisbehaviour in ibc-go/blob/v7.3.0/modules/light-clients/07-tendermint/misbehaviour_handle.go#L73 + if !misbehaviour.Header1.GetHeight().EQ(misbehaviour.Header2.GetHeight()) { + return errorsmod.Wrap(ibcclienttypes.ErrInvalidMisbehaviour, "headers are not at same height") + } + + // CheckForMisbehaviour verifies that the headers have different blockID hashes + ok := clientState.CheckForMisbehaviour(ctx, k.cdc, clientStore, &misbehaviour) + if !ok { + return errorsmod.Wrapf(ibcclienttypes.ErrInvalidMisbehaviour, "invalid misbehaviour for client-id: %s", misbehaviour.ClientId) + } + + // VerifyClientMessage calls verifyMisbehaviour which verifies that the headers in the misbehaviour + // are valid against their respective trusted consensus states and that at least a TrustLevel of the validator set signed their commit, + // see checkMisbehaviourHeader in ibc-go/blob/v7.3.0/modules/light-clients/07-tendermint/misbehaviour_handle.go#L126 + if err := clientState.VerifyClientMessage(ctx, k.cdc, clientStore, &misbehaviour); err != nil { + return err + } + + return nil +} + +// Check if the given block headers have conflicting state transitions. +// Note that this method was copied from ConflictingHeaderIsInvalid in CometBFT, +// see https://github.com/cometbft/cometbft/blob/v0.34.27/types/evidence.go#L285 +func headersStateTransitionsAreConflicting(h1, h2 tmtypes.Header) bool { + return !bytes.Equal(h1.ValidatorsHash, h2.ValidatorsHash) || + !bytes.Equal(h1.NextValidatorsHash, h2.NextValidatorsHash) || + !bytes.Equal(h1.ConsensusHash, h2.ConsensusHash) || + !bytes.Equal(h1.AppHash, h2.AppHash) || + !bytes.Equal(h1.LastResultsHash, h2.LastResultsHash) +} + +func verifyLightBlockCommitSig(lightBlock tmtypes.LightBlock, sigIdx int) error { + // get signature + sig := lightBlock.Commit.Signatures[sigIdx] + + // get validator + idx, val := lightBlock.ValidatorSet.GetByAddress(sig.ValidatorAddress) + if idx == -1 { + return fmt.Errorf("incorrect signature: validator address %s isn't part of the validator set", sig.ValidatorAddress.String()) + } + + // verify validator pubkey corresponds to signature validator address + if !bytes.Equal(val.PubKey.Address(), sig.ValidatorAddress) { + return fmt.Errorf("validator public key doesn't correspond to signature validator address: %s!= %s", val.PubKey.Address(), sig.ValidatorAddress) + } + + // validate signature + voteSignBytes := lightBlock.Commit.VoteSignBytes(lightBlock.ChainID, int32(sigIdx)) + if !val.PubKey.VerifySignature(voteSignBytes, sig.Signature) { + return fmt.Errorf("wrong signature (#%d): %X", sigIdx, sig.Signature) + } + + return nil +} + +// +// Punish Validator section +// + +// JailAndTombstoneValidator jails and tombstones the validator with the given provider consensus address +func (k Keeper) JailAndTombstoneValidator(ctx sdk.Context, providerAddr types.ProviderConsAddress) error { + validator, found := k.stakingKeeper.GetValidatorByConsAddr(ctx, providerAddr.ToSdkConsAddr()) + if !found { + return errorsmod.Wrapf(slashingtypes.ErrNoValidatorForAddress, "provider consensus address: %s", providerAddr.String()) + } + + if validator.IsUnbonded() { + return fmt.Errorf("validator is unbonded. provider consensus address: %s", providerAddr.String()) + } + + if k.slashingKeeper.IsTombstoned(ctx, providerAddr.ToSdkConsAddr()) { + return fmt.Errorf("validator is tombstoned. provider consensus address: %s", providerAddr.String()) + } + + // jail validator if not already + if !validator.IsJailed() { + k.stakingKeeper.Jail(ctx, providerAddr.ToSdkConsAddr()) + } + + k.slashingKeeper.JailUntil(ctx, providerAddr.ToSdkConsAddr(), evidencetypes.DoubleSignJailEndTime) + + // Tombstone the validator so that we cannot slash the validator more than once + // Note that we cannot simply use the fact that a validator is jailed to avoid slashing more than once + // because then a validator could i) perform an equivocation, ii) get jailed (e.g., through downtime) + // and in such a case the validator would not get slashed when we call `SlashValidator`. + k.slashingKeeper.Tombstone(ctx, providerAddr.ToSdkConsAddr()) + + return nil +} + +// ComputePowerToSlash computes the power to be slashed based on the tokens in non-matured `undelegations` and +// `redelegations`, as well as the current `power` of the validator. +// Note that this method does not perform any slashing. +func (k Keeper) ComputePowerToSlash(ctx sdk.Context, validator stakingtypes.Validator, undelegations []stakingtypes.UnbondingDelegation, + redelegations []stakingtypes.Redelegation, power int64, powerReduction math.Int, +) int64 { + // compute the total numbers of tokens currently being undelegated + undelegationsInTokens := sdk.NewInt(0) + + // Note that we use a **cached** context to avoid any actual slashing of undelegations or redelegations. + cachedCtx, _ := ctx.CacheContext() + for _, u := range undelegations { + amountSlashed := k.stakingKeeper.SlashUnbondingDelegation(cachedCtx, u, 0, sdk.NewDec(1)) + undelegationsInTokens = undelegationsInTokens.Add(amountSlashed) + } + + // compute the total numbers of tokens currently being redelegated + redelegationsInTokens := sdk.NewInt(0) + for _, r := range redelegations { + amountSlashed := k.stakingKeeper.SlashRedelegation(cachedCtx, validator, r, 0, sdk.NewDec(1)) + redelegationsInTokens = redelegationsInTokens.Add(amountSlashed) + } + + // The power we pass to staking's keeper `Slash` method is the current power of the validator together with the total + // power of all the currently undelegated and redelegated tokens (see docs/docs/adrs/adr-013-equivocation-slashing.md). + undelegationsAndRedelegationsInPower := sdk.TokensToConsensusPower( + undelegationsInTokens.Add(redelegationsInTokens), powerReduction) + + return power + undelegationsAndRedelegationsInPower +} + +// SlashValidator slashes validator with given provider Address +func (k Keeper) SlashValidator(ctx sdk.Context, providerAddr types.ProviderConsAddress) error { + validator, found := k.stakingKeeper.GetValidatorByConsAddr(ctx, providerAddr.ToSdkConsAddr()) + if !found { + return errorsmod.Wrapf(slashingtypes.ErrNoValidatorForAddress, "provider consensus address: %s", providerAddr.String()) + } + + if validator.IsUnbonded() { + return fmt.Errorf("validator is unbonded. provider consensus address: %s", providerAddr.String()) + } + + if k.slashingKeeper.IsTombstoned(ctx, providerAddr.ToSdkConsAddr()) { + return fmt.Errorf("validator is tombstoned. provider consensus address: %s", providerAddr.String()) + } + + undelegations := k.stakingKeeper.GetUnbondingDelegationsFromValidator(ctx, validator.GetOperator()) + redelegations := k.stakingKeeper.GetRedelegationsFromSrcValidator(ctx, validator.GetOperator()) + lastPower := k.stakingKeeper.GetLastValidatorPower(ctx, validator.GetOperator()) + powerReduction := k.stakingKeeper.PowerReduction(ctx) + totalPower := k.ComputePowerToSlash(ctx, validator, undelegations, redelegations, lastPower, powerReduction) + slashFraction := k.slashingKeeper.SlashFractionDoubleSign(ctx) + + consAdrr, err := validator.GetConsAddr() + if err != nil { + panic(err) + } + + k.stakingKeeper.SlashWithInfractionReason(ctx, consAdrr, 0, totalPower, slashFraction, stakingtypes.Infraction_INFRACTION_DOUBLE_SIGN) + + return nil +} + +// +// CRUD section +// + +// SetEquivocationEvidenceMinHeight sets the the minimum height +// of a valid consumer equivocation evidence for a given consumer chain ID +func (k Keeper) SetEquivocationEvidenceMinHeight(ctx sdk.Context, chainID string, height uint64) { + store := ctx.KVStore(k.storeKey) + heightBytes := make([]byte, 8) + binary.BigEndian.PutUint64(heightBytes, height) + + store.Set(types.EquivocationEvidenceMinHeightKey(chainID), heightBytes) +} + +// GetEquivocationEvidenceMinHeight returns the the minimum height +// of a valid consumer equivocation evidence for a given consumer chain ID +func (k Keeper) GetEquivocationEvidenceMinHeight(ctx sdk.Context, chainID string) uint64 { + store := ctx.KVStore(k.storeKey) + bz := store.Get(types.EquivocationEvidenceMinHeightKey(chainID)) + if bz == nil { + return 0 + } + + return binary.BigEndian.Uint64(bz) +} + +// DeleteEquivocationEvidenceMinHeight deletes the the minimum height +// of a valid consumer equivocation evidence for a given consumer chain ID +func (k Keeper) DeleteEquivocationEvidenceMinHeight(ctx sdk.Context, chainID string) { + store := ctx.KVStore(k.storeKey) + store.Delete(types.EquivocationEvidenceMinHeightKey(chainID)) +} diff --git a/x/ccv/provider/keeper/consumer_equivocation_test.go b/x/ccv/provider/keeper/consumer_equivocation_test.go new file mode 100644 index 0000000000..f7a736dff1 --- /dev/null +++ b/x/ccv/provider/keeper/consumer_equivocation_test.go @@ -0,0 +1,790 @@ +package keeper_test + +import ( + "fmt" + "testing" + "time" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" + + "cosmossdk.io/math" + + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + sdk "github.com/cosmos/cosmos-sdk/types" + evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + + tmtypes "github.com/cometbft/cometbft/types" + + cryptotestutil "github.com/cosmos/interchain-security/v3/testutil/crypto" + testkeeper "github.com/cosmos/interchain-security/v3/testutil/keeper" + "github.com/cosmos/interchain-security/v3/x/ccv/provider/types" +) + +func TestVerifyDoubleVotingEvidence(t *testing.T) { + keeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + chainID := "consumer" + + signer1 := tmtypes.NewMockPV() + signer2 := tmtypes.NewMockPV() + + val1 := tmtypes.NewValidator(signer1.PrivKey.PubKey(), 1) + val2 := tmtypes.NewValidator(signer2.PrivKey.PubKey(), 1) + + valSet := tmtypes.NewValidatorSet([]*tmtypes.Validator{val1, val2}) + + blockID1 := cryptotestutil.MakeBlockID([]byte("blockhash"), 1000, []byte("partshash")) + blockID2 := cryptotestutil.MakeBlockID([]byte("blockhash2"), 1000, []byte("partshash")) + + ctx = ctx.WithBlockTime(time.Now()) + + valPubkey1, err := cryptocodec.FromTmPubKeyInterface(val1.PubKey) + require.NoError(t, err) + + valPubkey2, err := cryptocodec.FromTmPubKeyInterface(val2.PubKey) + require.NoError(t, err) + + testCases := []struct { + name string + votes []*tmtypes.Vote + chainID string + pubkey cryptotypes.PubKey + expPass bool + }{ + { + "invalid verifying public key - shouldn't pass", + []*tmtypes.Vote{ + cryptotestutil.MakeAndSignVote( + blockID1, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer1, + chainID, + ), + cryptotestutil.MakeAndSignVote( + blockID2, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer1, + chainID, + ), + }, + chainID, + nil, + false, + }, + { + "verifying public key doesn't correspond to validator address", + []*tmtypes.Vote{ + cryptotestutil.MakeAndSignVoteWithForgedValAddress( + blockID1, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer1, + signer2, + chainID, + ), + cryptotestutil.MakeAndSignVoteWithForgedValAddress( + blockID2, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer1, + signer2, + chainID, + ), + }, + chainID, + valPubkey1, + false, + }, + { + "evidence has votes with different block height - shouldn't pass", + []*tmtypes.Vote{ + cryptotestutil.MakeAndSignVote( + blockID1, + ctx.BlockHeight()+1, + ctx.BlockTime(), + valSet, + signer1, + chainID, + ), + cryptotestutil.MakeAndSignVote( + blockID2, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer1, + chainID, + ), + }, + chainID, + valPubkey1, + false, + }, + { + "evidence has votes with different validator address - shouldn't pass", + []*tmtypes.Vote{ + cryptotestutil.MakeAndSignVote( + blockID1, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer1, + chainID, + ), + cryptotestutil.MakeAndSignVote( + blockID2, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer2, + chainID, + ), + }, + chainID, + valPubkey1, + false, + }, + { + "evidence has votes with same block IDs - shouldn't pass", + []*tmtypes.Vote{ + cryptotestutil.MakeAndSignVote( + blockID1, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer1, + chainID, + ), + cryptotestutil.MakeAndSignVote( + blockID1, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer1, + chainID, + ), + }, + chainID, + valPubkey1, + false, + }, + { + "given chain ID isn't the same as the one used to sign the votes - shouldn't pass", + []*tmtypes.Vote{ + cryptotestutil.MakeAndSignVote( + blockID1, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer1, + chainID, + ), + cryptotestutil.MakeAndSignVote( + blockID2, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer1, + chainID, + ), + }, + "WrongChainID", + valPubkey1, + false, + }, + { + "voteA is signed using the wrong chain ID - shouldn't pass", + []*tmtypes.Vote{ + cryptotestutil.MakeAndSignVote( + blockID1, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer1, + "WrongChainID", + ), + cryptotestutil.MakeAndSignVote( + blockID2, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer1, + chainID, + ), + }, + chainID, + valPubkey1, + false, + }, + { + "voteB is signed using the wrong chain ID - shouldn't pass", + []*tmtypes.Vote{ + cryptotestutil.MakeAndSignVote( + blockID1, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer1, + chainID, + ), + cryptotestutil.MakeAndSignVote( + blockID2, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer1, + "WrongChainID", + ), + }, + chainID, + valPubkey1, + false, + }, + { + "wrong public key - shouldn't pass", + []*tmtypes.Vote{ + cryptotestutil.MakeAndSignVote( + blockID1, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer1, + chainID, + ), + cryptotestutil.MakeAndSignVote( + blockID2, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer1, + chainID, + ), + }, + chainID, + valPubkey2, + false, + }, + { + "valid double voting evidence should pass", + []*tmtypes.Vote{ + cryptotestutil.MakeAndSignVote( + blockID1, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer1, + chainID, + ), + cryptotestutil.MakeAndSignVote( + blockID2, + ctx.BlockHeight(), + ctx.BlockTime(), + valSet, + signer1, + chainID, + ), + }, + chainID, + valPubkey1, + true, + }, + } + + for _, tc := range testCases { + err = keeper.VerifyDoubleVotingEvidence( + tmtypes.DuplicateVoteEvidence{ + VoteA: tc.votes[0], + VoteB: tc.votes[1], + ValidatorPower: val1.VotingPower, + TotalVotingPower: val1.VotingPower, + Timestamp: tc.votes[0].Timestamp, + }, + tc.chainID, + tc.pubkey, + ) + if tc.expPass { + require.NoError(t, err) + } else { + require.Error(t, err) + } + } +} + +// TestJailAndTombstoneValidator tests that the jailing of a validator is only executed +// under the conditions that the validator is neither unbonded, nor jailed, nor tombstoned. +func TestJailAndTombstoneValidator(t *testing.T) { + providerConsAddr := cryptotestutil.NewCryptoIdentityFromIntSeed(7842334).ProviderConsAddress() + testCases := []struct { + name string + provAddr types.ProviderConsAddress + expectedCalls func(sdk.Context, testkeeper.MockedKeepers, types.ProviderConsAddress) []*gomock.Call + }{ + { + "unfound validator", + providerConsAddr, + func(ctx sdk.Context, mocks testkeeper.MockedKeepers, + provAddr types.ProviderConsAddress, + ) []*gomock.Call { + return []*gomock.Call{ + // We only expect a single call to GetValidatorByConsAddr. + // Method will return once validator is not found. + mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr( + ctx, providerConsAddr.ToSdkConsAddr()).Return( + stakingtypes.Validator{}, false, // false = Not found. + ).Times(1), + } + }, + }, + { + "unbonded validator", + providerConsAddr, + func(ctx sdk.Context, mocks testkeeper.MockedKeepers, + provAddr types.ProviderConsAddress, + ) []*gomock.Call { + return []*gomock.Call{ + // We only expect a single call to GetValidatorByConsAddr. + mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr( + ctx, providerConsAddr.ToSdkConsAddr()).Return( + stakingtypes.Validator{Status: stakingtypes.Unbonded}, true, + ).Times(1), + } + }, + }, + { + "tombstoned validator", + providerConsAddr, + func(ctx sdk.Context, mocks testkeeper.MockedKeepers, + provAddr types.ProviderConsAddress, + ) []*gomock.Call { + return []*gomock.Call{ + mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr( + ctx, providerConsAddr.ToSdkConsAddr()).Return( + stakingtypes.Validator{}, true, + ).Times(1), + mocks.MockSlashingKeeper.EXPECT().IsTombstoned( + ctx, providerConsAddr.ToSdkConsAddr()).Return( + true, + ).Times(1), + } + }, + }, + { + "jailed validator", + providerConsAddr, + func(ctx sdk.Context, mocks testkeeper.MockedKeepers, + provAddr types.ProviderConsAddress, + ) []*gomock.Call { + return []*gomock.Call{ + mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr( + ctx, providerConsAddr.ToSdkConsAddr()).Return( + stakingtypes.Validator{Jailed: true}, true, + ).Times(1), + mocks.MockSlashingKeeper.EXPECT().IsTombstoned( + ctx, providerConsAddr.ToSdkConsAddr()).Return( + false, + ).Times(1), + mocks.MockSlashingKeeper.EXPECT().JailUntil( + ctx, providerConsAddr.ToSdkConsAddr(), evidencetypes.DoubleSignJailEndTime). + Times(1), + mocks.MockSlashingKeeper.EXPECT().Tombstone( + ctx, providerConsAddr.ToSdkConsAddr()). + Times(1), + } + }, + }, + { + "bonded validator", + providerConsAddr, + func(ctx sdk.Context, mocks testkeeper.MockedKeepers, + provAddr types.ProviderConsAddress, + ) []*gomock.Call { + return []*gomock.Call{ + mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr( + ctx, providerConsAddr.ToSdkConsAddr()).Return( + stakingtypes.Validator{Status: stakingtypes.Bonded}, true, + ).Times(1), + mocks.MockSlashingKeeper.EXPECT().IsTombstoned( + ctx, providerConsAddr.ToSdkConsAddr()).Return( + false, + ).Times(1), + mocks.MockStakingKeeper.EXPECT().Jail( + ctx, providerConsAddr.ToSdkConsAddr()). + Times(1), + mocks.MockSlashingKeeper.EXPECT().JailUntil( + ctx, providerConsAddr.ToSdkConsAddr(), evidencetypes.DoubleSignJailEndTime). + Times(1), + mocks.MockSlashingKeeper.EXPECT().Tombstone( + ctx, providerConsAddr.ToSdkConsAddr()). + Times(1), + } + }, + }, + } + + for _, tc := range testCases { + providerKeeper, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx( + t, testkeeper.NewInMemKeeperParams(t)) + + // Setup expected mock calls + gomock.InOrder(tc.expectedCalls(ctx, mocks, tc.provAddr)...) + + // Execute method and assert expected mock calls + providerKeeper.JailAndTombstoneValidator(ctx, tc.provAddr) + + ctrl.Finish() + } +} + +// createUndelegation creates an undelegation with `len(initialBalances)` entries +func createUndelegation(initialBalances []int64, completionTimes []time.Time) stakingtypes.UnbondingDelegation { + var entries []stakingtypes.UnbondingDelegationEntry + for i, balance := range initialBalances { + entry := stakingtypes.UnbondingDelegationEntry{ + InitialBalance: sdk.NewInt(balance), + CompletionTime: completionTimes[i], + } + entries = append(entries, entry) + } + + return stakingtypes.UnbondingDelegation{Entries: entries} +} + +// createRedelegation creates a redelegation with `len(initialBalances)` entries +func createRedelegation(initialBalances []int64, completionTimes []time.Time) stakingtypes.Redelegation { + var entries []stakingtypes.RedelegationEntry + for i, balance := range initialBalances { + entry := stakingtypes.RedelegationEntry{ + InitialBalance: sdk.NewInt(balance), + CompletionTime: completionTimes[i], + } + entries = append(entries, entry) + } + + return stakingtypes.Redelegation{Entries: entries} +} + +// TestComputePowerToSlash tests that `ComputePowerToSlash` computes the correct power to be slashed based on +// the tokens in non-mature undelegation and redelegation entries, as well as the current power of the validator +func TestComputePowerToSlash(t *testing.T) { + providerKeeper, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + // undelegation or redelegation entries with completion time `now` have matured + now := ctx.BlockHeader().Time + // undelegation or redelegation entries with completion time one hour in the future have not yet matured + nowPlus1Hour := now.Add(time.Hour) + + testCases := []struct { + name string + undelegations []stakingtypes.UnbondingDelegation + redelegations []stakingtypes.Redelegation + power int64 + powerReduction math.Int + expectedPower int64 + }{ + { + "both undelegations and redelegations 1", + // 1000 total undelegation tokens + []stakingtypes.UnbondingDelegation{ + createUndelegation([]int64{250, 250}, []time.Time{nowPlus1Hour, nowPlus1Hour}), + createUndelegation([]int64{500}, []time.Time{nowPlus1Hour, nowPlus1Hour}), + }, + // 1000 total redelegation tokens + []stakingtypes.Redelegation{ + createRedelegation([]int64{500}, []time.Time{nowPlus1Hour, nowPlus1Hour}), + createRedelegation([]int64{250, 250}, []time.Time{nowPlus1Hour, nowPlus1Hour}), + }, + int64(1000), + sdk.NewInt(1), + int64(2000/1 + 1000), + }, + { + "both undelegations and redelegations 2", + // 2000 total undelegation tokens + []stakingtypes.UnbondingDelegation{ + createUndelegation([]int64{250, 250}, []time.Time{nowPlus1Hour, nowPlus1Hour}), + createUndelegation([]int64{}, []time.Time{}), + createUndelegation([]int64{100, 100}, []time.Time{nowPlus1Hour, nowPlus1Hour}), + createUndelegation([]int64{800}, []time.Time{nowPlus1Hour}), + createUndelegation([]int64{500}, []time.Time{nowPlus1Hour}), + }, + // 3500 total redelegation tokens + []stakingtypes.Redelegation{ + createRedelegation([]int64{}, []time.Time{}), + createRedelegation([]int64{1600}, []time.Time{nowPlus1Hour}), + createRedelegation([]int64{350, 250}, []time.Time{nowPlus1Hour, nowPlus1Hour}), + createRedelegation([]int64{700, 200}, []time.Time{nowPlus1Hour, nowPlus1Hour}), + createRedelegation([]int64{}, []time.Time{}), + createRedelegation([]int64{400}, []time.Time{nowPlus1Hour}), + }, + int64(8391), + sdk.NewInt(2), + int64((2000+3500)/2 + 8391), + }, + { + "no undelegations or redelegations, return provided power", + []stakingtypes.UnbondingDelegation{}, + []stakingtypes.Redelegation{}, + int64(3000), + sdk.NewInt(5), + int64(3000), // expectedPower is 0/5 + 3000 + }, + { + "no undelegations", + []stakingtypes.UnbondingDelegation{}, + // 2000 total redelegation tokens + []stakingtypes.Redelegation{ + createRedelegation([]int64{}, []time.Time{}), + createRedelegation([]int64{500}, []time.Time{nowPlus1Hour}), + createRedelegation([]int64{250, 250}, []time.Time{nowPlus1Hour, nowPlus1Hour}), + createRedelegation([]int64{700, 200}, []time.Time{nowPlus1Hour, nowPlus1Hour}), + createRedelegation([]int64{}, []time.Time{}), + createRedelegation([]int64{100}, []time.Time{nowPlus1Hour}), + }, + int64(17), + sdk.NewInt(3), + int64(2000/3 + 17), + }, + { + "no redelegations", + // 2000 total undelegation tokens + []stakingtypes.UnbondingDelegation{ + createUndelegation([]int64{250, 250}, []time.Time{nowPlus1Hour, nowPlus1Hour}), + createUndelegation([]int64{}, []time.Time{}), + createUndelegation([]int64{100, 100}, []time.Time{nowPlus1Hour, nowPlus1Hour}), + createUndelegation([]int64{800}, []time.Time{nowPlus1Hour}), + createUndelegation([]int64{500}, []time.Time{nowPlus1Hour}), + }, + []stakingtypes.Redelegation{}, + int64(1), + sdk.NewInt(3), + int64(2000/3 + 1), + }, + { + "both (mature) undelegations and redelegations", + // 2000 total undelegation tokens, 250 + 100 + 500 = 850 of those are from mature undelegations, + // so 2000 - 850 = 1150 + []stakingtypes.UnbondingDelegation{ + createUndelegation([]int64{250, 250}, []time.Time{nowPlus1Hour, now}), + createUndelegation([]int64{}, []time.Time{}), + createUndelegation([]int64{100, 100}, []time.Time{now, nowPlus1Hour}), + createUndelegation([]int64{800}, []time.Time{nowPlus1Hour}), + createUndelegation([]int64{500}, []time.Time{now}), + }, + // 3500 total redelegation tokens, 350 + 200 + 400 = 950 of those are from mature redelegations + // so 3500 - 950 = 2550 + []stakingtypes.Redelegation{ + createRedelegation([]int64{}, []time.Time{}), + createRedelegation([]int64{1600}, []time.Time{nowPlus1Hour}), + createRedelegation([]int64{350, 250}, []time.Time{now, nowPlus1Hour}), + createRedelegation([]int64{700, 200}, []time.Time{nowPlus1Hour, now}), + createRedelegation([]int64{}, []time.Time{}), + createRedelegation([]int64{400}, []time.Time{now}), + }, + int64(8391), + sdk.NewInt(2), + int64((1150+2550)/2 + 8391), + }, + } + + pubKey, _ := cryptocodec.FromTmPubKeyInterface(tmtypes.NewMockPV().PrivKey.PubKey()) + validator, _ := stakingtypes.NewValidator(pubKey.Address().Bytes(), pubKey, stakingtypes.Description{}) + + for _, tc := range testCases { + gomock.InOrder(mocks.MockStakingKeeper.EXPECT(). + SlashUnbondingDelegation(gomock.Any(), gomock.Any(), int64(0), sdk.NewDec(1)). + DoAndReturn( + func(_ sdk.Context, undelegation stakingtypes.UnbondingDelegation, _ int64, _ sdk.Dec) math.Int { + sum := sdk.NewInt(0) + for _, r := range undelegation.Entries { + if r.IsMature(ctx.BlockTime()) { + continue + } + sum = sum.Add(sdk.NewInt(r.InitialBalance.Int64())) + } + return sum + }).AnyTimes(), + mocks.MockStakingKeeper.EXPECT(). + SlashRedelegation(gomock.Any(), gomock.Any(), gomock.Any(), int64(0), sdk.NewDec(1)). + DoAndReturn( + func(ctx sdk.Context, _ stakingtypes.Validator, redelegation stakingtypes.Redelegation, _ int64, _ sdk.Dec) math.Int { + sum := sdk.NewInt(0) + for _, r := range redelegation.Entries { + if r.IsMature(ctx.BlockTime()) { + continue + } + sum = sum.Add(sdk.NewInt(r.InitialBalance.Int64())) + } + return sum + }).AnyTimes(), + ) + + actualPower := providerKeeper.ComputePowerToSlash(ctx, validator, + tc.undelegations, tc.redelegations, tc.power, tc.powerReduction) + + if tc.expectedPower != actualPower { + require.Fail(t, fmt.Sprintf("\"%s\" failed", tc.name), + "expected is %d but actual is %d", tc.expectedPower, actualPower) + } + } +} + +// TestSlashValidator asserts that `SlashValidator` calls the staking module's `Slash` method +// with the correct arguments (i.e., `infractionHeight` of 0 and the expected slash power) +func TestSlashValidator(t *testing.T) { + keeper, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + // undelegation or redelegation entries with completion time `now` have matured + now := ctx.BlockHeader().Time + // undelegation or redelegation entries with completion time one hour in the future have not yet matured + nowPlus1Hour := now.Add(time.Hour) + + keeperParams := testkeeper.NewInMemKeeperParams(t) + testkeeper.NewInMemProviderKeeper(keeperParams, mocks) + + pubKey, _ := cryptocodec.FromTmPubKeyInterface(tmtypes.NewMockPV().PrivKey.PubKey()) + + validator, err := stakingtypes.NewValidator( + sdk.ValAddress(pubKey.Address().Bytes()), + pubKey, + stakingtypes.NewDescription("", "", "", "", ""), + ) + require.NoError(t, err) + validator.Status = stakingtypes.Bonded + + consAddr, _ := validator.GetConsAddr() + providerAddr := types.NewProviderConsAddress(consAddr) + + // we create 1000 tokens worth of undelegations, 750 of them are non-matured + // we also create 1000 tokens worth of redelegations, 750 of them are non-matured + undelegations := []stakingtypes.UnbondingDelegation{ + createUndelegation([]int64{250, 250}, []time.Time{nowPlus1Hour, now}), + createUndelegation([]int64{500}, []time.Time{nowPlus1Hour}), + } + redelegations := []stakingtypes.Redelegation{ + createRedelegation([]int64{250, 250}, []time.Time{now, nowPlus1Hour}), + createRedelegation([]int64{500}, []time.Time{nowPlus1Hour}), + } + + // validator's current power + currentPower := int64(3000) + + powerReduction := sdk.NewInt(2) + slashFraction, _ := sdk.NewDecFromStr("0.5") + + // the call to `Slash` should provide an `infractionHeight` of 0 and an expected power of + // (750 (undelegations) + 750 (redelegations)) / 2 (= powerReduction) + 3000 (currentPower) = 3750 + expectedInfractionHeight := int64(0) + expectedSlashPower := int64(3750) + + expectedCalls := []*gomock.Call{ + mocks.MockStakingKeeper.EXPECT(). + GetValidatorByConsAddr(ctx, gomock.Any()). + Return(validator, true), + mocks.MockSlashingKeeper.EXPECT(). + IsTombstoned(ctx, consAddr). + Return(false), + mocks.MockStakingKeeper.EXPECT(). + GetUnbondingDelegationsFromValidator(ctx, validator.GetOperator()). + Return(undelegations), + mocks.MockStakingKeeper.EXPECT(). + GetRedelegationsFromSrcValidator(ctx, validator.GetOperator()). + Return(redelegations), + mocks.MockStakingKeeper.EXPECT(). + GetLastValidatorPower(ctx, validator.GetOperator()). + Return(currentPower), + mocks.MockStakingKeeper.EXPECT(). + PowerReduction(ctx). + Return(powerReduction), + mocks.MockStakingKeeper.EXPECT(). + SlashUnbondingDelegation(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + DoAndReturn( + func(_ sdk.Context, undelegation stakingtypes.UnbondingDelegation, _ int64, _ sdk.Dec) math.Int { + sum := sdk.NewInt(0) + for _, r := range undelegation.Entries { + if r.IsMature(ctx.BlockTime()) { + continue + } + sum = sum.Add(sdk.NewInt(r.InitialBalance.Int64())) + } + return sum + }).AnyTimes(), + mocks.MockStakingKeeper.EXPECT(). + SlashRedelegation(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + DoAndReturn( + func(_ sdk.Context, _ stakingtypes.Validator, redelegation stakingtypes.Redelegation, _ int64, _ sdk.Dec) math.Int { + sum := sdk.NewInt(0) + for _, r := range redelegation.Entries { + if r.IsMature(ctx.BlockTime()) { + continue + } + sum = sum.Add(sdk.NewInt(r.InitialBalance.Int64())) + } + return sum + }).AnyTimes(), + mocks.MockSlashingKeeper.EXPECT(). + SlashFractionDoubleSign(ctx). + Return(slashFraction), + mocks.MockStakingKeeper.EXPECT(). + SlashWithInfractionReason(ctx, consAddr, expectedInfractionHeight, expectedSlashPower, slashFraction, stakingtypes.Infraction_INFRACTION_DOUBLE_SIGN). + Times(1), + } + + gomock.InOrder(expectedCalls...) + keeper.SlashValidator(ctx, providerAddr) +} + +// TestSlashValidatorDoesNotSlashIfValidatorIsUnbonded asserts that `SlashValidator` does not call +// the staking module's `Slash` method if the validator to be slashed is unbonded +func TestSlashValidatorDoesNotSlashIfValidatorIsUnbonded(t *testing.T) { + keeper, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + keeperParams := testkeeper.NewInMemKeeperParams(t) + testkeeper.NewInMemProviderKeeper(keeperParams, mocks) + + pubKey, _ := cryptocodec.FromTmPubKeyInterface(tmtypes.NewMockPV().PrivKey.PubKey()) + + // validator is initially unbonded + validator, _ := stakingtypes.NewValidator(pubKey.Address().Bytes(), pubKey, stakingtypes.Description{}) + + consAddr, _ := validator.GetConsAddr() + providerAddr := types.NewProviderConsAddress(consAddr) + + expectedCalls := []*gomock.Call{ + mocks.MockStakingKeeper.EXPECT(). + GetValidatorByConsAddr(ctx, gomock.Any()). + Return(validator, true), + } + + gomock.InOrder(expectedCalls...) + keeper.SlashValidator(ctx, providerAddr) +} + +func TestEquivocationEvidenceMinHeightCRUD(t *testing.T) { + chainID := consumer + expMinHeight := uint64(12) + keeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + height := keeper.GetEquivocationEvidenceMinHeight(ctx, chainID) + require.Zero(t, height, "equivocation evidence min height should be 0") + + keeper.SetEquivocationEvidenceMinHeight(ctx, chainID, expMinHeight) + height = keeper.GetEquivocationEvidenceMinHeight(ctx, chainID) + require.Equal(t, height, expMinHeight) + + keeper.DeleteEquivocationEvidenceMinHeight(ctx, chainID) + height = keeper.GetEquivocationEvidenceMinHeight(ctx, chainID) + require.Zero(t, height, "equivocation evidence min height should be 0") +} diff --git a/x/ccv/provider/keeper/hooks.go b/x/ccv/provider/keeper/hooks.go index 35c9b96301..da2daa7512 100644 --- a/x/ccv/provider/keeper/hooks.go +++ b/x/ccv/provider/keeper/hooks.go @@ -167,3 +167,7 @@ func (h Hooks) AfterValidatorBeginUnbonding(_ sdk.Context, _ sdk.ConsAddress, _ func (h Hooks) BeforeDelegationRemoved(_ sdk.Context, _ sdk.AccAddress, _ sdk.ValAddress) error { return nil } + +func (h Hooks) BeforeTokenizeShareRecordRemoved(_ sdk.Context, _ uint64) error { + return nil +} diff --git a/x/ccv/provider/keeper/keeper.go b/x/ccv/provider/keeper/keeper.go index d4470ec0aa..0148354b93 100644 --- a/x/ccv/provider/keeper/keeper.go +++ b/x/ccv/provider/keeper/keeper.go @@ -41,7 +41,6 @@ type Keeper struct { clientKeeper ccv.ClientKeeper stakingKeeper ccv.StakingKeeper slashingKeeper ccv.SlashingKeeper - evidenceKeeper ccv.EvidenceKeeper distributionKeeper ccv.DistributionKeeper bankKeeper ccv.BankKeeper feeCollectorName string @@ -53,8 +52,8 @@ func NewKeeper( channelKeeper ccv.ChannelKeeper, portKeeper ccv.PortKeeper, connectionKeeper ccv.ConnectionKeeper, clientKeeper ccv.ClientKeeper, stakingKeeper ccv.StakingKeeper, slashingKeeper ccv.SlashingKeeper, - accountKeeper ccv.AccountKeeper, evidenceKeeper ccv.EvidenceKeeper, - distributionKeeper ccv.DistributionKeeper, bankKeeper ccv.BankKeeper, + accountKeeper ccv.AccountKeeper, distributionKeeper ccv.DistributionKeeper, + bankKeeper ccv.BankKeeper, feeCollectorName string, ) Keeper { // set KeyTable if it has not already been set @@ -74,7 +73,6 @@ func NewKeeper( stakingKeeper: stakingKeeper, slashingKeeper: slashingKeeper, accountKeeper: accountKeeper, - evidenceKeeper: evidenceKeeper, distributionKeeper: distributionKeeper, bankKeeper: bankKeeper, feeCollectorName: feeCollectorName, @@ -94,8 +92,8 @@ func (k *Keeper) SetParamSpace(ctx sdk.Context, ps paramtypes.Subspace) { // non-nil values for all its fields. Otherwise this method will panic. func (k Keeper) mustValidateFields() { // Ensures no fields are missed in this validation - if reflect.ValueOf(k).NumField() != 15 { - panic("number of fields in provider keeper is not 15") + if reflect.ValueOf(k).NumField() != 14 { + panic("number of fields in provider keeper is not 14") } ccv.PanicIfZeroOrNil(k.cdc, "cdc") // 1 @@ -109,10 +107,9 @@ func (k Keeper) mustValidateFields() { ccv.PanicIfZeroOrNil(k.clientKeeper, "clientKeeper") // 9 ccv.PanicIfZeroOrNil(k.stakingKeeper, "stakingKeeper") // 10 ccv.PanicIfZeroOrNil(k.slashingKeeper, "slashingKeeper") // 11 - ccv.PanicIfZeroOrNil(k.evidenceKeeper, "evidenceKeeper") // 12 - ccv.PanicIfZeroOrNil(k.distributionKeeper, "distributionKeeper") // 13 - ccv.PanicIfZeroOrNil(k.bankKeeper, "bankKeeper") // 14 - ccv.PanicIfZeroOrNil(k.feeCollectorName, "feeCollectorName") // 15 + ccv.PanicIfZeroOrNil(k.distributionKeeper, "distributionKeeper") // 12 + ccv.PanicIfZeroOrNil(k.bankKeeper, "bankKeeper") // 13 + ccv.PanicIfZeroOrNil(k.feeCollectorName, "feeCollectorName") // 14 } // Logger returns a module-specific logger. diff --git a/x/ccv/provider/keeper/msg_server.go b/x/ccv/provider/keeper/msg_server.go index fecbdc8c36..03b98403bc 100644 --- a/x/ccv/provider/keeper/msg_server.go +++ b/x/ccv/provider/keeper/msg_server.go @@ -6,12 +6,15 @@ import ( errorsmod "cosmossdk.io/errors" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" sdk "github.com/cosmos/cosmos-sdk/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" tmprotocrypto "github.com/cometbft/cometbft/proto/tendermint/crypto" + tmtypes "github.com/cometbft/cometbft/types" "github.com/cosmos/interchain-security/v3/x/ccv/provider/types" + ccvtypes "github.com/cosmos/interchain-security/v3/x/ccv/types" ) type msgServer struct { @@ -26,7 +29,6 @@ func NewMsgServerImpl(keeper *Keeper) types.MsgServer { var _ types.MsgServer = msgServer{} -// CreateValidator defines a method for creating a new validator func (k msgServer) AssignConsumerKey(goCtx context.Context, msg *types.MsgAssignConsumerKey) (*types.MsgAssignConsumerKeyResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) @@ -107,3 +109,72 @@ func (k msgServer) AssignConsumerKey(goCtx context.Context, msg *types.MsgAssign return &types.MsgAssignConsumerKeyResponse{}, nil } + +func (k msgServer) SubmitConsumerMisbehaviour(goCtx context.Context, msg *types.MsgSubmitConsumerMisbehaviour) (*types.MsgSubmitConsumerMisbehaviourResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + if err := k.Keeper.HandleConsumerMisbehaviour(ctx, *msg.Misbehaviour); err != nil { + return nil, err + } + + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + 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()), + ), + }) + + return &types.MsgSubmitConsumerMisbehaviourResponse{}, nil +} + +func (k msgServer) SubmitConsumerDoubleVoting(goCtx context.Context, msg *types.MsgSubmitConsumerDoubleVoting) (*types.MsgSubmitConsumerDoubleVotingResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + evidence, err := tmtypes.DuplicateVoteEvidenceFromProto(msg.DuplicateVoteEvidence) + if err != nil { + return nil, err + } + + // parse the validator set of the infraction block header in order + // to find the public key of the validator who double voted + + // get validator set + valset, err := tmtypes.ValidatorSetFromProto(msg.InfractionBlockHeader.ValidatorSet) + if err != nil { + return nil, err + } + + // look for the malicious validator in the validator set + _, validator := valset.GetByAddress(evidence.VoteA.ValidatorAddress) + if validator == nil { + return nil, errorsmod.Wrapf( + ccvtypes.ErrInvalidDoubleVotingEvidence, + "misbehaving validator %s cannot be found in the infraction block header validator set", + evidence.VoteA.ValidatorAddress) + } + + pubkey, err := cryptocodec.FromTmPubKeyInterface(validator.PubKey) + if err != nil { + return nil, err + } + + // handle the double voting evidence using the chain ID of the infraction block header + // and the malicious validator's public key + if err := k.Keeper.HandleConsumerDoubleVoting(ctx, evidence, msg.InfractionBlockHeader.Header.ChainID, pubkey); err != nil { + return nil, err + } + + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + ccvtypes.EventTypeSubmitConsumerDoubleVoting, + sdk.NewAttribute(ccvtypes.AttributeConsumerDoubleVoting, msg.DuplicateVoteEvidence.String()), + sdk.NewAttribute(ccvtypes.AttributeChainID, msg.InfractionBlockHeader.Header.ChainID), + sdk.NewAttribute(ccvtypes.AttributeSubmitterAddress, msg.Submitter), + ), + }) + + return &types.MsgSubmitConsumerDoubleVotingResponse{}, nil +} diff --git a/x/ccv/provider/keeper/proposal.go b/x/ccv/provider/keeper/proposal.go index 20bf812395..395d372517 100644 --- a/x/ccv/provider/keeper/proposal.go +++ b/x/ccv/provider/keeper/proposal.go @@ -591,18 +591,6 @@ func (k Keeper) StopConsumerChainInCachedCtx(ctx sdk.Context, p types.ConsumerRe return } -// HandleEquivocationProposal handles an equivocation proposal. -// Proposal will be accepted if a record in the SlashLog exists for a given validator address. -func (k Keeper) HandleEquivocationProposal(ctx sdk.Context, p *types.EquivocationProposal) error { - for _, ev := range p.Equivocations { - if !k.GetSlashLog(ctx, types.NewProviderConsAddress(ev.GetConsensusAddress())) { - return fmt.Errorf("no equivocation record found for validator %s", ev.GetConsensusAddress().String()) - } - k.evidenceKeeper.HandleEquivocationEvidence(ctx, ev) - } - return nil -} - func (k Keeper) HandleConsumerRewardDenomProposal(ctx sdk.Context, p *types.ChangeRewardDenomsProposal) error { for _, denomToAdd := range p.DenomsToAdd { // Log error and move on if one of the denoms is already registered diff --git a/x/ccv/provider/keeper/proposal_test.go b/x/ccv/provider/keeper/proposal_test.go index 568b2c30e0..a5f0e6d536 100644 --- a/x/ccv/provider/keeper/proposal_test.go +++ b/x/ccv/provider/keeper/proposal_test.go @@ -14,7 +14,6 @@ import ( "github.com/stretchr/testify/require" sdk "github.com/cosmos/cosmos-sdk/types" - evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" abci "github.com/cometbft/cometbft/abci/types" @@ -1074,63 +1073,3 @@ func TestBeginBlockCCR(t *testing.T) { ctx, invalidProp.ChainId, invalidProp.StopTime) require.False(t, found) } - -func TestHandleEquivocationProposal(t *testing.T) { - equivocations := []*evidencetypes.Equivocation{ - { - Time: time.Now(), - Height: 1, - Power: 1, - ConsensusAddress: "cosmosvalcons1kswr5sq599365kcjmhgufevfps9njf43e4lwdk", - }, - { - Time: time.Now(), - Height: 1, - Power: 1, - ConsensusAddress: "cosmosvalcons1ezyrq65s3gshhx5585w6mpusq3xsj3ayzf4uv6", - }, - } - - prop := &providertypes.EquivocationProposal{ - Equivocations: []*evidencetypes.Equivocation{equivocations[0], equivocations[1]}, - } - - testCases := []struct { - name string - setSlashLogs bool - expectEquivsHandled bool - expectErr bool - }{ - {name: "slash logs not set", setSlashLogs: false, expectEquivsHandled: false, expectErr: true}, - {name: "slash logs set", setSlashLogs: true, expectEquivsHandled: true, expectErr: false}, - } - for _, tc := range testCases { - - keeperParams := testkeeper.NewInMemKeeperParams(t) - keeper, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, keeperParams) - - if tc.setSlashLogs { - // Set slash logs according to cons addrs in equivocations - consAddr := equivocations[0].GetConsensusAddress() - require.NotNil(t, consAddr, "consensus address could not be parsed") - keeper.SetSlashLog(ctx, providertypes.NewProviderConsAddress(consAddr)) - consAddr = equivocations[1].GetConsensusAddress() - require.NotNil(t, consAddr, "consensus address could not be parsed") - keeper.SetSlashLog(ctx, providertypes.NewProviderConsAddress(consAddr)) - } - - if tc.expectEquivsHandled { - mocks.MockEvidenceKeeper.EXPECT().HandleEquivocationEvidence(ctx, equivocations[0]) - mocks.MockEvidenceKeeper.EXPECT().HandleEquivocationEvidence(ctx, equivocations[1]) - } - - err := keeper.HandleEquivocationProposal(ctx, prop) - - if tc.expectErr { - require.Error(t, err) - } else { - require.NoError(t, err) - } - ctrl.Finish() - } -} diff --git a/x/ccv/provider/proposal_handler.go b/x/ccv/provider/proposal_handler.go index 7af7ec4e5f..b5bd4a6597 100644 --- a/x/ccv/provider/proposal_handler.go +++ b/x/ccv/provider/proposal_handler.go @@ -12,7 +12,7 @@ import ( ) // NewProviderProposalHandler defines the handler for consumer addition, -// consumer removal and equivocation proposals. +// consumer removal, and consumer reward denom proposals. // Passed proposals are executed during EndBlock. func NewProviderProposalHandler(k keeper.Keeper) govv1beta1.Handler { return func(ctx sdk.Context, content govv1beta1.Content) error { @@ -21,8 +21,6 @@ func NewProviderProposalHandler(k keeper.Keeper) govv1beta1.Handler { return k.HandleConsumerAdditionProposal(ctx, c) case *types.ConsumerRemovalProposal: return k.HandleConsumerRemovalProposal(ctx, c) - case *types.EquivocationProposal: - return k.HandleEquivocationProposal(ctx, c) case *types.ChangeRewardDenomsProposal: return k.HandleConsumerRewardDenomProposal(ctx, c) default: diff --git a/x/ccv/provider/proposal_handler_test.go b/x/ccv/provider/proposal_handler_test.go index 8f1322b3d2..7e4d586097 100644 --- a/x/ccv/provider/proposal_handler_test.go +++ b/x/ccv/provider/proposal_handler_test.go @@ -10,7 +10,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types" - evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" govv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" testkeeper "github.com/cosmos/interchain-security/v3/testutil/keeper" @@ -19,12 +18,11 @@ import ( ) // TestProviderProposalHandler tests the highest level handler for proposals -// concerning creating, stopping consumer chains and submitting equivocations. +// concerning creating, stopping consumer chains and changing reward denom. func TestProviderProposalHandler(t *testing.T) { // Snapshot times asserted in tests now := time.Now().UTC() hourFromNow := now.Add(time.Hour).UTC() - equivocation := &evidencetypes.Equivocation{Height: 42} testCases := []struct { name string @@ -32,7 +30,6 @@ func TestProviderProposalHandler(t *testing.T) { blockTime time.Time expValidConsumerAddition bool expValidConsumerRemoval bool - expValidEquivocation bool expValidChangeRewardDenom bool }{ { @@ -58,21 +55,6 @@ func TestProviderProposalHandler(t *testing.T) { blockTime: hourFromNow, expValidConsumerRemoval: true, }, - { - // no slash log for equivocation - name: "invalid equivocation proposal", - content: providertypes.NewEquivocationProposal( - "title", "description", []*evidencetypes.Equivocation{equivocation}), - blockTime: hourFromNow, - expValidEquivocation: false, - }, - { - name: "valid equivocation proposal", - content: providertypes.NewEquivocationProposal( - "title", "description", []*evidencetypes.Equivocation{equivocation}), - blockTime: hourFromNow, - expValidEquivocation: true, - }, { name: "valid change reward denoms proposal", content: providertypes.NewChangeRewardDenomsProposal( @@ -118,10 +100,6 @@ func TestProviderProposalHandler(t *testing.T) { // assert mocks for expected calls to `StopConsumerChain` when closing the underlying channel gomock.InOrder(testkeeper.GetMocksForStopConsumerChainWithCloseChannel(ctx, &mocks)...) - - case tc.expValidEquivocation: - providerKeeper.SetSlashLog(ctx, providertypes.NewProviderConsAddress(equivocation.GetConsensusAddress())) - mocks.MockEvidenceKeeper.EXPECT().HandleEquivocationEvidence(ctx, equivocation) case tc.expValidChangeRewardDenom: // Nothing to mock } @@ -131,7 +109,7 @@ func TestProviderProposalHandler(t *testing.T) { err := proposalHandler(ctx, tc.content) if tc.expValidConsumerAddition || tc.expValidConsumerRemoval || - tc.expValidEquivocation || tc.expValidChangeRewardDenom { + tc.expValidChangeRewardDenom { require.NoError(t, err) } else { require.Error(t, err) diff --git a/x/ccv/provider/types/codec.go b/x/ccv/provider/types/codec.go index ceed3bf789..d1b9203efb 100644 --- a/x/ccv/provider/types/codec.go +++ b/x/ccv/provider/types/codec.go @@ -35,7 +35,14 @@ func RegisterInterfaces(registry codectypes.InterfaceRegistry) { (*govv1beta1.Content)(nil), &ChangeRewardDenomsProposal{}, ) - + registry.RegisterImplementations( + (*sdk.Msg)(nil), + &MsgSubmitConsumerMisbehaviour{}, + ) + registry.RegisterImplementations( + (*sdk.Msg)(nil), + &MsgSubmitConsumerDoubleVoting{}, + ) msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) } diff --git a/x/ccv/provider/types/keys.go b/x/ccv/provider/types/keys.go index ee4c11015b..95b5b4b820 100644 --- a/x/ccv/provider/types/keys.go +++ b/x/ccv/provider/types/keys.go @@ -138,6 +138,10 @@ const ( // handled in the current block VSCMaturedHandledThisBlockBytePrefix + // EquivocationEvidenceMinHeightBytePrefix is the byte prefix storing the mapping from consumer chain IDs + // to the minimum height of a valid consumer equivocation evidence + EquivocationEvidenceMinHeightBytePrefix + // NOTE: DO NOT ADD NEW BYTE PREFIXES HERE WITHOUT ADDING THEM TO getAllKeyPrefixes() IN keys_test.go ) @@ -377,6 +381,12 @@ func ConsumerRewardDenomsKey(denom string) []byte { return append([]byte{ConsumerRewardDenomsBytePrefix}, []byte(denom)...) } +// EquivocationEvidenceMinHeightKey returns the key storing the minimum height +// of a valid consumer equivocation evidence for a given consumer chain ID +func EquivocationEvidenceMinHeightKey(consumerChainID string) []byte { + return append([]byte{EquivocationEvidenceMinHeightBytePrefix}, []byte(consumerChainID)...) +} + // NOTE: DO NOT ADD FULLY DEFINED KEY FUNCTIONS WITHOUT ADDING THEM TO getAllFullyDefinedKeys() IN keys_test.go // diff --git a/x/ccv/provider/types/keys_test.go b/x/ccv/provider/types/keys_test.go index 9f470f4a82..6b263a4fdb 100644 --- a/x/ccv/provider/types/keys_test.go +++ b/x/ccv/provider/types/keys_test.go @@ -54,6 +54,7 @@ func getAllKeyPrefixes() []byte { providertypes.ConsumerAddrsToPruneBytePrefix, providertypes.SlashLogBytePrefix, providertypes.VSCMaturedHandledThisBlockBytePrefix, + providertypes.EquivocationEvidenceMinHeightBytePrefix, } } @@ -98,6 +99,7 @@ func getAllFullyDefinedKeys() [][]byte { providertypes.ConsumerAddrsToPruneKey("chainID", 88), providertypes.SlashLogKey(providertypes.NewProviderConsAddress([]byte{0x05})), providertypes.VSCMaturedHandledThisBlockKey(), + providertypes.EquivocationEvidenceMinHeightKey("chainID"), } } diff --git a/x/ccv/provider/types/msg.go b/x/ccv/provider/types/msg.go index 901aa03600..c2551f9ceb 100644 --- a/x/ccv/provider/types/msg.go +++ b/x/ccv/provider/types/msg.go @@ -2,17 +2,33 @@ package types import ( "encoding/json" + "fmt" "strings" + ibctmtypes "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint" + + errorsmod "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + + tmtypes "github.com/cometbft/cometbft/proto/tendermint/types" + + ccvtypes "github.com/cosmos/interchain-security/v3/x/ccv/types" ) // provider message types const ( - TypeMsgAssignConsumerKey = "assign_consumer_key" + TypeMsgAssignConsumerKey = "assign_consumer_key" + TypeMsgSubmitConsumerMisbehaviour = "submit_consumer_misbehaviour" + TypeMsgSubmitConsumerDoubleVoting = "submit_consumer_double_vote" ) -var _ sdk.Msg = &MsgAssignConsumerKey{} +var ( + _ sdk.Msg = &MsgAssignConsumerKey{} + _ sdk.Msg = &MsgSubmitConsumerMisbehaviour{} + _ sdk.Msg = &MsgSubmitConsumerDoubleVoting{} +) // NewMsgAssignConsumerKey creates a new MsgAssignConsumerKey instance. // Delegator address and validator address are the same. @@ -49,7 +65,7 @@ func (msg MsgAssignConsumerKey) GetSigners() []sdk.AccAddress { // GetSignBytes returns the message bytes to sign over. func (msg MsgAssignConsumerKey) GetSignBytes() []byte { - bz := ModuleCdc.MustMarshalJSON(&msg) + bz := ccvtypes.ModuleCdc.MustMarshalJSON(&msg) return sdk.MustSortJSON(bz) } @@ -93,3 +109,99 @@ func ParseConsumerKeyFromJson(jsonStr string) (pkType, key string, err error) { } return pubKey.Type, pubKey.Key, 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. +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 errorsmod.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 := ccvtypes.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} +} + +func NewMsgSubmitConsumerDoubleVoting(submitter sdk.AccAddress, ev *tmtypes.DuplicateVoteEvidence, header *ibctmtypes.Header) (*MsgSubmitConsumerDoubleVoting, error) { + return &MsgSubmitConsumerDoubleVoting{Submitter: submitter.String(), DuplicateVoteEvidence: ev, InfractionBlockHeader: header}, nil +} + +// Route implements the sdk.Msg interface. +func (msg MsgSubmitConsumerDoubleVoting) Route() string { return RouterKey } + +// Type implements the sdk.Msg interface. +func (msg MsgSubmitConsumerDoubleVoting) Type() string { + return TypeMsgSubmitConsumerDoubleVoting +} + +// Type implements the sdk.Msg interface. +func (msg MsgSubmitConsumerDoubleVoting) ValidateBasic() error { + if msg.Submitter == "" { + return errorsmod.Wrap(sdkerrors.ErrInvalidAddress, msg.Submitter) + } + if msg.DuplicateVoteEvidence == nil { + return fmt.Errorf("double voting evidence cannot be nil") + } + + if msg.InfractionBlockHeader == nil { + return fmt.Errorf("infraction block header cannot be nil") + } + + if msg.InfractionBlockHeader.SignedHeader == nil { + return fmt.Errorf("signed header in infraction block header cannot be nil") + } + + if msg.InfractionBlockHeader.SignedHeader.Header == nil { + return fmt.Errorf("invalid signed header in infraction block header, 'SignedHeader.Header' is nil") + } + + if msg.InfractionBlockHeader.ValidatorSet == nil { + return fmt.Errorf("invalid infraction block header, validator set is nil") + } + + return nil +} + +// Type implements the sdk.Msg interface. +func (msg MsgSubmitConsumerDoubleVoting) GetSignBytes() []byte { + bz := ccvtypes.ModuleCdc.MustMarshalJSON(&msg) + return sdk.MustSortJSON(bz) +} + +// Type implements the sdk.Msg interface. +func (msg MsgSubmitConsumerDoubleVoting) 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/proposal.go b/x/ccv/provider/types/proposal.go index b2286d4adf..d1755852ef 100644 --- a/x/ccv/provider/types/proposal.go +++ b/x/ccv/provider/types/proposal.go @@ -27,15 +27,15 @@ const ( var ( _ govv1beta1.Content = &ConsumerAdditionProposal{} _ govv1beta1.Content = &ConsumerRemovalProposal{} - _ govv1beta1.Content = &EquivocationProposal{} _ govv1beta1.Content = &ChangeRewardDenomsProposal{} + _ govv1beta1.Content = &EquivocationProposal{} ) func init() { govv1beta1.RegisterProposalType(ProposalTypeConsumerAddition) govv1beta1.RegisterProposalType(ProposalTypeConsumerRemoval) - govv1beta1.RegisterProposalType(ProposalTypeEquivocation) govv1beta1.RegisterProposalType(ProposalTypeChangeRewardDenoms) + govv1beta1.RegisterProposalType(ProposalTypeEquivocation) } // NewConsumerAdditionProposal creates a new consumer addition proposal. @@ -204,6 +204,8 @@ func (sccp *ConsumerRemovalProposal) ValidateBasic() error { } // NewEquivocationProposal creates a new equivocation proposal. +// [DEPRECATED]: do not use because equivocations can be submitted +// and verified automatically on the provider. func NewEquivocationProposal(title, description string, equivocations []*evidencetypes.Equivocation) govv1beta1.Content { return &EquivocationProposal{ Title: title, diff --git a/x/ccv/provider/types/proposal_test.go b/x/ccv/provider/types/proposal_test.go index 357c555d0e..d8f10766dd 100644 --- a/x/ccv/provider/types/proposal_test.go +++ b/x/ccv/provider/types/proposal_test.go @@ -12,7 +12,6 @@ import ( "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" - evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" govv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" govv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" @@ -307,62 +306,6 @@ func TestConsumerAdditionProposalString(t *testing.T) { require.Equal(t, expect, proposal.String(), "string method for ConsumerAdditionProposal returned unexpected string") } -func TestEquivocationProposalValidateBasic(t *testing.T) { - tests := []struct { - name string - proposal govv1beta1.Content - expectedError string - }{ - { - name: "fail: validate abstract - empty title", - proposal: types.NewEquivocationProposal("", "", nil), - expectedError: "proposal title cannot be blank: invalid proposal content", - }, - { - name: "fail: equivocations is empty", - proposal: types.NewEquivocationProposal("title", "desc", nil), - expectedError: "invalid equivocation proposal: empty equivocations", - }, - { - name: "fail: invalid equivocation", - proposal: types.NewEquivocationProposal("title", "desc", - []*evidencetypes.Equivocation{ - { - Time: time.Now(), - Height: 1, - Power: 1, - ConsensusAddress: "addr", - }, - {}, // invalid one - }), - expectedError: "invalid equivocation time: 0001-01-01 00:00:00 +0000 UTC", - }, - { - name: "ok", - proposal: types.NewEquivocationProposal("title", "desc", - []*evidencetypes.Equivocation{ - { - Time: time.Now(), - Height: 1, - Power: 1, - ConsensusAddress: "addr", - }, - }), - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := tt.proposal.ValidateBasic() - - if tt.expectedError != "" { - require.EqualError(t, err, tt.expectedError) - return - } - require.NoError(t, err) - }) - } -} - func TestChangeRewardDenomsProposalValidateBasic(t *testing.T) { tcs := []struct { name string diff --git a/x/ccv/provider/types/provider.pb.go b/x/ccv/provider/types/provider.pb.go index 22bc700a2f..e251053ef9 100644 --- a/x/ccv/provider/types/provider.pb.go +++ b/x/ccv/provider/types/provider.pb.go @@ -205,6 +205,10 @@ func (m *ConsumerRemovalProposal) GetStopTime() time.Time { // punish a validator for equivocation on a consumer chain. // // This type is only used internally to the consumer CCV module. +// WARNING: This message is deprecated now that equivocations can be submitted +// and verified automatically on the provider. (see SubmitConsumerDoubleVoting in proto/interchain-security/ccv/provider/v1/tx.proto). +// +// Deprecated: Do not use. type EquivocationProposal struct { // the title of the proposal Title string `protobuf:"bytes,1,opt,name=title,proto3" json:"title,omitempty"` @@ -1411,113 +1415,113 @@ func init() { } var fileDescriptor_f22ec409a72b7b72 = []byte{ - // 1690 bytes of a gzipped FileDescriptorProto + // 1694 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x58, 0xcd, 0x72, 0x1b, 0xc7, - 0x11, 0xe6, 0x12, 0x20, 0x45, 0x34, 0xf8, 0xa7, 0x25, 0x6d, 0x81, 0x0a, 0x03, 0x52, 0xeb, 0xd8, - 0x61, 0xca, 0xe5, 0x45, 0x48, 0x55, 0xaa, 0x5c, 0xaa, 0xb8, 0x5c, 0x24, 0x28, 0x5b, 0x14, 0x63, - 0x8b, 0x5e, 0x32, 0x54, 0x25, 0x39, 0x6c, 0x0d, 0x66, 0x47, 0xc0, 0x14, 0x77, 0x77, 0x56, 0x33, - 0xb3, 0x2b, 0xe3, 0x92, 0x73, 0x8e, 0xce, 0xcd, 0x95, 0x5c, 0x9c, 0xbc, 0x40, 0xce, 0x79, 0x03, + 0x11, 0xe6, 0x12, 0x20, 0x45, 0x34, 0xf8, 0xa7, 0x25, 0x6d, 0x2d, 0x15, 0x06, 0xa4, 0xd6, 0xb1, + 0xc3, 0x94, 0xcb, 0x8b, 0x90, 0xaa, 0x54, 0xb9, 0x54, 0x71, 0xb9, 0x48, 0x50, 0xb6, 0x28, 0xc6, + 0x16, 0xbd, 0x64, 0xa8, 0x4a, 0x72, 0xd8, 0x1a, 0xcc, 0x8e, 0x80, 0x29, 0x2e, 0x76, 0x56, 0x33, + 0xb3, 0x2b, 0xe3, 0x92, 0x73, 0x8e, 0xce, 0xcd, 0x95, 0x4b, 0x9c, 0xbc, 0x40, 0xce, 0x79, 0x03, 0x1f, 0x7d, 0xcc, 0xc9, 0x4e, 0x51, 0xc7, 0xbc, 0x44, 0x6a, 0x66, 0xff, 0x41, 0x52, 0x81, 0x4a, 0xf1, 0x6d, 0xb6, 0xa7, 0xfb, 0xeb, 0x9e, 0xe9, 0xee, 0xaf, 0x07, 0x80, 0x3d, 0x1a, 0x4a, 0xc2, - 0xf1, 0x08, 0xd1, 0xd0, 0x15, 0x04, 0xc7, 0x9c, 0xca, 0x71, 0x0f, 0xe3, 0xa4, 0x17, 0x71, 0x96, - 0x50, 0x8f, 0xf0, 0x5e, 0xb2, 0x5b, 0xac, 0xed, 0x88, 0x33, 0xc9, 0xcc, 0x77, 0xae, 0xb1, 0xb1, - 0x31, 0x4e, 0xec, 0x42, 0x2f, 0xd9, 0xbd, 0xfb, 0xee, 0x4d, 0xc0, 0xc9, 0x6e, 0xef, 0x05, 0xe5, - 0x24, 0xc5, 0xba, 0xbb, 0x3e, 0x64, 0x43, 0xa6, 0x97, 0x3d, 0xb5, 0xca, 0xa4, 0x5b, 0x43, 0xc6, - 0x86, 0x3e, 0xe9, 0xe9, 0xaf, 0x41, 0xfc, 0xac, 0x27, 0x69, 0x40, 0x84, 0x44, 0x41, 0x94, 0x29, - 0x74, 0x27, 0x15, 0xbc, 0x98, 0x23, 0x49, 0x59, 0x98, 0x03, 0xd0, 0x01, 0xee, 0x61, 0xc6, 0x49, - 0x0f, 0xfb, 0x94, 0x84, 0x52, 0x79, 0x4d, 0x57, 0x99, 0x42, 0x4f, 0x29, 0xf8, 0x74, 0x38, 0x92, - 0xa9, 0x58, 0xf4, 0x24, 0x09, 0x3d, 0xc2, 0x03, 0x9a, 0x2a, 0x97, 0x5f, 0x99, 0xc1, 0x66, 0x65, - 0x1f, 0xf3, 0x71, 0x24, 0x59, 0xef, 0x82, 0x8c, 0x45, 0xb6, 0xfb, 0x1e, 0x66, 0x22, 0x60, 0xa2, - 0x47, 0xd4, 0xf9, 0x43, 0x4c, 0x7a, 0xc9, 0xee, 0x80, 0x48, 0xb4, 0x5b, 0x08, 0xf2, 0xb8, 0x33, - 0xbd, 0x01, 0x12, 0xa5, 0x0e, 0x66, 0x34, 0x8b, 0xdb, 0xfa, 0x61, 0x1e, 0x3a, 0x7d, 0x16, 0x8a, - 0x38, 0x20, 0x7c, 0xdf, 0xf3, 0xa8, 0x3a, 0xd2, 0x09, 0x67, 0x11, 0x13, 0xc8, 0x37, 0xd7, 0x61, - 0x4e, 0x52, 0xe9, 0x93, 0x8e, 0xb1, 0x6d, 0xec, 0xb4, 0x9c, 0xf4, 0xc3, 0xdc, 0x86, 0xb6, 0x47, - 0x04, 0xe6, 0x34, 0x52, 0xca, 0x9d, 0x59, 0xbd, 0x57, 0x15, 0x99, 0x1b, 0xb0, 0x90, 0xe6, 0x81, - 0x7a, 0x9d, 0x86, 0xde, 0xbe, 0xa5, 0xbf, 0x8f, 0x3c, 0xf3, 0x53, 0x58, 0xa6, 0x21, 0x95, 0x14, - 0xf9, 0xee, 0x88, 0xa8, 0xdb, 0xe8, 0x34, 0xb7, 0x8d, 0x9d, 0xf6, 0xde, 0x5d, 0x9b, 0x0e, 0xb0, - 0xad, 0x2e, 0xd0, 0xce, 0xae, 0x2d, 0xd9, 0xb5, 0x1f, 0x69, 0x8d, 0x83, 0xe6, 0xb7, 0xdf, 0x6f, - 0xcd, 0x38, 0x4b, 0x99, 0x5d, 0x2a, 0x34, 0xef, 0xc1, 0xe2, 0x90, 0x84, 0x44, 0x50, 0xe1, 0x8e, - 0x90, 0x18, 0x75, 0xe6, 0xb6, 0x8d, 0x9d, 0x45, 0xa7, 0x9d, 0xc9, 0x1e, 0x21, 0x31, 0x32, 0xb7, - 0xa0, 0x3d, 0xa0, 0x21, 0xe2, 0xe3, 0x54, 0x63, 0x5e, 0x6b, 0x40, 0x2a, 0xd2, 0x0a, 0x7d, 0x00, - 0x11, 0xa1, 0x17, 0xa1, 0xab, 0xb2, 0xdd, 0xb9, 0x95, 0x05, 0x92, 0x66, 0xda, 0xce, 0x33, 0x6d, - 0x9f, 0xe5, 0xa5, 0x70, 0xb0, 0xa0, 0x02, 0xf9, 0xea, 0x87, 0x2d, 0xc3, 0x69, 0x69, 0x3b, 0xb5, - 0x63, 0x7e, 0x0e, 0xab, 0x71, 0x38, 0x60, 0xa1, 0x47, 0xc3, 0xa1, 0x1b, 0x11, 0x4e, 0x99, 0xd7, - 0x59, 0xd0, 0x50, 0x1b, 0x57, 0xa0, 0x0e, 0xb3, 0xa2, 0x49, 0x91, 0xbe, 0x56, 0x48, 0x2b, 0x85, - 0xf1, 0x89, 0xb6, 0x35, 0xbf, 0x00, 0x13, 0xe3, 0x44, 0x87, 0xc4, 0x62, 0x99, 0x23, 0xb6, 0xa6, - 0x47, 0x5c, 0xc5, 0x38, 0x39, 0x4b, 0xad, 0x33, 0xc8, 0x3f, 0xc0, 0x1d, 0xc9, 0x51, 0x28, 0x9e, - 0x11, 0x3e, 0x89, 0x0b, 0xd3, 0xe3, 0xbe, 0x95, 0x63, 0xd4, 0xc1, 0x1f, 0xc1, 0x36, 0xce, 0x0a, - 0xc8, 0xe5, 0xc4, 0xa3, 0x42, 0x72, 0x3a, 0x88, 0x95, 0xad, 0xfb, 0x8c, 0x23, 0xac, 0x6b, 0xa4, - 0xad, 0x8b, 0xa0, 0x9b, 0xeb, 0x39, 0x35, 0xb5, 0x4f, 0x32, 0x2d, 0xf3, 0x09, 0xfc, 0x6c, 0xe0, - 0x33, 0x7c, 0x21, 0x54, 0x70, 0x6e, 0x0d, 0x49, 0xbb, 0x0e, 0xa8, 0x10, 0x0a, 0x6d, 0x71, 0xdb, - 0xd8, 0x69, 0x38, 0xf7, 0x52, 0xdd, 0x13, 0xc2, 0x0f, 0x2b, 0x9a, 0x67, 0x15, 0x45, 0xf3, 0x03, - 0x30, 0x47, 0x54, 0x48, 0xc6, 0x29, 0x46, 0xbe, 0x4b, 0x42, 0xc9, 0x29, 0x11, 0x9d, 0x25, 0x6d, - 0x7e, 0xbb, 0xdc, 0x79, 0x98, 0x6e, 0x98, 0x8f, 0xe1, 0xde, 0x8d, 0x4e, 0x5d, 0x3c, 0x42, 0x61, - 0x48, 0xfc, 0xce, 0xb2, 0x3e, 0xca, 0x96, 0x77, 0x83, 0xcf, 0x7e, 0xaa, 0xf6, 0x60, 0xe1, 0x4f, - 0xdf, 0x6c, 0xcd, 0x7c, 0xfd, 0xcd, 0xd6, 0x8c, 0xf5, 0x0f, 0x03, 0xee, 0xf4, 0x8b, 0x83, 0x07, - 0x2c, 0x41, 0xfe, 0x8f, 0xd9, 0x60, 0xfb, 0xd0, 0x12, 0x92, 0x45, 0x69, 0x49, 0x37, 0x5f, 0xa3, - 0xa4, 0x17, 0x94, 0x99, 0xda, 0xb0, 0xfe, 0x6a, 0xc0, 0xfa, 0xc3, 0xe7, 0x31, 0x4d, 0x18, 0x46, - 0xff, 0x17, 0x3e, 0x38, 0x86, 0x25, 0x52, 0xc1, 0x13, 0x9d, 0xc6, 0x76, 0x63, 0xa7, 0xbd, 0xf7, - 0xae, 0x9d, 0x92, 0x93, 0x5d, 0x70, 0x56, 0x46, 0x50, 0x76, 0xd5, 0xbb, 0x53, 0xb7, 0xb5, 0xfe, - 0x6e, 0xc0, 0x5d, 0x75, 0xcb, 0x43, 0xe2, 0x90, 0x17, 0x88, 0x7b, 0x87, 0x24, 0x64, 0x81, 0x78, - 0xe3, 0x18, 0x2d, 0x58, 0xf2, 0x34, 0x92, 0x2b, 0x99, 0x8b, 0x3c, 0x4f, 0xc7, 0xa8, 0x75, 0x94, - 0xf0, 0x8c, 0xed, 0x7b, 0x9e, 0xb9, 0x03, 0xab, 0xa5, 0x0e, 0x57, 0xb9, 0x54, 0x57, 0xac, 0xd4, - 0x96, 0x73, 0x35, 0x9d, 0x61, 0x62, 0xfd, 0xc7, 0x80, 0xd5, 0x4f, 0x7d, 0x36, 0x40, 0xfe, 0xa9, - 0x8f, 0xc4, 0x48, 0x55, 0xd8, 0x58, 0xa5, 0x86, 0x93, 0xac, 0xb5, 0x75, 0x78, 0x53, 0xa7, 0x46, - 0x99, 0x69, 0xb2, 0xf9, 0x18, 0x6e, 0x17, 0xcd, 0x56, 0x54, 0x80, 0x3e, 0xcd, 0xc1, 0xda, 0xe5, - 0xf7, 0x5b, 0x2b, 0x79, 0xa1, 0xf5, 0x75, 0x35, 0x1c, 0x3a, 0x2b, 0xb8, 0x26, 0xf0, 0xcc, 0x2e, - 0xb4, 0xe9, 0x00, 0xbb, 0x82, 0x3c, 0x77, 0xc3, 0x38, 0xd0, 0xc5, 0xd3, 0x74, 0x5a, 0x74, 0x80, - 0x4f, 0xc9, 0xf3, 0xcf, 0xe3, 0xc0, 0xbc, 0x0f, 0x6f, 0xe7, 0x43, 0xd5, 0x4d, 0x90, 0xef, 0x2a, - 0x7b, 0x75, 0x1d, 0x5c, 0xd7, 0xd2, 0xa2, 0xb3, 0x96, 0xef, 0x9e, 0x23, 0x5f, 0x39, 0xdb, 0xf7, - 0x3c, 0x6e, 0xfd, 0x73, 0x0e, 0xe6, 0x4f, 0x10, 0x47, 0x81, 0x30, 0xcf, 0x60, 0x45, 0x92, 0x20, - 0xf2, 0x91, 0x24, 0x6e, 0x4a, 0xe4, 0xd9, 0x49, 0xdf, 0xd7, 0x04, 0x5f, 0x1d, 0x80, 0x76, 0x65, - 0xe4, 0x25, 0xbb, 0x76, 0x5f, 0x4b, 0x4f, 0x25, 0x92, 0xc4, 0x59, 0xce, 0x31, 0x52, 0xa1, 0xf9, - 0x21, 0x74, 0x24, 0x8f, 0x85, 0x2c, 0x29, 0xb6, 0xe4, 0x96, 0x34, 0x97, 0x6f, 0xe7, 0xfb, 0x29, - 0x2b, 0x15, 0x9c, 0x72, 0x3d, 0x9b, 0x36, 0xde, 0x84, 0x4d, 0x4f, 0x61, 0x4d, 0x8d, 0xa2, 0x49, - 0xcc, 0xe6, 0xf4, 0x98, 0xb7, 0x95, 0x7d, 0x1d, 0xf4, 0x0b, 0x30, 0x13, 0x81, 0x27, 0x31, 0xe7, - 0x5e, 0x23, 0xce, 0x44, 0xe0, 0x3a, 0xa4, 0x07, 0x9b, 0x42, 0x15, 0x9f, 0x1b, 0x10, 0xa9, 0xb9, - 0x39, 0xf2, 0x49, 0x48, 0xc5, 0x28, 0x07, 0x9f, 0x9f, 0x1e, 0x7c, 0x43, 0x03, 0x7d, 0xa6, 0x70, - 0x9c, 0x1c, 0x26, 0xf3, 0xd2, 0x87, 0xee, 0xf5, 0x5e, 0x8a, 0x04, 0xdd, 0xd2, 0x09, 0xfa, 0xc9, - 0x35, 0x10, 0x45, 0x96, 0x04, 0xbc, 0x57, 0x99, 0x21, 0xaa, 0xab, 0x5d, 0xdd, 0x50, 0x2e, 0x27, - 0x43, 0x45, 0xb4, 0x28, 0x1d, 0x27, 0x84, 0x14, 0x73, 0x30, 0x63, 0x0e, 0xf5, 0xac, 0x29, 0x58, - 0xa3, 0xcf, 0x68, 0x98, 0x3d, 0x16, 0xac, 0x72, 0xd4, 0x14, 0x1c, 0xe1, 0x54, 0xb0, 0x3e, 0x21, - 0xe4, 0x71, 0x73, 0x61, 0x61, 0xb5, 0x65, 0xfd, 0x02, 0x5a, 0xba, 0x45, 0xf7, 0xf1, 0x85, 0x30, - 0x37, 0xa1, 0xa5, 0x6a, 0x9d, 0x08, 0x41, 0x44, 0xc7, 0xd0, 0x9d, 0x5d, 0x0a, 0x2c, 0x09, 0x1b, - 0x37, 0x3d, 0x95, 0x84, 0xf9, 0x14, 0x6e, 0x45, 0x44, 0xcf, 0x71, 0x6d, 0xd8, 0xde, 0xfb, 0xc8, - 0x9e, 0xe2, 0xd5, 0x6a, 0xdf, 0x04, 0xe8, 0xe4, 0x68, 0x16, 0x2f, 0x1f, 0x68, 0x13, 0xe3, 0x43, - 0x98, 0xe7, 0x93, 0x4e, 0x7f, 0xfd, 0x5a, 0x4e, 0x27, 0xf0, 0x4a, 0x9f, 0xef, 0x43, 0x7b, 0x3f, - 0x3d, 0xf6, 0x6f, 0xa8, 0x90, 0x57, 0xaf, 0x65, 0xb1, 0x7a, 0x2d, 0x8f, 0x61, 0x39, 0x9b, 0x7a, - 0x67, 0x4c, 0xd3, 0x8c, 0xf9, 0x53, 0x80, 0x6c, 0x5c, 0x2a, 0x7a, 0x4a, 0x89, 0xb8, 0x95, 0x49, - 0x8e, 0xbc, 0xda, 0xf4, 0x9a, 0xad, 0x4d, 0x2f, 0xcb, 0x81, 0x95, 0x73, 0x81, 0x7f, 0x9b, 0x3f, - 0x89, 0x9e, 0x44, 0xc2, 0x7c, 0x0b, 0xe6, 0x55, 0x67, 0x64, 0x40, 0x4d, 0x67, 0x2e, 0x11, 0xf8, - 0x48, 0x73, 0x71, 0xf9, 0xec, 0x62, 0x91, 0x4b, 0x3d, 0xd1, 0x99, 0xdd, 0x6e, 0xec, 0x34, 0x9d, - 0xe5, 0xb8, 0x34, 0x3f, 0xf2, 0x84, 0xf5, 0x3b, 0x68, 0x57, 0x00, 0xcd, 0x65, 0x98, 0x2d, 0xb0, - 0x66, 0xa9, 0x67, 0x3e, 0x80, 0x8d, 0x12, 0xa8, 0x4e, 0xae, 0x29, 0x62, 0xcb, 0xb9, 0x53, 0x28, - 0xd4, 0xf8, 0x55, 0x58, 0x4f, 0x60, 0xfd, 0xa8, 0x6c, 0xe5, 0x82, 0xba, 0x6b, 0x27, 0x34, 0xea, - 0xf3, 0x79, 0x13, 0x5a, 0xc5, 0x6f, 0x0b, 0x7d, 0xfa, 0xa6, 0x53, 0x0a, 0xac, 0x00, 0x56, 0xcf, - 0x05, 0x3e, 0x25, 0xa1, 0x57, 0x82, 0xdd, 0x70, 0x01, 0x07, 0x93, 0x40, 0x53, 0xbf, 0x5d, 0x4b, - 0x77, 0x0c, 0x36, 0xce, 0x91, 0x4f, 0x3d, 0x24, 0x19, 0x3f, 0x25, 0x32, 0x1d, 0xab, 0x27, 0x08, - 0x5f, 0x10, 0x29, 0x4c, 0x07, 0x9a, 0x3e, 0x15, 0x32, 0xab, 0xac, 0x0f, 0x6f, 0xac, 0xac, 0x64, - 0xd7, 0xbe, 0x09, 0xe4, 0x10, 0x49, 0x94, 0x75, 0xa4, 0xc6, 0xb2, 0x7e, 0x0e, 0x6b, 0x9f, 0x21, - 0x19, 0x73, 0xe2, 0xd5, 0x72, 0xbc, 0x0a, 0x0d, 0x95, 0x3f, 0x43, 0xe7, 0x4f, 0x2d, 0xd5, 0x94, - 0xef, 0x3c, 0xfc, 0x32, 0x62, 0x5c, 0x12, 0xef, 0xca, 0x8d, 0xbc, 0xe2, 0x7a, 0x2f, 0x60, 0x4d, - 0x5d, 0x96, 0x20, 0xa1, 0xe7, 0x16, 0xe7, 0x4c, 0xf3, 0xd8, 0xde, 0xfb, 0xd5, 0x54, 0xdd, 0x31, - 0xe9, 0x2e, 0x3b, 0xc0, 0xed, 0x64, 0x42, 0x2e, 0xac, 0x3f, 0x1b, 0xd0, 0x39, 0x26, 0xe3, 0x7d, - 0x21, 0xe8, 0x30, 0x0c, 0x48, 0x28, 0x15, 0xb3, 0x21, 0x4c, 0xd4, 0xd2, 0x7c, 0x07, 0x96, 0x8a, - 0x49, 0xaa, 0x07, 0xa8, 0xa1, 0x07, 0xe8, 0x62, 0x2e, 0x54, 0x0d, 0x66, 0x3e, 0x00, 0x88, 0x38, - 0x49, 0x5c, 0xec, 0x5e, 0x90, 0x71, 0x96, 0xc5, 0xcd, 0xea, 0x60, 0x4c, 0x7f, 0xf9, 0xd9, 0x27, - 0xf1, 0xc0, 0xa7, 0xf8, 0x98, 0x8c, 0x9d, 0x05, 0xa5, 0xdf, 0x3f, 0x26, 0x63, 0xf5, 0xd2, 0x89, - 0xd8, 0x0b, 0xc2, 0xf5, 0x34, 0x6b, 0x38, 0xe9, 0x87, 0xf5, 0x17, 0x03, 0xee, 0x14, 0xe9, 0xc8, - 0xcb, 0xf5, 0x24, 0x1e, 0x28, 0x8b, 0x57, 0xdc, 0xdb, 0x95, 0x68, 0x67, 0xaf, 0x89, 0xf6, 0x63, - 0x58, 0x2c, 0x1a, 0x44, 0xc5, 0xdb, 0x98, 0x22, 0xde, 0x76, 0x6e, 0x71, 0x4c, 0xc6, 0xd6, 0x1f, - 0x2b, 0xb1, 0x1d, 0x8c, 0x2b, 0xdc, 0xc7, 0xff, 0x47, 0x6c, 0x85, 0xdb, 0x6a, 0x6c, 0xb8, 0x6a, - 0x7f, 0xe5, 0x00, 0x8d, 0xab, 0x07, 0xb0, 0xfe, 0x66, 0xc0, 0x7a, 0xd5, 0xab, 0x38, 0x63, 0x27, - 0x3c, 0x0e, 0xc9, 0xab, 0xbc, 0x97, 0xed, 0x37, 0x5b, 0x6d, 0xbf, 0xa7, 0xb0, 0x5c, 0x0b, 0x4a, - 0x64, 0xb7, 0xf1, 0xcb, 0xa9, 0x6a, 0xac, 0xc2, 0xae, 0xce, 0x52, 0xf5, 0x1c, 0xe2, 0xe0, 0xe9, - 0xb7, 0x97, 0x5d, 0xe3, 0xbb, 0xcb, 0xae, 0xf1, 0xef, 0xcb, 0xae, 0xf1, 0xd5, 0xcb, 0xee, 0xcc, - 0x77, 0x2f, 0xbb, 0x33, 0xff, 0x7a, 0xd9, 0x9d, 0xf9, 0xfd, 0x47, 0x43, 0x2a, 0x47, 0xf1, 0xc0, - 0xc6, 0x2c, 0xe8, 0x65, 0x3f, 0xeb, 0x4b, 0x5f, 0x1f, 0x14, 0xff, 0x79, 0x24, 0xf7, 0x7b, 0x5f, - 0xd6, 0xff, 0x51, 0x91, 0xe3, 0x88, 0x88, 0xc1, 0xbc, 0x66, 0x85, 0xfb, 0xff, 0x0d, 0x00, 0x00, - 0xff, 0xff, 0x95, 0xc6, 0x06, 0x3d, 0x82, 0x11, 0x00, 0x00, + 0xf1, 0x10, 0xd1, 0xd0, 0x13, 0x04, 0xc7, 0x9c, 0xca, 0x71, 0x17, 0xe3, 0xa4, 0x1b, 0x71, 0x96, + 0x50, 0x9f, 0xf0, 0x6e, 0xb2, 0x5b, 0xac, 0x9d, 0x88, 0x33, 0xc9, 0xcc, 0x77, 0xae, 0xb1, 0x71, + 0x30, 0x4e, 0x9c, 0x42, 0x2f, 0xd9, 0xbd, 0xfb, 0xee, 0x4d, 0xc0, 0xc9, 0x6e, 0xf7, 0x05, 0xe5, + 0x24, 0xc5, 0xba, 0xbb, 0x3e, 0x60, 0x03, 0xa6, 0x97, 0x5d, 0xb5, 0xca, 0xa4, 0x5b, 0x03, 0xc6, + 0x06, 0x01, 0xe9, 0xea, 0xaf, 0x7e, 0xfc, 0xac, 0x2b, 0xe9, 0x88, 0x08, 0x89, 0x46, 0x51, 0xa6, + 0xd0, 0x99, 0x54, 0xf0, 0x63, 0x8e, 0x24, 0x65, 0x61, 0x0e, 0x40, 0xfb, 0xb8, 0x8b, 0x19, 0x27, + 0x5d, 0x1c, 0x50, 0x12, 0x4a, 0xe5, 0x35, 0x5d, 0x65, 0x0a, 0x5d, 0xa5, 0x10, 0xd0, 0xc1, 0x50, + 0xa6, 0x62, 0xd1, 0x95, 0x24, 0xf4, 0x09, 0x1f, 0xd1, 0x54, 0xb9, 0xfc, 0xca, 0x0c, 0x36, 0x2b, + 0xfb, 0x98, 0x8f, 0x23, 0xc9, 0xba, 0x17, 0x64, 0x2c, 0xb2, 0xdd, 0xf7, 0x30, 0x13, 0x23, 0x26, + 0xba, 0x44, 0x9d, 0x3f, 0xc4, 0xa4, 0x9b, 0xec, 0xf6, 0x89, 0x44, 0xbb, 0x85, 0x20, 0x8f, 0x3b, + 0xd3, 0xeb, 0x23, 0x51, 0xea, 0x60, 0x46, 0xb3, 0xb8, 0xed, 0x1f, 0xe6, 0xc1, 0xea, 0xb1, 0x50, + 0xc4, 0x23, 0xc2, 0xf7, 0x7d, 0x9f, 0xaa, 0x23, 0x9d, 0x70, 0x16, 0x31, 0x81, 0x02, 0x73, 0x1d, + 0xe6, 0x24, 0x95, 0x01, 0xb1, 0x8c, 0x6d, 0x63, 0xa7, 0xe5, 0xa6, 0x1f, 0xe6, 0x36, 0xb4, 0x7d, + 0x22, 0x30, 0xa7, 0x91, 0x52, 0xb6, 0x66, 0xf5, 0x5e, 0x55, 0x64, 0x6e, 0xc0, 0x42, 0x9a, 0x07, + 0xea, 0x5b, 0x0d, 0xbd, 0x7d, 0x4b, 0x7f, 0x1f, 0xf9, 0xe6, 0xa7, 0xb0, 0x4c, 0x43, 0x2a, 0x29, + 0x0a, 0xbc, 0x21, 0x51, 0xb7, 0x61, 0x35, 0xb7, 0x8d, 0x9d, 0xf6, 0xde, 0x5d, 0x87, 0xf6, 0xb1, + 0xa3, 0x2e, 0xd0, 0xc9, 0xae, 0x2d, 0xd9, 0x75, 0x1e, 0x69, 0x8d, 0x83, 0xe6, 0xb7, 0xdf, 0x6f, + 0xcd, 0xb8, 0x4b, 0x99, 0x5d, 0x2a, 0x34, 0xef, 0xc1, 0xe2, 0x80, 0x84, 0x44, 0x50, 0xe1, 0x0d, + 0x91, 0x18, 0x5a, 0x73, 0xdb, 0xc6, 0xce, 0xa2, 0xdb, 0xce, 0x64, 0x8f, 0x90, 0x18, 0x9a, 0x5b, + 0xd0, 0xee, 0xd3, 0x10, 0xf1, 0x71, 0xaa, 0x31, 0xaf, 0x35, 0x20, 0x15, 0x69, 0x85, 0x1e, 0x80, + 0x88, 0xd0, 0x8b, 0xd0, 0x53, 0xd9, 0xb6, 0x6e, 0x65, 0x81, 0xa4, 0x99, 0x76, 0xf2, 0x4c, 0x3b, + 0x67, 0x79, 0x29, 0x1c, 0x2c, 0xa8, 0x40, 0xbe, 0xfa, 0x61, 0xcb, 0x70, 0x5b, 0xda, 0x4e, 0xed, + 0x98, 0x9f, 0xc3, 0x6a, 0x1c, 0xf6, 0x59, 0xe8, 0xd3, 0x70, 0xe0, 0x45, 0x84, 0x53, 0xe6, 0x5b, + 0x0b, 0x1a, 0x6a, 0xe3, 0x0a, 0xd4, 0x61, 0x56, 0x34, 0x29, 0xd2, 0xd7, 0x0a, 0x69, 0xa5, 0x30, + 0x3e, 0xd1, 0xb6, 0xe6, 0x17, 0x60, 0x62, 0x9c, 0xe8, 0x90, 0x58, 0x2c, 0x73, 0xc4, 0xd6, 0xf4, + 0x88, 0xab, 0x18, 0x27, 0x67, 0xa9, 0x75, 0x06, 0xf9, 0x07, 0xb8, 0x23, 0x39, 0x0a, 0xc5, 0x33, + 0xc2, 0x27, 0x71, 0x61, 0x7a, 0xdc, 0xb7, 0x72, 0x8c, 0x3a, 0xf8, 0x23, 0xd8, 0xc6, 0x59, 0x01, + 0x79, 0x9c, 0xf8, 0x54, 0x48, 0x4e, 0xfb, 0xb1, 0xb2, 0xf5, 0x9e, 0x71, 0x84, 0x75, 0x8d, 0xb4, + 0x75, 0x11, 0x74, 0x72, 0x3d, 0xb7, 0xa6, 0xf6, 0x49, 0xa6, 0x65, 0x3e, 0x81, 0x9f, 0xf5, 0x03, + 0x86, 0x2f, 0x84, 0x0a, 0xce, 0xab, 0x21, 0x69, 0xd7, 0x23, 0x2a, 0x84, 0x42, 0x5b, 0xdc, 0x36, + 0x76, 0x1a, 0xee, 0xbd, 0x54, 0xf7, 0x84, 0xf0, 0xc3, 0x8a, 0xe6, 0x59, 0x45, 0xd1, 0xfc, 0x00, + 0xcc, 0x21, 0x15, 0x92, 0x71, 0x8a, 0x51, 0xe0, 0x91, 0x50, 0x72, 0x4a, 0x84, 0xb5, 0xa4, 0xcd, + 0x6f, 0x97, 0x3b, 0x0f, 0xd3, 0x0d, 0xf3, 0x31, 0xdc, 0xbb, 0xd1, 0xa9, 0x87, 0x87, 0x28, 0x0c, + 0x49, 0x60, 0x2d, 0xeb, 0xa3, 0x6c, 0xf9, 0x37, 0xf8, 0xec, 0xa5, 0x6a, 0x0f, 0x16, 0xfe, 0xf4, + 0xcd, 0xd6, 0xcc, 0xd7, 0xdf, 0x6c, 0xcd, 0xd8, 0xff, 0x30, 0xe0, 0x4e, 0xaf, 0x38, 0xf8, 0x88, + 0x25, 0x28, 0xf8, 0x31, 0x1b, 0x6c, 0x1f, 0x5a, 0x42, 0xb2, 0x28, 0x2d, 0xe9, 0xe6, 0x6b, 0x94, + 0xf4, 0x82, 0x32, 0x53, 0x1b, 0xf6, 0x5f, 0x0d, 0x58, 0x7f, 0xf8, 0x3c, 0xa6, 0x09, 0xc3, 0xe8, + 0xff, 0xc2, 0x07, 0xc7, 0xb0, 0x44, 0x2a, 0x78, 0xc2, 0x6a, 0x6c, 0x37, 0x76, 0xda, 0x7b, 0xef, + 0x3a, 0x29, 0x39, 0x39, 0x05, 0x67, 0x65, 0x04, 0xe5, 0x54, 0xbd, 0xbb, 0x75, 0xdb, 0x07, 0xb3, + 0x96, 0x61, 0xff, 0xdd, 0x80, 0xbb, 0xea, 0xa6, 0x07, 0xc4, 0x25, 0x2f, 0x10, 0xf7, 0x0f, 0x49, + 0xc8, 0x46, 0xe2, 0x8d, 0xe3, 0xb4, 0x61, 0xc9, 0xd7, 0x48, 0x9e, 0x64, 0x1e, 0xf2, 0x7d, 0x1d, + 0xa7, 0xd6, 0x51, 0xc2, 0x33, 0xb6, 0xef, 0xfb, 0xe6, 0x0e, 0xac, 0x96, 0x3a, 0x5c, 0xe5, 0x53, + 0x5d, 0xb3, 0x52, 0x5b, 0xce, 0xd5, 0x74, 0x96, 0x89, 0xfd, 0x1f, 0x03, 0x56, 0x3f, 0x0d, 0x58, + 0x1f, 0x05, 0xa7, 0x01, 0x12, 0x43, 0x55, 0x65, 0x63, 0x95, 0x1e, 0x4e, 0xb2, 0xf6, 0xd6, 0xe1, + 0x4d, 0x9d, 0x1e, 0x65, 0xa6, 0x09, 0xe7, 0x63, 0xb8, 0x5d, 0x34, 0x5c, 0x51, 0x05, 0xfa, 0x34, + 0x07, 0x6b, 0x97, 0xdf, 0x6f, 0xad, 0xe4, 0xc5, 0xd6, 0xd3, 0x15, 0x71, 0xe8, 0xae, 0xe0, 0x9a, + 0xc0, 0x37, 0x3b, 0xd0, 0xa6, 0x7d, 0xec, 0x09, 0xf2, 0xdc, 0x0b, 0xe3, 0x91, 0x2e, 0xa0, 0xa6, + 0xdb, 0xa2, 0x7d, 0x7c, 0x4a, 0x9e, 0x7f, 0x1e, 0x8f, 0xcc, 0xfb, 0xf0, 0x76, 0x3e, 0x58, 0xbd, + 0x04, 0x05, 0x9e, 0xb2, 0x57, 0xd7, 0xc1, 0x75, 0x3d, 0x2d, 0xba, 0x6b, 0xf9, 0xee, 0x39, 0x0a, + 0x94, 0xb3, 0x7d, 0xdf, 0xe7, 0xf6, 0x3f, 0xe7, 0x60, 0xfe, 0x04, 0x71, 0x34, 0x12, 0xe6, 0x19, + 0xac, 0x48, 0x32, 0x8a, 0x02, 0x24, 0x89, 0x97, 0x92, 0x79, 0x76, 0xd2, 0xf7, 0x35, 0xc9, 0x57, + 0x87, 0xa0, 0x53, 0x19, 0x7b, 0xc9, 0xae, 0xd3, 0xd3, 0xd2, 0x53, 0x89, 0x24, 0x71, 0x97, 0x73, + 0x8c, 0x54, 0x68, 0x7e, 0x08, 0x96, 0xe4, 0xb1, 0x90, 0x25, 0xcd, 0x96, 0xfc, 0x92, 0xe6, 0xf2, + 0xed, 0x7c, 0x3f, 0x65, 0xa6, 0x82, 0x57, 0xae, 0x67, 0xd4, 0xc6, 0x9b, 0x30, 0xea, 0x29, 0xac, + 0xa9, 0x71, 0x34, 0x89, 0xd9, 0x9c, 0x1e, 0xf3, 0xb6, 0xb2, 0xaf, 0x83, 0x7e, 0x01, 0x66, 0x22, + 0xf0, 0x24, 0xe6, 0xdc, 0x6b, 0xc4, 0x99, 0x08, 0x5c, 0x87, 0xf4, 0x61, 0x53, 0xa8, 0xe2, 0xf3, + 0x46, 0x44, 0x6a, 0x7e, 0x8e, 0x02, 0x12, 0x52, 0x31, 0xcc, 0xc1, 0xe7, 0xa7, 0x07, 0xdf, 0xd0, + 0x40, 0x9f, 0x29, 0x1c, 0x37, 0x87, 0xc9, 0xbc, 0xf4, 0xa0, 0x73, 0xbd, 0x97, 0x22, 0x41, 0xb7, + 0x74, 0x82, 0x7e, 0x72, 0x0d, 0x44, 0x91, 0x25, 0x01, 0xef, 0x55, 0xe6, 0x88, 0xea, 0x6a, 0x4f, + 0x37, 0x94, 0xc7, 0xc9, 0x40, 0x91, 0x2d, 0x4a, 0x47, 0x0a, 0x21, 0xc5, 0x2c, 0xcc, 0xd8, 0x43, + 0x3d, 0x6d, 0x0a, 0xe6, 0xe8, 0x31, 0x1a, 0x66, 0x0f, 0x06, 0xbb, 0x1c, 0x37, 0x05, 0x47, 0xb8, + 0x15, 0xac, 0x4f, 0x08, 0x79, 0xdc, 0x5c, 0x58, 0x58, 0x6d, 0xd9, 0xbf, 0x80, 0x96, 0x6e, 0xd1, + 0x7d, 0x7c, 0x21, 0xcc, 0x4d, 0x68, 0xa9, 0x5a, 0x27, 0x42, 0x10, 0x61, 0x19, 0xba, 0xb3, 0x4b, + 0x81, 0x2d, 0x61, 0xe3, 0xa6, 0xe7, 0x92, 0x30, 0x9f, 0xc2, 0xad, 0x88, 0xe8, 0x59, 0xae, 0x0d, + 0xdb, 0x7b, 0x1f, 0x39, 0x53, 0xbc, 0x5c, 0x9d, 0x9b, 0x00, 0xdd, 0x1c, 0xcd, 0xe6, 0xe5, 0x23, + 0x6d, 0x62, 0x84, 0x08, 0xf3, 0x7c, 0xd2, 0xe9, 0xaf, 0x5f, 0xcb, 0xe9, 0x04, 0x5e, 0xe9, 0xf3, + 0x7d, 0x68, 0xef, 0xa7, 0xc7, 0xfe, 0x0d, 0x15, 0xf2, 0xea, 0xb5, 0x2c, 0x56, 0xaf, 0xe5, 0x31, + 0x2c, 0x67, 0x93, 0xef, 0x8c, 0x69, 0x9a, 0x31, 0x7f, 0x0a, 0x90, 0x8d, 0x4c, 0x45, 0x4f, 0x29, + 0x11, 0xb7, 0x32, 0xc9, 0x91, 0x5f, 0x9b, 0x60, 0xb3, 0xb5, 0x09, 0x66, 0xbb, 0xb0, 0x72, 0x2e, + 0xf0, 0x6f, 0xf3, 0x67, 0xd1, 0x93, 0x48, 0x98, 0x6f, 0xc1, 0xbc, 0xea, 0x8c, 0x0c, 0xa8, 0xe9, + 0xce, 0x25, 0x02, 0x1f, 0x69, 0x2e, 0x2e, 0x9f, 0x5e, 0x2c, 0xf2, 0xa8, 0x2f, 0xac, 0xd9, 0xed, + 0xc6, 0x4e, 0xd3, 0x5d, 0x8e, 0x4b, 0xf3, 0x23, 0x5f, 0xd8, 0xbf, 0x83, 0x76, 0x05, 0xd0, 0x5c, + 0x86, 0xd9, 0x02, 0x6b, 0x96, 0xfa, 0xe6, 0x03, 0xd8, 0x28, 0x81, 0xea, 0xe4, 0x9a, 0x22, 0xb6, + 0xdc, 0x3b, 0x85, 0x42, 0x8d, 0x5f, 0x85, 0xfd, 0x04, 0xd6, 0x8f, 0xca, 0x56, 0x2e, 0xa8, 0xbb, + 0x76, 0x42, 0xa3, 0x3e, 0xa3, 0x37, 0xa1, 0x55, 0xfc, 0xbe, 0xd0, 0xa7, 0x6f, 0xba, 0xa5, 0xc0, + 0x1e, 0xc1, 0xea, 0xb9, 0xc0, 0xa7, 0x24, 0xf4, 0x4b, 0xb0, 0x1b, 0x2e, 0xe0, 0x60, 0x12, 0x68, + 0xea, 0xf7, 0x6b, 0xe9, 0x8e, 0xc1, 0xc6, 0x39, 0x0a, 0xa8, 0x8f, 0x24, 0xe3, 0xa7, 0x44, 0xa6, + 0x63, 0xf5, 0x04, 0xe1, 0x0b, 0x22, 0x85, 0xe9, 0x42, 0x33, 0xa0, 0x42, 0x66, 0x95, 0xf5, 0xe1, + 0x8d, 0x95, 0x95, 0xec, 0x3a, 0x37, 0x81, 0x1c, 0x22, 0x89, 0xb2, 0x8e, 0xd4, 0x58, 0xf6, 0xcf, + 0x61, 0xed, 0x33, 0x24, 0x63, 0x4e, 0xfc, 0x5a, 0x8e, 0x57, 0xa1, 0xa1, 0xf2, 0x67, 0xe8, 0xfc, + 0xa9, 0xa5, 0x9a, 0xf2, 0xd6, 0xc3, 0x2f, 0x23, 0xc6, 0x25, 0xf1, 0xaf, 0xdc, 0xc8, 0x2b, 0xae, + 0xf7, 0x02, 0xd6, 0xd4, 0x65, 0x09, 0x12, 0xfa, 0x5e, 0x71, 0xce, 0x34, 0x8f, 0xed, 0xbd, 0x5f, + 0x4d, 0xd5, 0x1d, 0x93, 0xee, 0xb2, 0x03, 0xdc, 0x4e, 0x26, 0xe4, 0xc2, 0xfe, 0xb3, 0x01, 0xd6, + 0x31, 0x19, 0xef, 0x0b, 0x41, 0x07, 0xe1, 0x88, 0x84, 0x52, 0x31, 0x1b, 0xc2, 0x44, 0x2d, 0xcd, + 0x77, 0x60, 0xa9, 0x98, 0xa4, 0x7a, 0x80, 0x1a, 0x7a, 0x80, 0x2e, 0xe6, 0x42, 0xd5, 0x60, 0xe6, + 0x03, 0x80, 0x88, 0x93, 0xc4, 0xc3, 0xde, 0x05, 0x19, 0x67, 0x59, 0xdc, 0xac, 0x0e, 0xc6, 0xf4, + 0xd7, 0x9f, 0x73, 0x12, 0xf7, 0x03, 0x8a, 0x8f, 0xc9, 0xd8, 0x5d, 0x50, 0xfa, 0xbd, 0x63, 0x32, + 0x56, 0x2f, 0x9d, 0x88, 0xbd, 0x20, 0x5c, 0x4f, 0xb3, 0x86, 0x9b, 0x7e, 0xd8, 0x7f, 0x31, 0xe0, + 0x4e, 0x91, 0x8e, 0xbc, 0x5c, 0x4f, 0xe2, 0xbe, 0xb2, 0x78, 0xc5, 0xbd, 0x5d, 0x89, 0x76, 0xf6, + 0x9a, 0x68, 0x3f, 0x86, 0xc5, 0xa2, 0x41, 0x54, 0xbc, 0x8d, 0x29, 0xe2, 0x6d, 0xe7, 0x16, 0xc7, + 0x64, 0x6c, 0xff, 0xb1, 0x12, 0xdb, 0xc1, 0xb8, 0xc2, 0x7d, 0xfc, 0x7f, 0xc4, 0x56, 0xb8, 0xad, + 0xc6, 0x86, 0xab, 0xf6, 0x57, 0x0e, 0xd0, 0xb8, 0x7a, 0x00, 0xfb, 0x6f, 0x06, 0xac, 0x57, 0xbd, + 0x8a, 0x33, 0x76, 0xc2, 0xe3, 0x90, 0xbc, 0xca, 0x7b, 0xd9, 0x7e, 0xb3, 0xd5, 0xf6, 0x7b, 0x0a, + 0xcb, 0xb5, 0xa0, 0x44, 0x76, 0x1b, 0xbf, 0x9c, 0xaa, 0xc6, 0x2a, 0xec, 0xea, 0x2e, 0x55, 0xcf, + 0x21, 0x0e, 0x9e, 0x7e, 0x7b, 0xd9, 0x31, 0xbe, 0xbb, 0xec, 0x18, 0xff, 0xbe, 0xec, 0x18, 0x5f, + 0xbd, 0xec, 0xcc, 0x7c, 0xf7, 0xb2, 0x33, 0xf3, 0xaf, 0x97, 0x9d, 0x99, 0xdf, 0x7f, 0x34, 0xa0, + 0x72, 0x18, 0xf7, 0x1d, 0xcc, 0x46, 0xdd, 0xec, 0xa7, 0x7d, 0xe9, 0xeb, 0x83, 0xe2, 0x7f, 0x8f, + 0xe4, 0x7e, 0xf7, 0xcb, 0xfa, 0xbf, 0x2a, 0x72, 0x1c, 0x11, 0xd1, 0x9f, 0xd7, 0xac, 0x70, 0xff, + 0xbf, 0x01, 0x00, 0x00, 0xff, 0xff, 0xa7, 0xa1, 0xa2, 0x88, 0x86, 0x11, 0x00, 0x00, } func (m *ConsumerAdditionProposal) Marshal() (dAtA []byte, err error) { diff --git a/x/ccv/provider/types/tx.pb.go b/x/ccv/provider/types/tx.pb.go index 80487a06c9..b89c515dd3 100644 --- a/x/ccv/provider/types/tx.pb.go +++ b/x/ccv/provider/types/tx.pb.go @@ -6,9 +6,13 @@ package types import ( context "context" fmt "fmt" + types "github.com/cometbft/cometbft/proto/tendermint/types" + _ "github.com/cosmos/cosmos-proto" + _ "github.com/cosmos/cosmos-sdk/codec/types" _ "github.com/cosmos/gogoproto/gogoproto" grpc1 "github.com/cosmos/gogoproto/grpc" proto "github.com/cosmos/gogoproto/proto" + _07_tendermint "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint" grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" @@ -108,9 +112,171 @@ func (m *MsgAssignConsumerKeyResponse) XXX_DiscardUnknown() { var xxx_messageInfo_MsgAssignConsumerKeyResponse proto.InternalMessageInfo +// MsgSubmitConsumerMisbehaviour defines a message that reports a light client attack, +// also known as 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 + // two conflicting IBC headers + Misbehaviour *_07_tendermint.Misbehaviour `protobuf:"bytes,2,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{2} +} +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 + +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{3} +} +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 + +// MsgSubmitConsumerDoubleVoting defines a message that reports +// a double signing infraction observed on a consumer chain +type MsgSubmitConsumerDoubleVoting struct { + Submitter string `protobuf:"bytes,1,opt,name=submitter,proto3" json:"submitter,omitempty"` + // The equivocation of the consumer chain wrapping + // an evidence of a validator that signed two conflicting votes + DuplicateVoteEvidence *types.DuplicateVoteEvidence `protobuf:"bytes,2,opt,name=duplicate_vote_evidence,json=duplicateVoteEvidence,proto3" json:"duplicate_vote_evidence,omitempty"` + // The light client header of the infraction block + InfractionBlockHeader *_07_tendermint.Header `protobuf:"bytes,3,opt,name=infraction_block_header,json=infractionBlockHeader,proto3" json:"infraction_block_header,omitempty"` +} + +func (m *MsgSubmitConsumerDoubleVoting) Reset() { *m = MsgSubmitConsumerDoubleVoting{} } +func (m *MsgSubmitConsumerDoubleVoting) String() string { return proto.CompactTextString(m) } +func (*MsgSubmitConsumerDoubleVoting) ProtoMessage() {} +func (*MsgSubmitConsumerDoubleVoting) Descriptor() ([]byte, []int) { + return fileDescriptor_43221a4391e9fbf4, []int{4} +} +func (m *MsgSubmitConsumerDoubleVoting) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgSubmitConsumerDoubleVoting) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgSubmitConsumerDoubleVoting.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 *MsgSubmitConsumerDoubleVoting) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgSubmitConsumerDoubleVoting.Merge(m, src) +} +func (m *MsgSubmitConsumerDoubleVoting) XXX_Size() int { + return m.Size() +} +func (m *MsgSubmitConsumerDoubleVoting) XXX_DiscardUnknown() { + xxx_messageInfo_MsgSubmitConsumerDoubleVoting.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgSubmitConsumerDoubleVoting proto.InternalMessageInfo + +type MsgSubmitConsumerDoubleVotingResponse struct { +} + +func (m *MsgSubmitConsumerDoubleVotingResponse) Reset() { *m = MsgSubmitConsumerDoubleVotingResponse{} } +func (m *MsgSubmitConsumerDoubleVotingResponse) String() string { return proto.CompactTextString(m) } +func (*MsgSubmitConsumerDoubleVotingResponse) ProtoMessage() {} +func (*MsgSubmitConsumerDoubleVotingResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_43221a4391e9fbf4, []int{5} +} +func (m *MsgSubmitConsumerDoubleVotingResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgSubmitConsumerDoubleVotingResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgSubmitConsumerDoubleVotingResponse.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 *MsgSubmitConsumerDoubleVotingResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgSubmitConsumerDoubleVotingResponse.Merge(m, src) +} +func (m *MsgSubmitConsumerDoubleVotingResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgSubmitConsumerDoubleVotingResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgSubmitConsumerDoubleVotingResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgSubmitConsumerDoubleVotingResponse 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((*MsgSubmitConsumerMisbehaviour)(nil), "interchain_security.ccv.provider.v1.MsgSubmitConsumerMisbehaviour") + proto.RegisterType((*MsgSubmitConsumerMisbehaviourResponse)(nil), "interchain_security.ccv.provider.v1.MsgSubmitConsumerMisbehaviourResponse") + proto.RegisterType((*MsgSubmitConsumerDoubleVoting)(nil), "interchain_security.ccv.provider.v1.MsgSubmitConsumerDoubleVoting") + proto.RegisterType((*MsgSubmitConsumerDoubleVotingResponse)(nil), "interchain_security.ccv.provider.v1.MsgSubmitConsumerDoubleVotingResponse") } func init() { @@ -118,28 +284,46 @@ func init() { } var fileDescriptor_43221a4391e9fbf4 = []byte{ - // 328 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xd2, 0xc9, 0xcc, 0x2b, 0x49, - 0x2d, 0x4a, 0xce, 0x48, 0xcc, 0xcc, 0x8b, 0x2f, 0x4e, 0x4d, 0x2e, 0x2d, 0xca, 0x2c, 0xa9, 0xd4, - 0x4f, 0x4e, 0x2e, 0xd3, 0x2f, 0x28, 0xca, 0x2f, 0xcb, 0x4c, 0x49, 0x2d, 0xd2, 0x2f, 0x33, 0xd4, - 0x2f, 0xa9, 0xd0, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x52, 0xc6, 0xa2, 0x5a, 0x2f, 0x39, 0xb9, - 0x4c, 0x0f, 0xa6, 0x5a, 0xaf, 0xcc, 0x50, 0x4a, 0x24, 0x3d, 0x3f, 0x3d, 0x1f, 0xac, 0x5e, 0x1f, - 0xc4, 0x82, 0x68, 0x55, 0x9a, 0xce, 0xc8, 0x25, 0xe2, 0x5b, 0x9c, 0xee, 0x58, 0x5c, 0x9c, 0x99, - 0x9e, 0xe7, 0x9c, 0x9f, 0x57, 0x5c, 0x9a, 0x9b, 0x5a, 0xe4, 0x9d, 0x5a, 0x29, 0x24, 0xc9, 0xc5, - 0x01, 0x31, 0x30, 0x33, 0x45, 0x82, 0x51, 0x81, 0x51, 0x83, 0x33, 0x88, 0x1d, 0xcc, 0xf7, 0x4c, - 0x11, 0x32, 0xe7, 0xe2, 0x85, 0x19, 0x1c, 0x9f, 0x98, 0x92, 0x52, 0x24, 0xc1, 0x04, 0x92, 0x77, - 0x12, 0xfa, 0x74, 0x4f, 0x9e, 0xaf, 0x32, 0x31, 0x37, 0xc7, 0x4a, 0x09, 0x24, 0x9a, 0x5a, 0x5c, - 0xac, 0x14, 0xc4, 0x03, 0x53, 0xe8, 0x98, 0x92, 0x52, 0x24, 0xa4, 0xc8, 0xc5, 0x93, 0x0c, 0xb5, - 0x22, 0x3e, 0x3b, 0xb5, 0x52, 0x82, 0x19, 0x6c, 0x2e, 0x77, 0x32, 0xc2, 0x5a, 0x2b, 0x8e, 0x8e, - 0x05, 0xf2, 0x0c, 0x2f, 0x16, 0xc8, 0x33, 0x28, 0xc9, 0x71, 0xc9, 0x60, 0x73, 0x58, 0x50, 0x6a, - 0x71, 0x41, 0x7e, 0x5e, 0x71, 0xaa, 0xd1, 0x4c, 0x46, 0x2e, 0x66, 0xdf, 0xe2, 0x74, 0xa1, 0x89, - 0x8c, 0x5c, 0x82, 0x98, 0xce, 0xb7, 0xd4, 0x23, 0x22, 0x4c, 0xf4, 0xb0, 0x59, 0x20, 0xe5, 0x48, - 0xb6, 0x56, 0x98, 0xdb, 0x9c, 0xc2, 0x4f, 0x3c, 0x92, 0x63, 0xbc, 0xf0, 0x48, 0x8e, 0xf1, 0xc1, - 0x23, 0x39, 0xc6, 0x09, 0x8f, 0xe5, 0x18, 0x2e, 0x3c, 0x96, 0x63, 0xb8, 0xf1, 0x58, 0x8e, 0x21, - 0xca, 0x36, 0x3d, 0xb3, 0x24, 0xa3, 0x34, 0x49, 0x2f, 0x39, 0x3f, 0x57, 0x3f, 0x39, 0xbf, 0x38, - 0x37, 0xbf, 0x58, 0x1f, 0x61, 0x9b, 0x2e, 0x3c, 0xaa, 0xcb, 0x8c, 0xf5, 0x2b, 0x50, 0xe3, 0xbb, - 0xa4, 0xb2, 0x20, 0xb5, 0x38, 0x89, 0x0d, 0x1c, 0x6b, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, - 0xb3, 0xd9, 0x75, 0xa5, 0x20, 0x02, 0x00, 0x00, + // 610 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x54, 0xcf, 0x4f, 0xd4, 0x4e, + 0x14, 0xdf, 0x42, 0xf2, 0xfd, 0xc2, 0x80, 0x26, 0x36, 0x10, 0x60, 0x83, 0x5d, 0x5d, 0xa3, 0x78, + 0xc0, 0x99, 0x00, 0x07, 0x23, 0x89, 0x07, 0x56, 0x4c, 0xfc, 0x91, 0x4d, 0x4c, 0x4d, 0x30, 0xf1, + 0x60, 0xd3, 0x4e, 0x1f, 0xdd, 0x09, 0xed, 0xcc, 0x66, 0x66, 0xda, 0xd0, 0xff, 0x80, 0xa3, 0x9e, + 0x8c, 0x37, 0xfe, 0x00, 0xff, 0x10, 0x8f, 0x1c, 0x3d, 0x19, 0x03, 0x17, 0xcf, 0x5e, 0xbc, 0x9a, + 0x9d, 0xb6, 0x6c, 0x89, 0x15, 0x08, 0xde, 0xfa, 0xde, 0xfb, 0xbc, 0xf7, 0x3e, 0x9f, 0x37, 0xaf, + 0x0f, 0xad, 0x32, 0xae, 0x41, 0xd2, 0x81, 0xcf, 0xb8, 0xa7, 0x80, 0xa6, 0x92, 0xe9, 0x9c, 0x50, + 0x9a, 0x91, 0xa1, 0x14, 0x19, 0x0b, 0x41, 0x92, 0x6c, 0x8d, 0xe8, 0x7d, 0x3c, 0x94, 0x42, 0x0b, + 0xfb, 0x4e, 0x03, 0x1a, 0x53, 0x9a, 0xe1, 0x0a, 0x8d, 0xb3, 0xb5, 0xf6, 0x5c, 0x24, 0x22, 0x61, + 0xf0, 0x64, 0xf4, 0x55, 0xa4, 0xb6, 0x97, 0xa8, 0x50, 0x89, 0x50, 0x5e, 0x11, 0x28, 0x8c, 0x2a, + 0x14, 0x09, 0x11, 0xc5, 0x40, 0x8c, 0x15, 0xa4, 0xbb, 0xc4, 0xe7, 0x79, 0x19, 0x22, 0x2c, 0xa0, + 0x24, 0x66, 0xd1, 0x40, 0xd3, 0x98, 0x01, 0xd7, 0x8a, 0x68, 0xe0, 0x21, 0xc8, 0x84, 0x71, 0x6d, + 0x98, 0x9d, 0x5a, 0x65, 0x42, 0xa7, 0x16, 0xd7, 0xf9, 0x10, 0x14, 0x81, 0x11, 0x31, 0x4e, 0xa1, + 0x00, 0x74, 0x3f, 0x5a, 0x68, 0xae, 0xaf, 0xa2, 0x2d, 0xa5, 0x58, 0xc4, 0x9f, 0x08, 0xae, 0xd2, + 0x04, 0xe4, 0x4b, 0xc8, 0xed, 0x25, 0x34, 0x55, 0x08, 0x63, 0xe1, 0xa2, 0x75, 0xcb, 0xba, 0x3f, + 0xed, 0xfe, 0x6f, 0xec, 0xe7, 0xa1, 0xfd, 0x10, 0x5d, 0xab, 0x04, 0x7a, 0x7e, 0x18, 0xca, 0xc5, + 0x89, 0x51, 0xbc, 0x67, 0xff, 0xfc, 0xd6, 0xb9, 0x9e, 0xfb, 0x49, 0xbc, 0xd9, 0x1d, 0x79, 0x41, + 0xa9, 0xae, 0x3b, 0x5b, 0x01, 0xb7, 0xc2, 0x50, 0xda, 0xb7, 0xd1, 0x2c, 0x2d, 0x5b, 0x78, 0x7b, + 0x90, 0x2f, 0x4e, 0x9a, 0xba, 0x33, 0x74, 0xdc, 0x76, 0x73, 0xea, 0xe0, 0xb0, 0xd3, 0xfa, 0x71, + 0xd8, 0x69, 0x75, 0x1d, 0xb4, 0xdc, 0x44, 0xcc, 0x05, 0x35, 0x14, 0x5c, 0x41, 0xf7, 0x93, 0x85, + 0x6e, 0xf6, 0x55, 0xf4, 0x3a, 0x0d, 0x12, 0xa6, 0x2b, 0x40, 0x9f, 0xa9, 0x00, 0x06, 0x7e, 0xc6, + 0x44, 0x2a, 0xed, 0x65, 0x34, 0xad, 0x4c, 0x54, 0x83, 0x2c, 0x35, 0x8c, 0x1d, 0xf6, 0x2b, 0x34, + 0x9b, 0xd4, 0xd0, 0x46, 0xc4, 0xcc, 0xfa, 0x2a, 0x66, 0x01, 0xc5, 0xf5, 0x11, 0xe3, 0xda, 0x50, + 0xb3, 0x35, 0x5c, 0xef, 0xe0, 0x9e, 0xa9, 0x50, 0xe3, 0xbe, 0x82, 0xee, 0x9e, 0x4b, 0xed, 0x54, + 0xc4, 0xc1, 0x44, 0x83, 0x88, 0x6d, 0x91, 0x06, 0x31, 0xec, 0x08, 0xcd, 0x78, 0x74, 0x81, 0x08, + 0x0f, 0x2d, 0x84, 0xe9, 0x30, 0x66, 0xd4, 0xd7, 0xe0, 0x65, 0x42, 0x83, 0x57, 0xbd, 0x6f, 0xa9, + 0x67, 0xa5, 0x4e, 0xdf, 0x6c, 0x00, 0xde, 0xae, 0x12, 0x76, 0x84, 0x86, 0xa7, 0x25, 0xdc, 0x9d, + 0x0f, 0x9b, 0xdc, 0xf6, 0x3b, 0xb4, 0xc0, 0xf8, 0xae, 0xf4, 0xa9, 0x66, 0x82, 0x7b, 0x41, 0x2c, + 0xe8, 0x9e, 0x37, 0x00, 0x3f, 0x04, 0x69, 0x5e, 0x6f, 0x66, 0xfd, 0xde, 0x45, 0x03, 0x7b, 0x66, + 0xd0, 0xee, 0xfc, 0xb8, 0x4c, 0x6f, 0x54, 0xa5, 0x70, 0x5f, 0x30, 0xb3, 0xfa, 0x24, 0xaa, 0x99, + 0xad, 0xff, 0x9a, 0x44, 0x93, 0x7d, 0x15, 0xd9, 0x1f, 0x2c, 0x74, 0xe3, 0xcf, 0xbd, 0x7d, 0x84, + 0x2f, 0xf1, 0x53, 0xe2, 0xa6, 0xcd, 0x6a, 0x6f, 0x5d, 0x39, 0xb5, 0xe2, 0x66, 0x7f, 0xb6, 0x50, + 0xfb, 0x9c, 0x8d, 0xec, 0x5d, 0xb6, 0xc3, 0xdf, 0x6b, 0xb4, 0x5f, 0xfc, 0x7b, 0x8d, 0x73, 0xe8, + 0x9e, 0xd9, 0xbd, 0x2b, 0xd2, 0xad, 0xd7, 0xb8, 0x2a, 0xdd, 0xa6, 0x97, 0xef, 0xbd, 0xf9, 0x72, + 0xec, 0x58, 0x47, 0xc7, 0x8e, 0xf5, 0xfd, 0xd8, 0xb1, 0xde, 0x9f, 0x38, 0xad, 0xa3, 0x13, 0xa7, + 0xf5, 0xf5, 0xc4, 0x69, 0xbd, 0x7d, 0x1c, 0x31, 0x3d, 0x48, 0x03, 0x4c, 0x45, 0x52, 0x1e, 0x53, + 0x32, 0x6e, 0xfb, 0xe0, 0xf4, 0x92, 0x67, 0x1b, 0x64, 0xff, 0xec, 0x39, 0x37, 0xbf, 0x44, 0xf0, + 0x9f, 0x39, 0x86, 0x1b, 0xbf, 0x03, 0x00, 0x00, 0xff, 0xff, 0x4c, 0x5a, 0x31, 0xaa, 0xff, 0x05, + 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -155,6 +339,8 @@ const _ = grpc.SupportPackageIsVersion4 // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. type MsgClient interface { AssignConsumerKey(ctx context.Context, in *MsgAssignConsumerKey, opts ...grpc.CallOption) (*MsgAssignConsumerKeyResponse, error) + SubmitConsumerMisbehaviour(ctx context.Context, in *MsgSubmitConsumerMisbehaviour, opts ...grpc.CallOption) (*MsgSubmitConsumerMisbehaviourResponse, error) + SubmitConsumerDoubleVoting(ctx context.Context, in *MsgSubmitConsumerDoubleVoting, opts ...grpc.CallOption) (*MsgSubmitConsumerDoubleVotingResponse, error) } type msgClient struct { @@ -174,9 +360,29 @@ func (c *msgClient) AssignConsumerKey(ctx context.Context, in *MsgAssignConsumer 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 +} + +func (c *msgClient) SubmitConsumerDoubleVoting(ctx context.Context, in *MsgSubmitConsumerDoubleVoting, opts ...grpc.CallOption) (*MsgSubmitConsumerDoubleVotingResponse, error) { + out := new(MsgSubmitConsumerDoubleVotingResponse) + err := c.cc.Invoke(ctx, "/interchain_security.ccv.provider.v1.Msg/SubmitConsumerDoubleVoting", 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) + SubmitConsumerMisbehaviour(context.Context, *MsgSubmitConsumerMisbehaviour) (*MsgSubmitConsumerMisbehaviourResponse, error) + SubmitConsumerDoubleVoting(context.Context, *MsgSubmitConsumerDoubleVoting) (*MsgSubmitConsumerDoubleVotingResponse, error) } // UnimplementedMsgServer can be embedded to have forward compatible implementations. @@ -186,6 +392,12 @@ type UnimplementedMsgServer struct { func (*UnimplementedMsgServer) AssignConsumerKey(ctx context.Context, req *MsgAssignConsumerKey) (*MsgAssignConsumerKeyResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method AssignConsumerKey not implemented") } +func (*UnimplementedMsgServer) SubmitConsumerMisbehaviour(ctx context.Context, req *MsgSubmitConsumerMisbehaviour) (*MsgSubmitConsumerMisbehaviourResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SubmitConsumerMisbehaviour not implemented") +} +func (*UnimplementedMsgServer) SubmitConsumerDoubleVoting(ctx context.Context, req *MsgSubmitConsumerDoubleVoting) (*MsgSubmitConsumerDoubleVotingResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SubmitConsumerDoubleVoting not implemented") +} func RegisterMsgServer(s grpc1.Server, srv MsgServer) { s.RegisterService(&_Msg_serviceDesc, srv) @@ -209,6 +421,42 @@ func _Msg_AssignConsumerKey_Handler(srv interface{}, ctx context.Context, dec fu 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) +} + +func _Msg_SubmitConsumerDoubleVoting_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgSubmitConsumerDoubleVoting) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).SubmitConsumerDoubleVoting(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/interchain_security.ccv.provider.v1.Msg/SubmitConsumerDoubleVoting", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).SubmitConsumerDoubleVoting(ctx, req.(*MsgSubmitConsumerDoubleVoting)) + } + return interceptor(ctx, in, info, handler) +} + var _Msg_serviceDesc = grpc.ServiceDesc{ ServiceName: "interchain_security.ccv.provider.v1.Msg", HandlerType: (*MsgServer)(nil), @@ -217,6 +465,14 @@ var _Msg_serviceDesc = grpc.ServiceDesc{ MethodName: "AssignConsumerKey", Handler: _Msg_AssignConsumerKey_Handler, }, + { + MethodName: "SubmitConsumerMisbehaviour", + Handler: _Msg_SubmitConsumerMisbehaviour_Handler, + }, + { + MethodName: "SubmitConsumerDoubleVoting", + Handler: _Msg_SubmitConsumerDoubleVoting_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "interchain_security/ccv/provider/v1/tx.proto", @@ -289,6 +545,148 @@ func (m *MsgAssignConsumerKeyResponse) MarshalToSizedBuffer(dAtA []byte) (int, e 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] = 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 (m *MsgSubmitConsumerDoubleVoting) 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 *MsgSubmitConsumerDoubleVoting) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgSubmitConsumerDoubleVoting) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.InfractionBlockHeader != nil { + { + size, err := m.InfractionBlockHeader.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + if m.DuplicateVoteEvidence != nil { + { + size, err := m.DuplicateVoteEvidence.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + 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 *MsgSubmitConsumerDoubleVotingResponse) 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 *MsgSubmitConsumerDoubleVotingResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgSubmitConsumerDoubleVotingResponse) 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 @@ -330,17 +728,73 @@ func (m *MsgAssignConsumerKeyResponse) Size() (n int) { return n } -func sovTx(x uint64) (n int) { - return (math_bits.Len64(x|1) + 6) / 7 -} -func sozTx(x uint64) (n int) { - return sovTx(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +func (m *MsgSubmitConsumerMisbehaviour) Size() (n int) { + if m == nil { + return 0 + } + 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)) + } + return n } -func (m *MsgAssignConsumerKey) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx + +func (m *MsgSubmitConsumerMisbehaviourResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *MsgSubmitConsumerDoubleVoting) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Submitter) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + if m.DuplicateVoteEvidence != nil { + l = m.DuplicateVoteEvidence.Size() + n += 1 + l + sovTx(uint64(l)) + } + if m.InfractionBlockHeader != nil { + l = m.InfractionBlockHeader.Size() + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgSubmitConsumerDoubleVotingResponse) 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 +} +func sozTx(x uint64) (n int) { + return sovTx(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *MsgAssignConsumerKey) 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 { @@ -532,6 +986,378 @@ func (m *MsgAssignConsumerKeyResponse) 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 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) + } + 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 = &_07_tendermint.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 (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 (m *MsgSubmitConsumerDoubleVoting) 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: MsgSubmitConsumerDoubleVoting: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgSubmitConsumerDoubleVoting: illegal tag %d (wire type %d)", fieldNum, wire) + } + 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 DuplicateVoteEvidence", 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.DuplicateVoteEvidence == nil { + m.DuplicateVoteEvidence = &types.DuplicateVoteEvidence{} + } + if err := m.DuplicateVoteEvidence.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field InfractionBlockHeader", 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.InfractionBlockHeader == nil { + m.InfractionBlockHeader = &_07_tendermint.Header{} + } + if err := m.InfractionBlockHeader.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 (m *MsgSubmitConsumerDoubleVotingResponse) 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: MsgSubmitConsumerDoubleVotingResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgSubmitConsumerDoubleVotingResponse: 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/errors.go b/x/ccv/types/errors.go index b719e60479..e492984e48 100644 --- a/x/ccv/types/errors.go +++ b/x/ccv/types/errors.go @@ -6,14 +6,20 @@ import ( // CCV sentinel errors var ( - ErrInvalidPacketData = errorsmod.Register(ModuleName, 1, "invalid CCV packet data") - ErrInvalidVersion = errorsmod.Register(ModuleName, 2, "invalid CCV version") - ErrInvalidChannelFlow = errorsmod.Register(ModuleName, 3, "invalid message sent to channel end") - ErrInvalidGenesis = errorsmod.Register(ModuleName, 4, "invalid genesis state") - ErrDuplicateChannel = errorsmod.Register(ModuleName, 5, "CCV channel already exists") - ErrInvalidVSCMaturedId = errorsmod.Register(ModuleName, 6, "invalid vscId for VSC packet") - ErrInvalidVSCMaturedTime = errorsmod.Register(ModuleName, 7, "invalid maturity time for VSC packet") - ErrInvalidHandshakeMetadata = errorsmod.Register(ModuleName, 8, "invalid provider handshake metadata") - ErrChannelNotFound = errorsmod.Register(ModuleName, 9, "channel not found") - ErrClientNotFound = errorsmod.Register(ModuleName, 10, "client not found") + ErrInvalidPacketData = errorsmod.Register(ModuleName, 1, "invalid CCV packet data") + ErrInvalidVersion = errorsmod.Register(ModuleName, 2, "invalid CCV version") + ErrInvalidChannelFlow = errorsmod.Register(ModuleName, 3, "invalid message sent to channel end") + ErrInvalidGenesis = errorsmod.Register(ModuleName, 4, "invalid genesis state") + ErrDuplicateChannel = errorsmod.Register(ModuleName, 5, "CCV channel already exists") + ErrInvalidVSCMaturedId = errorsmod.Register(ModuleName, 6, "invalid vscId for VSC packet") + ErrInvalidVSCMaturedTime = errorsmod.Register(ModuleName, 7, "invalid maturity time for VSC packet") + ErrInvalidHandshakeMetadata = errorsmod.Register(ModuleName, 8, "invalid provider handshake metadata") + ErrChannelNotFound = errorsmod.Register(ModuleName, 9, "channel not found") + ErrClientNotFound = errorsmod.Register(ModuleName, 10, "client not found") + ErrInvalidConsumerState = errorsmod.Register(ModuleName, 11, "provider chain has invalid state for consumer chain") + ErrInvalidConsumerClient = errorsmod.Register(ModuleName, 12, "ccv channel is not built on correct client") + ErrInvalidProposal = errorsmod.Register(ModuleName, 13, "invalid proposal") + ErrDuplicateConsumerChain = errorsmod.Register(ModuleName, 14, "consumer chain already exists") + ErrConsumerChainNotFound = errorsmod.Register(ModuleName, 15, "consumer chain not found") + ErrInvalidDoubleVotingEvidence = errorsmod.Register(ModuleName, 16, "invalid consumer double voting evidence") ) diff --git a/x/ccv/types/events.go b/x/ccv/types/events.go index 7c7343021e..4c597ded56 100644 --- a/x/ccv/types/events.go +++ b/x/ccv/types/events.go @@ -2,17 +2,41 @@ package types // CCV events const ( - EventTypeTimeout = "timeout" - EventTypePacket = "ccv_packet" - EventTypeChannelEstablished = "channel_established" + EventTypeTimeout = "timeout" + EventTypePacket = "ccv_packet" + EventTypeChannelEstablished = "channel_established" + EventTypeFeeTransferChannelOpened = "fee_transfer_channel_opened" + EventTypeConsumerClientCreated = "consumer_client_created" + EventTypeAssignConsumerKey = "assign_consumer_key" + EventTypeAddConsumerRewardDenom = "add_consumer_reward_denom" + EventTypeRemoveConsumerRewardDenom = "remove_consumer_reward_denom" + EventTypeSubmitConsumerMisbehaviour = "submit_consumer_misbehaviour" + EventTypeSubmitConsumerDoubleVoting = "submit_consumer_double_voting" + EventTypeExecuteConsumerChainSlash = "execute_consumer_chain_slash" + EventTypeFeeDistribution = "fee_distribution" + EventTypeConsumerSlashRequest = "consumer_slash_request" + EventTypeVSCMatured = "vsc_matured" - AttributeKeyAckSuccess = "success" - AttributeKeyAck = "acknowledgement" - AttributeKeyAckError = "error" - - AttributeChainID = "chain_id" - AttributeValidatorAddress = "validator_address" - AttributeInfractionType = "infraction_type" - - AttributeValSetUpdateID = "valset_update_id" + AttributeKeyAckSuccess = "success" + AttributeKeyAck = "acknowledgement" + AttributeKeyAckError = "error" + AttributeInfractionHeight = "infraction_height" + AttributeConsumerHeight = "consumer_height" + AttributeTimestamp = "timestamp" + AttributeInitialHeight = "initial_height" + AttributeInitializationTimeout = "initialization_timeout" + AttributeTrustingPeriod = "trusting_period" + AttributeUnbondingPeriod = "unbonding_period" + AttributeProviderValidatorAddress = "provider_validator_address" + AttributeConsumerConsensusPubKey = "consumer_consensus_pub_key" + AttributeSubmitterAddress = "submitter_address" + AttributeConsumerMisbehaviour = "consumer_misbehaviour" + AttributeMisbehaviourClientId = "misbehaviour_client_id" + AttributeMisbehaviourHeight1 = "misbehaviour_height_1" + AttributeMisbehaviourHeight2 = "misbehaviour_height_2" + AttributeConsumerDoubleVoting = "consumer_double_voting" + AttributeChainID = "chain_id" + AttributeValidatorAddress = "validator_address" + AttributeInfractionType = "infraction_type" + AttributeValSetUpdateID = "valset_update_id" ) diff --git a/x/ccv/types/expected_keepers.go b/x/ccv/types/expected_keepers.go index 2f203a47ea..5b2f3f7a53 100644 --- a/x/ccv/types/expected_keepers.go +++ b/x/ccv/types/expected_keepers.go @@ -15,7 +15,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" auth "github.com/cosmos/cosmos-sdk/x/auth/types" capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" - evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" @@ -31,10 +30,11 @@ type StakingKeeper interface { UnbondingTime(ctx sdk.Context) time.Duration GetValidatorByConsAddr(ctx sdk.Context, consAddr sdk.ConsAddress) (validator stakingtypes.Validator, found bool) GetLastValidatorPower(ctx sdk.Context, operator sdk.ValAddress) (power int64) - // slash the validator and delegators of the validator, specifying offence height, offence power, and slash fraction Jail(sdk.Context, sdk.ConsAddress) // jail a validator Slash(sdk.Context, sdk.ConsAddress, int64, int64, sdk.Dec) math.Int SlashWithInfractionReason(sdk.Context, sdk.ConsAddress, int64, int64, sdk.Dec, stakingtypes.Infraction) math.Int + SlashUnbondingDelegation(sdk.Context, stakingtypes.UnbondingDelegation, int64, sdk.Dec) math.Int + SlashRedelegation(sdk.Context, stakingtypes.Validator, stakingtypes.Redelegation, int64, sdk.Dec) math.Int Unjail(ctx sdk.Context, addr sdk.ConsAddress) GetValidator(ctx sdk.Context, addr sdk.ValAddress) (validator stakingtypes.Validator, found bool) IterateLastValidatorPowers(ctx sdk.Context, cb func(addr sdk.ValAddress, power int64) (stop bool)) @@ -48,12 +48,10 @@ type StakingKeeper interface { MaxValidators(ctx sdk.Context) uint32 GetLastTotalPower(ctx sdk.Context) math.Int GetLastValidators(ctx sdk.Context) (validators []stakingtypes.Validator) - GetUnbondingType(ctx sdk.Context, id uint64) (unbondingType stakingtypes.UnbondingType, found bool) BondDenom(ctx sdk.Context) (res string) -} - -type EvidenceKeeper interface { - HandleEquivocationEvidence(ctx sdk.Context, evidence *evidencetypes.Equivocation) + GetUnbondingDelegationsFromValidator(ctx sdk.Context, valAddr sdk.ValAddress) (ubds []stakingtypes.UnbondingDelegation) + GetRedelegationsFromSrcValidator(ctx sdk.Context, valAddr sdk.ValAddress) (reds []stakingtypes.Redelegation) + GetUnbondingType(ctx sdk.Context, id uint64) (unbondingType stakingtypes.UnbondingType, found bool) } // SlashingKeeper defines the contract expected to perform ccv slashing @@ -101,6 +99,9 @@ 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) + GetClientConsensusState(ctx sdk.Context, clientID string, height ibcexported.Height) (ibcexported.ConsensusState, bool) } // DistributionKeeper defines the expected interface of the distribution keeper From fec3eccad59416cbdb6844e279f59e3f81242888 Mon Sep 17 00:00:00 2001 From: Marius Poke Date: Wed, 22 Nov 2023 19:08:39 +0100 Subject: [PATCH 12/19] docs: bring v2.4.0-lsm changelog section to main (#1441) * backport v2.4.0-lsm changelog section to main * update release info * add existing major features --- .changelog/epilogue.md | 22 ++++++++++++++++++++++ CHANGELOG.md | 11 +++++++++++ FEATURES.md | 18 ++++++++++++++++++ README.md | 6 +++++- RELEASES.md | 8 +++++--- 5 files changed, 61 insertions(+), 4 deletions(-) create mode 100644 FEATURES.md diff --git a/.changelog/epilogue.md b/.changelog/epilogue.md index c8963c332f..861dc712e3 100644 --- a/.changelog/epilogue.md +++ b/.changelog/epilogue.md @@ -23,6 +23,28 @@ Interchain Security v3 uses SDK 0.47 and IBC 7. * `[x/ccv/provider]` (fix) [#977](https://github.com/cosmos/interchain-security/pull/977) Avoids panicking the provider when an unbonding delegation was removed through a `CancelUnbondingDelegation` message. * `[x/ccv/democracy]` (feat) [#1019](https://github.com/cosmos/interchain-security/pull/1019) Whitelisting non-legacy params in the "democracy module" require the entire module to be whitelisted. +## v2.4.0-lsm + +*November 20, 2023* + +* (fix) [#1439](https://github.com/cosmos/interchain-security/pull/1439) Fix unmarshaling for the CLI consumer double vote cmd. +* (feat!) [#1435](https://github.com/cosmos/interchain-security/pull/1435) Add height-base filter for consumer equivocation evidence. + +## v2.3.0-provider-lsm + +*November 15, 2023* + +❗ *This release is deprecated and should not be used in production.* + +* (fix!) [#1422](https://github.com/cosmos/interchain-security/pull/1422) Fix the misbehaviour handling by verifying the signatures of byzantine validators. + +## v2.2.0-provider-lsm + +❗ *This release is deprecated and should not be used in production.* + +### Cryptographic verification of equivocation +* New feature enabling the provider chain to verify equivocation evidence on its own instead of trusting consumer chains, see [EPIC](https://github.com/cosmos/interchain-security/issues/732). + ## v2.1.0-provider-lsm Date: September 15th, 2023 diff --git a/CHANGELOG.md b/CHANGELOG.md index 046a01c16e..7236eb5ec7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,14 +25,25 @@ Interchain Security v3 uses SDK 0.47 and IBC 7. * `[x/ccv/provider]` (fix) [#977](https://github.com/cosmos/interchain-security/pull/977) Avoids panicking the provider when an unbonding delegation was removed through a `CancelUnbondingDelegation` message. * `[x/ccv/democracy]` (feat) [#1019](https://github.com/cosmos/interchain-security/pull/1019) Whitelisting non-legacy params in the "democracy module" require the entire module to be whitelisted. +## v2.4.0-lsm + +*November 20, 2023* + +* (fix) [#1439](https://github.com/cosmos/interchain-security/pull/1439) Fix unmarshaling for the CLI consumer double vote cmd. +* (feat!) [#1435](https://github.com/cosmos/interchain-security/pull/1435) Add height-base filter for consumer equivocation evidence. + ## v2.3.0-provider-lsm *November 15, 2023* +❗ *This release is deprecated and should not be used in production.* + * (fix!) [#1422](https://github.com/cosmos/interchain-security/pull/1422) Fix the misbehaviour handling by verifying the signatures of byzantine validators. ## v2.2.0-provider-lsm +❗ *This release is deprecated and should not be used in production.* + ### Cryptographic verification of equivocation * New feature enabling the provider chain to verify equivocation evidence on its own instead of trusting consumer chains, see [EPIC](https://github.com/cosmos/interchain-security/issues/732). diff --git a/FEATURES.md b/FEATURES.md new file mode 100644 index 0000000000..d536a58643 --- /dev/null +++ b/FEATURES.md @@ -0,0 +1,18 @@ +# Features + +The following table indicates the major ICS features available in the [currently active releases](./RELEASES.md#version-matrix): + +| Feature | Release | `v1.2.0-multiden` | `v2.0.0` | `v2.1.0-provider-lsm` | `v2.4.0-lsm` | `v3.1.0` | +|---------|---------|------------------:|---------:|----------------------:|-------------:|---------:| +| [Channel initialization: new chains](https://github.com/cosmos/ibc/blob/main/spec/app/ics-028-cross-chain-validation/overview_and_basic_concepts.md#channel-initialization-new-chains) | | ✅ | ✅ | ✅ | ✅ | ✅ | +| [Validator set update](https://github.com/cosmos/ibc/blob/main/spec/app/ics-028-cross-chain-validation/overview_and_basic_concepts.md#validator-set-update) | | ✅ | ✅ | ✅ | ✅ | ✅ | +| [Completion of unbonding operations](https://github.com/cosmos/ibc/blob/main/spec/app/ics-028-cross-chain-validation/overview_and_basic_concepts.md#completion-of-unbonding-operations) | | ✅ | ✅ | ✅ | ✅ | ✅ | +| [Consumer initiated slashing](https://github.com/cosmos/ibc/blob/main/spec/app/ics-028-cross-chain-validation/overview_and_basic_concepts.md#consumer-initiated-slashing) | | ✅ | ✅ | ✅ | ✅ | ✅ | +| [Reward distribution](https://github.com/cosmos/ibc/blob/main/spec/app/ics-028-cross-chain-validation/overview_and_basic_concepts.md#reward-distribution) | | ✅ | ✅ | ✅ | ✅ | ✅ | +| [Consumer chain removal](https://github.com/cosmos/ibc/blob/main/spec/app/ics-028-cross-chain-validation/methods.md#consumer-chain-removal) | | ✅ | ✅ | ✅ | ✅ | ✅ | +| [Key assignment](https://github.com/cosmos/interchain-security/issues/26) | | ✅ | ✅ | ✅ | ✅ | ✅ | +| [Jail throttling](https://github.com/cosmos/interchain-security/issues/404) | | ✅ | ✅ | ✅ | ✅ | ✅ | +| [Soft opt-out](https://github.com/cosmos/interchain-security/issues/851) | | ✅ | ✅ | ✅ | ✅ | ✅ | +| [Channel initialization: existing chains](https://github.com/cosmos/ibc/blob/main/spec/app/ics-028-cross-chain-validation/overview_and_basic_concepts.md#channel-initialization-existing-chains) (aka [Standalone to consumer changeover](https://github.com/cosmos/interchain-security/issues/756))| | ❌ | ✅ | ✅ | ✅ | ✅ | +| [Cryptographic verification of equivocation](https://github.com/cosmos/interchain-security/issues/732) | | ❌ | ❌ | ❌ | ✅ | ❌ | + diff --git a/README.md b/README.md index 0b9ea0e5c9..c3e908b4b3 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,11 @@ **interchain-security** contains a working and in-production implementation of the Replicated Security protocol (aka Interchain Security V1). Replicated security is an open sourced IBC application which allows cosmos blockchains to lease their proof-of-stake security to one another. -For more details on the Replicated Security protocol, take a look at the [docs](https://cosmos.github.io/interchain-security/) or [technical specification](https://github.com/cosmos/ibc/blob/main/spec/app/ics-028-cross-chain-validation/README.md). +For more details on the **Replicated Security protocol**, take a look at the [docs](https://cosmos.github.io/interchain-security/) or [technical specification](https://github.com/cosmos/ibc/blob/main/spec/app/ics-028-cross-chain-validation/README.md). + +For a list of **currently active releases**, see [RELEASES.md](./RELEASES.md#version-matrix). + +For a list of **major ICS features** available in the currently active releases, see [FEATURES.md](./FEATURES.md). ## Instructions diff --git a/RELEASES.md b/RELEASES.md index 92662b3318..ee382b0309 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -61,7 +61,7 @@ All missing minor release versions have been discontinued. | `v1.2.x` | February 21, 2024 | | `v2.0.x` | June 09, 2024 | | `v2.1.x-provider-lsm` | June 09, 2024 | -| `v2.3.x-provider-lsm` | June 09, 2024 | +| `v2.4.x-lsm` | June 09, 2024 | | `v3.1.x` | July 10, 2024 | | `v3.2.x` | July 10, 2024 | @@ -81,16 +81,18 @@ Versions of Golang, IBC, Cosmos SDK and CometBFT used by ICS in the currently ac | [v1.2.0-multiden](https://github.com/cosmos/interchain-security/releases/tag/v1.2.0-multiden) | 1.18 | v4.2.0 | v0.45.15-ics | v0.34.27 | Consumer only | | [v2.0.0](https://github.com/cosmos/interchain-security/releases/tag/v2.0.0) | 1.19 | v4.4.2 | v0.45.15-ics | v0.34.28 | | [v2.1.0-provider-lsm](https://github.com/cosmos/interchain-security/releases/tag/v2.1.0-provider-lsm) | 1.19 | v4.4.2 | v0.45.16-ics-lsm | v0.34.28 | Provider only (Cosmos Hub specific) | -| [v2.3.0-provider-lsm](https://github.com/cosmos/interchain-security/releases/tag/v2.3.0-provider-lsm) | 1.19 | v4.4.2 | v0.45.16-ics-lsm | v0.34.28 | Provider only (Cosmos Hub specific) | +| [v2.4.0-lsm](https://github.com/cosmos/interchain-security/releases/tag/v2.4.0-lsm) | 1.19 | v4.4.2 | v0.45.16-ics-lsm | v0.34.28 | Provider only (Cosmos Hub specific) | | [v3.1.0](https://github.com/cosmos/interchain-security/releases/tag/v3.1.0) | 1.20 | v7.1.0 | v0.47.3 | v0.37.2 | +**Note:** For a list of major ICS features available in the currently active releases, see [FEATURES.md](./FEATURES.md). + ### Backwards Compatibility A MAJOR version of ICS will always be backwards compatible with the previous MAJOR version of ICS. The following table indicates the compatibility of currently active releases: -| consumer | provider | `v2.0.0` | `v2.1.0-provider-lsm` | `v2.3.0-provider-lsm` | `v3.1.0` | +| Consumer | Provider | `v2.0.0` | `v2.1.0-provider-lsm` | `v2.4.0-lsm` | `v3.1.0` | |----------|----------|--------:|----------------------:|----------------------:|---------:| | `v1.2.0-multiden` || ✅ | ✅ | ✅ | ✅ | | `v2.0.0` || ✅ | ✅ | ✅ | ✅ | From e377d59c544d46b691e3076929c2e637f2748f6e Mon Sep 17 00:00:00 2001 From: bernd-m <43466467+bermuell@users.noreply.github.com> Date: Mon, 27 Nov 2023 17:39:28 +0100 Subject: [PATCH 13/19] test: Update outdated base image version of fedora to latest version (#1450) Update outdated base image version of fedora to latest version --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 2e4ea3c334..665aa3d84f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ ADD . /interchain-security WORKDIR /interchain-security -# Do not specify version here. It leads to odd replacement behavior +# Do not specify version here. It leads to odd replacement behavior RUN if [ -d "./cosmos-sdk" ]; then go mod edit -replace github.com/cosmos/cosmos-sdk=./cosmos-sdk; fi RUN go mod tidy @@ -37,7 +37,7 @@ FROM ghcr.io/informalsystems/cometmock:v0.37.x as cometmock-builder # Get GoRelayer FROM ghcr.io/informalsystems/relayer-no-gas-sim:v2.3.0-rc4-no-gas-sim AS gorelayer-builder -FROM --platform=linux/amd64 fedora:36 +FROM --platform=linux/amd64 fedora:39 RUN dnf update -y RUN dnf install -y which iproute iputils procps-ng vim-minimal tmux net-tools htop jq USER root From 2a8a3d4e6ca88eec78b0417e6acd76c1f3eee2bd Mon Sep 17 00:00:00 2001 From: Marius Poke Date: Mon, 27 Nov 2023 17:40:11 +0100 Subject: [PATCH 14/19] ci: update test workflow (#1449) * update test workflow * rename Gosec job to avoid conflict --- .github/workflows/automated-tests.yml | 72 -------- .github/workflows/build.yml | 32 ---- .github/workflows/gosec.yml | 2 +- .github/workflows/test.yml | 239 ++++++++++++++++++++++++++ Makefile | 31 ++-- TESTING.md | 15 +- sonar-project.properties | 6 +- 7 files changed, 276 insertions(+), 121 deletions(-) delete mode 100644 .github/workflows/automated-tests.yml delete mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/automated-tests.yml b/.github/workflows/automated-tests.yml deleted file mode 100644 index f7d9988bad..0000000000 --- a/.github/workflows/automated-tests.yml +++ /dev/null @@ -1,72 +0,0 @@ -name: Automated Tests -on: - push: - branches: - - main - - release/v* - - feat/* - pull_request: - branches: - - main - - release/v* - - feat/* -jobs: - Automated_Tests: - runs-on: ubuntu-latest - steps: - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v4 - with: - lfs: true - - name: Checkout LFS objects - run: git lfs checkout - - name: Setup Go - uses: actions/setup-go@v4 - with: - go-version: "1.20" # The Go version to download (if necessary) and use. - - name: Proto Check - run: make proto-check - - name: Unit, integration and difference tests - run: go test ./... - E2E_Tests: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - lfs: true - - name: Checkout LFS objects - run: git lfs checkout - - name: Setup Go - uses: actions/setup-go@v4 - with: - go-version: "1.20" - - name: E2E tests - run: make test-e2e-short - Cometmock_Tests: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - lfs: true - - name: Checkout LFS objects - run: git lfs checkout - - name: Setup Go - uses: actions/setup-go@v4 - with: - go-version: "1.20" - - name: E2E tests - run: make test-e2e-short-cometmock - Trace-Tests: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - lfs: true - - name: Checkout LFS objects - run: git lfs checkout - - name: Setup Go - uses: actions/setup-go@v4 - with: - go-version: "1.20" - - name: E2E tests - run: make test-trace diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index a812b9fcf2..0000000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Build -on: - push: - branches: - - main - - feat/* - pull_request: - types: [opened, synchronize, reopened] -jobs: - sonarcloud: - # skip this on forks or dependabot PRs - name: SonarCloud - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - lfs: true - - name: Checkout LFS objects - run: git lfs checkout - - name: Setup Go - uses: actions/setup-go@v4 - with: - go-version: "1.20" # The Go version to download (if necessary) and use. - - name: Test with coverage - run: go test -coverpkg=./x/... -coverprofile=coverage.out ./... - - name: SonarCloud Scan - uses: SonarSource/sonarcloud-github-action@master - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - if: env.SONAR_TOKEN != null diff --git a/.github/workflows/gosec.yml b/.github/workflows/gosec.yml index 80dc4a3689..d01bfd37f4 100644 --- a/.github/workflows/gosec.yml +++ b/.github/workflows/gosec.yml @@ -9,7 +9,7 @@ on: - main - feat/* jobs: - tests: + Gosec: runs-on: ubuntu-latest env: GO111MODULE: on diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000000..efd2860398 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,239 @@ +name: Test +on: + workflow_call: + pull_request: + push: + branches: + - main + - release/v* + - feat/* + +permissions: + contents: read + +concurrency: + group: ci-${{ github.ref }}-tests + cancel-in-progress: true + +jobs: + tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v4 + with: + go-version: "1.20" + check-latest: true + cache: true + cache-dependency-path: go.sum + - uses: technote-space/get-diff-action@v6.1.2 + id: git_diff + with: + PATTERNS: | + **/*.proto + **/*.go + go.mod + go.sum + **/go.mod + **/go.sum + **/Makefile + Makefile + - uses: actions/cache@v3.3.2 + with: + path: | + ~/.cache/go-build + ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.mod') }} + restore-keys: | + ${{ runner.os }}-go- + - name: proto check + run: make proto-check + - name: test & coverage report creation + if: env.GIT_DIFF + run: | + make test-unit-cov + - uses: actions/upload-artifact@v3 + if: env.GIT_DIFF + with: + name: "${{ github.sha }}-coverage" + path: ./profile.out + + test-integration: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v4 + with: + go-version: "1.20" + check-latest: true + cache: true + cache-dependency-path: go.sum + - uses: technote-space/get-diff-action@v6.1.2 + id: git_diff + with: + PATTERNS: | + **/*.go + go.mod + go.sum + **/go.mod + **/go.sum + **/Makefile + Makefile + - name: integration tests + if: env.GIT_DIFF + run: | + make test-integration-cov + - uses: actions/upload-artifact@v3 + if: env.GIT_DIFF + with: + name: "${{ github.sha }}-integration-coverage" + path: ./integration-profile.out + + test-difference: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v4 + with: + go-version: "1.20" + check-latest: true + cache: true + cache-dependency-path: go.sum + - uses: technote-space/get-diff-action@v6.1.2 + id: git_diff + with: + PATTERNS: | + **/*.go + go.mod + go.sum + **/go.mod + **/go.sum + **/Makefile + Makefile + - name: difference tests + if: env.GIT_DIFF + run: | + make test-difference-cov + - uses: actions/upload-artifact@v3 + if: env.GIT_DIFF + with: + name: "${{ github.sha }}-difference-coverage" + path: ./difference-profile.out + + repo-analysis: + runs-on: ubuntu-latest + needs: [tests, test-integration, test-difference] + steps: + - uses: actions/checkout@v4 + - uses: technote-space/get-diff-action@v6.1.2 + id: git_diff + with: + PATTERNS: | + **/*.go + go.mod + go.sum + **/go.mod + **/go.sum + - uses: actions/download-artifact@v3 + if: env.GIT_DIFF + with: + name: "${{ github.sha }}-coverage" + - uses: actions/download-artifact@v3 + if: env.GIT_DIFF + with: + name: "${{ github.sha }}-integration-coverage" + - uses: actions/download-artifact@v3 + if: env.GIT_DIFF + with: + name: "${{ github.sha }}-difference-coverage" + continue-on-error: true + - name: sonarcloud + if: ${{ env.GIT_DIFF && !github.event.pull_request.draft }} + uses: SonarSource/sonarcloud-github-action@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + + test-e2e: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + lfs: true + - name: checkout LFS objects + run: git lfs checkout + - uses: actions/setup-go@v4 + with: + go-version: "1.20" + check-latest: true + cache: true + cache-dependency-path: go.sum + - uses: technote-space/get-diff-action@v6.1.2 + id: git_diff + with: + PATTERNS: | + **/*.go + go.mod + go.sum + **/go.mod + **/go.sum + **/Makefile + Makefile + - name: e2e tests + run: make test-e2e-short + + test-cometmock: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + lfs: true + - name: checkout LFS objects + run: git lfs checkout + - uses: actions/setup-go@v4 + with: + go-version: "1.20" + check-latest: true + cache: true + cache-dependency-path: go.sum + - uses: technote-space/get-diff-action@v6.1.2 + id: git_diff + with: + PATTERNS: | + **/*.go + go.mod + go.sum + **/go.mod + **/go.sum + **/Makefile + Makefile + - name: cometmock tests + run: make test-e2e-short-cometmock + + test-trace: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + lfs: true + - name: checkout LFS objects + run: git lfs checkout + - uses: actions/setup-go@v4 + with: + go-version: "1.20" + check-latest: true + cache: true + cache-dependency-path: go.sum + - uses: technote-space/get-diff-action@v6.1.2 + id: git_diff + with: + PATTERNS: | + **/*.go + go.mod + go.sum + **/go.mod + **/go.sum + **/Makefile + Makefile + - name: trace-e2e tests + run: make test-trace diff --git a/Makefile b/Makefile index a614e3bea0..33420f4c4e 100644 --- a/Makefile +++ b/Makefile @@ -10,25 +10,36 @@ install: go.sum go install $(BUILD_FLAGS) ./cmd/interchain-security-sd # run all tests: unit, integration, diff, and E2E -test: - go test ./... && go run ./tests/e2e/... +test: test-unit test-integration test-difference test-e2e -# run all unit tests +# shortcut for local development +test-dev: test-unit test-integration test-difference + +# run unit tests test-unit: - go test ./... + go test ./x/... ./app/... + +test-unit-cov: + go test ./x/... ./app/... -coverpkg=./... -coverprofile=profile.out -covermode=atomic # run unit and integration tests -test-short: - go test ./x/... ./app/... ./tests/integration/... +test-integration: + go test ./tests/integration/... -timeout 30m + +test-integration-cov: + go test ./tests/integration/... -timeout 30m -coverpkg=./... -coverprofile=integration-profile.out -covermode=atomic + +# run difference tests +test-difference: + go test ./tests/difference/... -timeout 30m + +test-difference-cov: + go test ./tests/difference/... -timeout 30m -coverpkg=./... -coverprofile=difference-profile.out -covermode=atomic # run E2E tests test-e2e: go run ./tests/e2e/... -# run difference tests -test-diff: - go test ./tests/difference/... - # run only happy path E2E tests test-e2e-short: go run ./tests/e2e/... --tc happy-path diff --git a/TESTING.md b/TESTING.md index 4c288f5136..40117f9127 100644 --- a/TESTING.md +++ b/TESTING.md @@ -29,15 +29,24 @@ Tests can be run using `make`: # run unit, integration, diff, and E2E tests make test -# run unit and integration tests - prefer this for local development -make test-short +# run unit tests +make test-unit + +# run integration tests +make test-integration # run difference tests -make test-diff +make test-difference + +# run unit, integration, and difference tests - shortcut for local development +mate test-dev # run E2E tests make test-e2e +# run only happy path E2E tests +make test-e2e-short + # equivalent to make test with caching disabled make test-no-cache ``` diff --git a/sonar-project.properties b/sonar-project.properties index 34d8d28397..d845247826 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -2,7 +2,7 @@ sonar.projectKey=cosmos_interchain-security sonar.organization=cosmos # This is the name and version displayed in the SonarCloud UI. -#sonar.projectName=interchain-security +sonar.projectName=Interchain Security #sonar.projectVersion=1.0 # Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows. @@ -14,10 +14,10 @@ sonar.organization=cosmos # All golang artifacts sonar.sources=. # Do not calculate coverage metrics for statements in these files -sonar.exclusions=**/vendor/**,**/*.pb.go,**/*.pb.gw.go,proto,**/*_test.go,tests/**,testutil/**,legacy_ibc_testing/**,docs/**,app/**,cmd/**, +sonar.exclusions=**/vendor/**,**/*.pb.go,**/*.pb.gw.go,proto,**/*_test.go,tests/**,testutil/**,docs/**,app/**,cmd/**, sonar.tests=. # Run unit and integration tests, but not E2E tests sonar.test.inclusions=**/*_test.go # Do not run any vendor tests sonar.test.exclusions=**/vendor/** -sonar.go.coverage.reportPaths=coverage.out +sonar.go.coverage.reportPaths=coverage.out,*profile.out From 11b58587eeeae57b5ebe40a0e745e22d928401be Mon Sep 17 00:00:00 2001 From: yaruwangway <69694322+yaruwangway@users.noreply.github.com> Date: Tue, 28 Nov 2023 10:52:01 +0100 Subject: [PATCH 15/19] feat!: key assignment checking key in use (#1339) * feat!: store proposed chainID before voting finishes (#1289) * feat: store chain in proposal * add govHook * delete GetChainsInProposal * check proposal type * update key * feat: add query proposed chainIDs * feat: set govhook * feat: parse key * refactor: names * feat: add list proposed consumer chains * test: add e2e test * add e2e test * update comments * update ProposeConsumerChains in e2e test * remove wait for block * docs: update changelog * fix: lint * add TestParseProposedConsumerChainKey * refactor gov hook * Update proto/interchain_security/ccv/provider/v1/query.proto Co-authored-by: MSalopek * update proto * add test for set kv * refactor key to be prefix_proposalID * formatting * update e2e test * format * Update x/ccv/provider/keeper/gov_hook.go Co-authored-by: Shawn <44221603+smarshall-spitzbart@users.noreply.github.com> * Update x/ccv/provider/keeper/keeper.go Co-authored-by: Shawn <44221603+smarshall-spitzbart@users.noreply.github.com> * Update x/ccv/provider/keeper/keeper.go Co-authored-by: Shawn <44221603+smarshall-spitzbart@users.noreply.github.com> * fix e2e test * fix gosec * remove type url check * test: add unit test * lint * fix lint * fix err --------- Co-authored-by: MSalopek Co-authored-by: Shawn <44221603+smarshall-spitzbart@users.noreply.github.com> * fix test * feat: update GetAllValidatorsByConsumerAddr for fast find consensus key in use (#1279) * update GetAllValidatorsByConsumerAddr * fix test * update ValidatorConsensusKeyInUse * update changelog * Update proto/interchain_security/ccv/provider/v1/query.proto * Update x/ccv/provider/keeper/gov_hook.go * Update x/ccv/provider/keeper/keeper.go * Update x/ccv/provider/keeper/keeper.go * Update x/ccv/provider/keeper/keeper.go * fix gov hooks * fix bug and add tests * finish unit testing of consu addition legacy prop getter * nit * update changelog * lint * update changelog entry * Update .changelog/unreleased/features/provider/1339-check-key-assignment-in-use.md Co-authored-by: Marius Poke * fix #1282 * remove todos and update doc * tests: fix broken unit tests * tests: fix broken integration test consumer registration * tests: update names --------- Co-authored-by: MSalopek Co-authored-by: Shawn <44221603+smarshall-spitzbart@users.noreply.github.com> Co-authored-by: Simon Noetzlin Co-authored-by: Marius Poke --- .../1339-check-key-assignment-in-use.md | 3 + .../1339-check-key-assignment-in-use.md | 3 + app/provider/app.go | 42 +- go.sum | 2 + .../ccv/provider/v1/query.proto | 21 + tests/e2e/config.go | 3 +- tests/e2e/state.go | 28 + tests/e2e/steps_start_chains.go | 2 + tests/integration/setup.go | 4 + testutil/keeper/mocks.go | 39 + testutil/keeper/unit_test_helpers.go | 3 + x/ccv/provider/client/cli/query.go | 28 + x/ccv/provider/handler_test.go | 24 + x/ccv/provider/keeper/grpc_query.go | 14 + x/ccv/provider/keeper/hooks.go | 110 ++- x/ccv/provider/keeper/hooks_test.go | 151 +++- x/ccv/provider/keeper/keeper.go | 85 ++- x/ccv/provider/keeper/keeper_test.go | 95 +++ x/ccv/provider/keeper/key_assignment.go | 63 +- x/ccv/provider/keeper/key_assignment_test.go | 55 +- x/ccv/provider/keeper/msg_server.go | 8 +- x/ccv/provider/types/keys.go | 23 + x/ccv/provider/types/keys_test.go | 20 + x/ccv/provider/types/msg.go | 3 +- x/ccv/provider/types/query.pb.go | 688 ++++++++++++++++-- x/ccv/provider/types/query.pb.gw.go | 65 ++ x/ccv/types/expected_keepers.go | 5 + 27 files changed, 1433 insertions(+), 154 deletions(-) create mode 100644 .changelog/unreleased/features/provider/1339-check-key-assignment-in-use.md create mode 100644 .changelog/unreleased/state-breaking/provider/1339-check-key-assignment-in-use.md diff --git a/.changelog/unreleased/features/provider/1339-check-key-assignment-in-use.md b/.changelog/unreleased/features/provider/1339-check-key-assignment-in-use.md new file mode 100644 index 0000000000..9f274f7bb4 --- /dev/null +++ b/.changelog/unreleased/features/provider/1339-check-key-assignment-in-use.md @@ -0,0 +1,3 @@ +- Update how consumer-assigned keys are checked when a validator is + created on the provider. + ([\#1339](https://github.com/cosmos/interchain-security/pull/1339)) \ No newline at end of file diff --git a/.changelog/unreleased/state-breaking/provider/1339-check-key-assignment-in-use.md b/.changelog/unreleased/state-breaking/provider/1339-check-key-assignment-in-use.md new file mode 100644 index 0000000000..2890582ba8 --- /dev/null +++ b/.changelog/unreleased/state-breaking/provider/1339-check-key-assignment-in-use.md @@ -0,0 +1,3 @@ +- Change the states by adding a consumer key for each chain that is + not yet registered meaning for which the gov proposal has not passed. + ([\#1339](https://github.com/cosmos/interchain-security/pull/1339)) \ No newline at end of file diff --git a/app/provider/app.go b/app/provider/app.go index 497158449a..f0f5e9e61e 100644 --- a/app/provider/app.go +++ b/app/provider/app.go @@ -203,7 +203,7 @@ type App struct { // nolint: golint // different fee-pool from the consumer chain ConsumerKeeper DistrKeeper distrkeeper.Keeper - GovKeeper govkeeper.Keeper + GovKeeper *govkeeper.Keeper // Gov Keeper must be a pointer in the app, so we can SetRouter on it correctly CrisisKeeper crisiskeeper.Keeper UpgradeKeeper upgradekeeper.Keeper ParamsKeeper paramskeeper.Keeper @@ -403,6 +403,25 @@ func New( scopedIBCKeeper, ) + // create evidence keeper with router + app.EvidenceKeeper = *evidencekeeper.NewKeeper( + appCodec, + keys[evidencetypes.StoreKey], + app.StakingKeeper, + app.SlashingKeeper, + ) + + app.GovKeeper = govkeeper.NewKeeper( + appCodec, + keys[govtypes.StoreKey], + app.AccountKeeper, + app.BankKeeper, + app.StakingKeeper, + app.MsgServiceRouter(), + govtypes.DefaultConfig(), + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + app.ProviderKeeper = ibcproviderkeeper.NewKeeper( appCodec, keys[providertypes.StoreKey], @@ -417,6 +436,7 @@ func New( app.AccountKeeper, app.DistrKeeper, app.BankKeeper, + app.GovKeeper, authtypes.FeeCollectorName, ) @@ -431,22 +451,14 @@ func New( AddRoute(ibchost.RouterKey, ibcclient.NewClientProposalHandler(app.IBCKeeper.ClientKeeper)). AddRoute(providertypes.RouterKey, ibcprovider.NewProviderProposalHandler(app.ProviderKeeper)). AddRoute(ibcclienttypes.RouterKey, ibcclient.NewClientProposalHandler(app.IBCKeeper.ClientKeeper)) - govConfig := govtypes.DefaultConfig() - - app.GovKeeper = *govkeeper.NewKeeper( - appCodec, - keys[govtypes.StoreKey], - app.AccountKeeper, - app.BankKeeper, - app.StakingKeeper, - app.MsgServiceRouter(), - govConfig, - authtypes.NewModuleAddress(govtypes.ModuleName).String(), - ) // Set legacy router for backwards compatibility with gov v1beta1 app.GovKeeper.SetLegacyRouter(govRouter) + app.GovKeeper = app.GovKeeper.SetHooks( + govtypes.NewMultiGovHooks(app.ProviderKeeper.Hooks()), + ) + app.TransferKeeper = ibctransferkeeper.NewKeeper( appCodec, keys[ibctransfertypes.StoreKey], @@ -493,7 +505,7 @@ func New( bank.NewAppModule(appCodec, app.BankKeeper, app.AccountKeeper, app.GetSubspace(banktypes.ModuleName)), capability.NewAppModule(appCodec, *app.CapabilityKeeper, false), crisis.NewAppModule(&app.CrisisKeeper, skipGenesisInvariants, app.GetSubspace(crisistypes.ModuleName)), - gov.NewAppModule(appCodec, &app.GovKeeper, app.AccountKeeper, app.BankKeeper, app.GetSubspace(govtypes.ModuleName)), + gov.NewAppModule(appCodec, app.GovKeeper, app.AccountKeeper, app.BankKeeper, app.GetSubspace(govtypes.ModuleName)), mint.NewAppModule(appCodec, app.MintKeeper, app.AccountKeeper, nil, app.GetSubspace(minttypes.ModuleName)), slashing.NewAppModule(appCodec, app.SlashingKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper, app.GetSubspace(slashingtypes.ModuleName)), distr.NewAppModule(appCodec, app.DistrKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper, app.GetSubspace(distrtypes.ModuleName)), @@ -590,7 +602,7 @@ func New( auth.NewAppModule(appCodec, app.AccountKeeper, authsims.RandomGenesisAccounts, app.GetSubspace(authtypes.ModuleName)), bank.NewAppModule(appCodec, app.BankKeeper, app.AccountKeeper, app.GetSubspace(banktypes.ModuleName)), capability.NewAppModule(appCodec, *app.CapabilityKeeper, false), - gov.NewAppModule(appCodec, &app.GovKeeper, app.AccountKeeper, app.BankKeeper, app.GetSubspace(govtypes.ModuleName)), + gov.NewAppModule(appCodec, app.GovKeeper, app.AccountKeeper, app.BankKeeper, app.GetSubspace(govtypes.ModuleName)), mint.NewAppModule(appCodec, app.MintKeeper, app.AccountKeeper, nil, app.GetSubspace(minttypes.ModuleName)), staking.NewAppModule(appCodec, app.StakingKeeper, app.AccountKeeper, app.BankKeeper, app.GetSubspace(stakingtypes.ModuleName)), distr.NewAppModule(appCodec, app.DistrKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper, app.GetSubspace(distrtypes.ModuleName)), diff --git a/go.sum b/go.sum index d4bce198d6..9619db64ae 100644 --- a/go.sum +++ b/go.sum @@ -1056,6 +1056,7 @@ golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= +golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1350,6 +1351,7 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/proto/interchain_security/ccv/provider/v1/query.proto b/proto/interchain_security/ccv/provider/v1/query.proto index e648e2be35..05071a354c 100644 --- a/proto/interchain_security/ccv/provider/v1/query.proto +++ b/proto/interchain_security/ccv/provider/v1/query.proto @@ -73,6 +73,15 @@ service Query { option (google.api.http).get = "/interchain_security/ccv/provider/registered_consumer_reward_denoms"; } + + // QueryProposedConsumerChainIDs returns the chain IDs of the proposed consumer chain addition proposals + // that are still in the voting period + rpc QueryProposedConsumerChainIDs( + QueryProposedChainIDsRequest) + returns (QueryProposedChainIDsResponse) { + option (google.api.http).get = + "/interchain_security/ccv/provider/proposed_consumer_chains"; + } } message QueryConsumerGenesisRequest { string chain_id = 1; } @@ -150,3 +159,15 @@ message QueryRegisteredConsumerRewardDenomsRequest {} message QueryRegisteredConsumerRewardDenomsResponse { repeated string denoms = 1; } + +message QueryProposedChainIDsRequest {} + +message QueryProposedChainIDsResponse { + repeated ProposedChain proposedChains = 1 + [ (gogoproto.nullable) = false ]; +} + +message ProposedChain { + string chainID = 1; + uint64 proposalID = 2; +} diff --git a/tests/e2e/config.go b/tests/e2e/config.go index 6eacdfe3c9..311ee67c89 100644 --- a/tests/e2e/config.go +++ b/tests/e2e/config.go @@ -505,7 +505,7 @@ func (s *TestConfig) SetRelayerConfig(useRly bool) { } // validateStringLiterals enforces that configs follow the constraints -// necessary to to execute the tests +// necessary to execute the tests // // Note: Network interfaces (name of virtual ethernet interfaces for ip link) // within the container will be named as "$CHAIN_ID-$VAL_ID-out" etc. @@ -513,7 +513,6 @@ func (s *TestConfig) SetRelayerConfig(useRly bool) { // used as a validatorID or chainID needs to be 5 char or less. func (s *TestConfig) validateStringLiterals() { for valID, valConfig := range s.validatorConfigs { - if len(valID) > 5 { panic("validator id string literal must be 5 char or less") } diff --git a/tests/e2e/state.go b/tests/e2e/state.go index 1671236104..563ce1ff30 100644 --- a/tests/e2e/state.go +++ b/tests/e2e/state.go @@ -20,6 +20,7 @@ type State map[ChainID]ChainState type ChainState struct { ValBalances *map[ValidatorID]uint Proposals *map[uint]Proposal + ProposedConsumerChains *[]string ValPowers *map[ValidatorID]uint StakedTokens *map[ValidatorID]uint Params *[]Param @@ -123,6 +124,11 @@ func (tr TestConfig) getChainState(chain ChainID, modelState ChainState) ChainSt chainState.Proposals = &proposals } + if modelState.ProposedConsumerChains != nil { + proposedConsumerChains := tr.getProposedConsumerChains(chain) + chainState.ProposedConsumerChains = &proposedConsumerChains + } + if modelState.ValPowers != nil { tr.waitBlocks(chain, 1, 10*time.Second) powers := tr.getValPowers(chain, *modelState.ValPowers) @@ -836,6 +842,28 @@ func (tc TestConfig) getTrustedHeight( return clienttypes.Height{RevisionHeight: uint64(revHeight), RevisionNumber: uint64(revNumber)} } +func (tr TestConfig) getProposedConsumerChains(chain ChainID) []string { + tr.waitBlocks(chain, 1, 10*time.Second) + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + bz, err := exec.Command("docker", "exec", tr.containerConfig.InstanceName, tr.chainConfigs[chain].BinaryName, + "query", "provider", "list-proposed-consumer-chains", + `--node`, tr.getQueryNode(chain), + `-o`, `json`, + ).CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(bz)) + } + + arr := gjson.Get(string(bz), "proposedChains").Array() + chains := []string{} + for _, c := range arr { + cid := c.Get("chainID").String() + chains = append(chains, cid) + } + + return chains +} + func uintPtr(i uint) *uint { return &i } diff --git a/tests/e2e/steps_start_chains.go b/tests/e2e/steps_start_chains.go index ace3d6c255..58a5f75162 100644 --- a/tests/e2e/steps_start_chains.go +++ b/tests/e2e/steps_start_chains.go @@ -54,6 +54,7 @@ func stepsStartConsumerChain(consumerName string, proposalIndex, chainIndex uint Status: "PROPOSAL_STATUS_VOTING_PERIOD", }, }, + ProposedConsumerChains: &[]string{consumerName}, }, }, }, @@ -163,6 +164,7 @@ func stepsStartConsumerChain(consumerName string, proposalIndex, chainIndex uint ValidatorID("bob"): 9500000000, ValidatorID("carol"): 9500000000, }, + ProposedConsumerChains: &[]string{}, }, ChainID(consumerName): ChainState{ ValBalances: &map[ValidatorID]uint{ diff --git a/tests/integration/setup.go b/tests/integration/setup.go index eee8fb1a22..4628ca3111 100644 --- a/tests/integration/setup.go +++ b/tests/integration/setup.go @@ -24,6 +24,7 @@ import ( testutil "github.com/cosmos/interchain-security/v3/testutil/integration" "github.com/cosmos/interchain-security/v3/testutil/simibc" consumertypes "github.com/cosmos/interchain-security/v3/x/ccv/consumer/types" + "github.com/cosmos/interchain-security/v3/x/ccv/provider/types" ccv "github.com/cosmos/interchain-security/v3/x/ccv/types" ) @@ -129,6 +130,9 @@ func (suite *CCVTestSuite) SetupTest() { providerKeeper := suite.providerApp.GetProviderKeeper() // re-assign all validator keys for the first consumer chain + providerKeeper.SetPendingConsumerAdditionProp(suite.providerCtx(), &types.ConsumerAdditionProposal{ + ChainId: icstestingutils.FirstConsumerChainID, + }) preProposalKeyAssignment(suite, icstestingutils.FirstConsumerChainID) // start consumer chains diff --git a/testutil/keeper/mocks.go b/testutil/keeper/mocks.go index b00678a50a..02b4749e43 100644 --- a/testutil/keeper/mocks.go +++ b/testutil/keeper/mocks.go @@ -14,6 +14,7 @@ import ( types0 "github.com/cosmos/cosmos-sdk/types" types1 "github.com/cosmos/cosmos-sdk/x/auth/types" types2 "github.com/cosmos/cosmos-sdk/x/capability/types" + v1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" types3 "github.com/cosmos/cosmos-sdk/x/slashing/types" types4 "github.com/cosmos/cosmos-sdk/x/staking/types" types5 "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" @@ -1166,3 +1167,41 @@ func (mr *MockScopedKeeperMockRecorder) GetCapability(ctx, name interface{}) *go mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCapability", reflect.TypeOf((*MockScopedKeeper)(nil).GetCapability), ctx, name) } + +// MockGovKeeper is a mock of GovKeeper interface. +type MockGovKeeper struct { + ctrl *gomock.Controller + recorder *MockGovKeeperMockRecorder +} + +// MockGovKeeperMockRecorder is the mock recorder for MockGovKeeper. +type MockGovKeeperMockRecorder struct { + mock *MockGovKeeper +} + +// NewMockGovKeeper creates a new mock instance. +func NewMockGovKeeper(ctrl *gomock.Controller) *MockGovKeeper { + mock := &MockGovKeeper{ctrl: ctrl} + mock.recorder = &MockGovKeeperMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockGovKeeper) EXPECT() *MockGovKeeperMockRecorder { + return m.recorder +} + +// GetProposal mocks base method. +func (m *MockGovKeeper) GetProposal(ctx types0.Context, proposalID uint64) (v1.Proposal, bool) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetProposal", ctx, proposalID) + ret0, _ := ret[0].(v1.Proposal) + ret1, _ := ret[1].(bool) + return ret0, ret1 +} + +// GetProposal indicates an expected call of GetProposal. +func (mr *MockGovKeeperMockRecorder) GetProposal(ctx, proposalID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProposal", reflect.TypeOf((*MockGovKeeper)(nil).GetProposal), ctx, proposalID) +} diff --git a/testutil/keeper/unit_test_helpers.go b/testutil/keeper/unit_test_helpers.go index 557be11123..d114a57651 100644 --- a/testutil/keeper/unit_test_helpers.go +++ b/testutil/keeper/unit_test_helpers.go @@ -88,6 +88,7 @@ type MockedKeepers struct { *MockIBCTransferKeeper *MockIBCCoreKeeper *MockDistributionKeeper + *MockGovKeeper } // NewMockedKeepers instantiates a struct with pointers to properly instantiated mocked keepers. @@ -105,6 +106,7 @@ func NewMockedKeepers(ctrl *gomock.Controller) MockedKeepers { MockIBCTransferKeeper: NewMockIBCTransferKeeper(ctrl), MockIBCCoreKeeper: NewMockIBCCoreKeeper(ctrl), MockDistributionKeeper: NewMockDistributionKeeper(ctrl), + MockGovKeeper: NewMockGovKeeper(ctrl), } } @@ -124,6 +126,7 @@ func NewInMemProviderKeeper(params InMemKeeperParams, mocks MockedKeepers) provi mocks.MockAccountKeeper, mocks.MockDistributionKeeper, mocks.MockBankKeeper, + mocks.MockGovKeeper, authtypes.FeeCollectorName, ) } diff --git a/x/ccv/provider/client/cli/query.go b/x/ccv/provider/client/cli/query.go index 80746ff5d2..2b1d0e1dee 100644 --- a/x/ccv/provider/client/cli/query.go +++ b/x/ccv/provider/client/cli/query.go @@ -32,6 +32,7 @@ func NewQueryCmd() *cobra.Command { cmd.AddCommand(CmdProviderValidatorKey()) cmd.AddCommand(CmdThrottleState()) cmd.AddCommand(CmdRegisteredConsumerRewardDenoms()) + cmd.AddCommand(CmdProposedConsumerChains()) return cmd } @@ -92,6 +93,33 @@ func CmdConsumerChains() *cobra.Command { return cmd } +func CmdProposedConsumerChains() *cobra.Command { + cmd := &cobra.Command{ + Use: "list-proposed-consumer-chains", + Short: "Query chainIDs in consumer addition proposal before voting finishes", + Args: cobra.ExactArgs(0), + RunE: func(cmd *cobra.Command, args []string) (err error) { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := types.NewQueryClient(clientCtx) + + req := &types.QueryProposedChainIDsRequest{} + res, err := queryClient.QueryProposedConsumerChainIDs(cmd.Context(), req) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + func CmdConsumerStartProposals() *cobra.Command { cmd := &cobra.Command{ Use: "list-start-proposals", diff --git a/x/ccv/provider/handler_test.go b/x/ccv/provider/handler_test.go index e4835cf14a..3ff2d5a26a 100644 --- a/x/ccv/provider/handler_test.go +++ b/x/ccv/provider/handler_test.go @@ -51,6 +51,9 @@ func TestAssignConsensusKeyForConsumerChain(t *testing.T) { setup: func(ctx sdk.Context, k keeper.Keeper, mocks testkeeper.MockedKeepers, ) { + k.SetPendingConsumerAdditionProp(ctx, &providertypes.ConsumerAdditionProposal{ + ChainId: "chainid", + }) gomock.InOrder( mocks.MockStakingKeeper.EXPECT().GetValidator( ctx, providerCryptoId.SDKValOpAddress(), @@ -64,11 +67,29 @@ func TestAssignConsensusKeyForConsumerChain(t *testing.T) { expError: false, chainID: "chainid", }, + { + name: "fail: chain ID not registered", + setup: func(ctx sdk.Context, + k keeper.Keeper, mocks testkeeper.MockedKeepers, + ) { + gomock.InOrder( + mocks.MockStakingKeeper.EXPECT().GetValidator( + ctx, providerCryptoId.SDKValOpAddress(), + // Return a valid validator, found! + ).Return(providerCryptoId.SDKStakingValidator(), true).Times(1), + ) + }, + expError: true, + chainID: "chainid", + }, { name: "fail: missing validator", setup: func(ctx sdk.Context, k keeper.Keeper, mocks testkeeper.MockedKeepers, ) { + k.SetPendingConsumerAdditionProp(ctx, &providertypes.ConsumerAdditionProposal{ + ChainId: "chainid", + }) gomock.InOrder( mocks.MockStakingKeeper.EXPECT().GetValidator( ctx, providerCryptoId.SDKValOpAddress(), @@ -84,6 +105,9 @@ func TestAssignConsensusKeyForConsumerChain(t *testing.T) { setup: func(ctx sdk.Context, k keeper.Keeper, mocks testkeeper.MockedKeepers, ) { + k.SetPendingConsumerAdditionProp(ctx, &providertypes.ConsumerAdditionProposal{ + ChainId: "chainid", + }) // Use the consumer key already k.SetValidatorByConsumerAddr(ctx, "chainid", consumerConsAddr, providerConsAddr) diff --git a/x/ccv/provider/keeper/grpc_query.go b/x/ccv/provider/keeper/grpc_query.go index 881a6b1a98..d1f111e38e 100644 --- a/x/ccv/provider/keeper/grpc_query.go +++ b/x/ccv/provider/keeper/grpc_query.go @@ -168,3 +168,17 @@ func (k Keeper) QueryRegisteredConsumerRewardDenoms(goCtx context.Context, req * Denoms: denoms, }, nil } + +func (k Keeper) QueryProposedConsumerChainIDs(goCtx context.Context, req *types.QueryProposedChainIDsRequest) (*types.QueryProposedChainIDsResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + ctx := sdk.UnwrapSDKContext(goCtx) + + chains := k.GetAllProposedConsumerChainIDs(ctx) + + return &types.QueryProposedChainIDsResponse{ + ProposedChains: chains, + }, nil +} diff --git a/x/ccv/provider/keeper/hooks.go b/x/ccv/provider/keeper/hooks.go index da2daa7512..be7e5947a1 100644 --- a/x/ccv/provider/keeper/hooks.go +++ b/x/ccv/provider/keeper/hooks.go @@ -1,7 +1,11 @@ package keeper import ( + "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkgov "github.com/cosmos/cosmos-sdk/x/gov/types" + v1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" providertypes "github.com/cosmos/interchain-security/v3/x/ccv/provider/types" @@ -13,13 +17,20 @@ type Hooks struct { k *Keeper } -var _ stakingtypes.StakingHooks = Hooks{} +var ( + _ stakingtypes.StakingHooks = Hooks{} + _ sdkgov.GovHooks = Hooks{} +) // Returns new provider hooks func (k *Keeper) Hooks() Hooks { return Hooks{k} } +// +// staking hooks +// + // This stores a record of each unbonding op from staking, allowing us to track which consumer chains have unbonded func (h Hooks) AfterUnbondingInitiated(ctx sdk.Context, id uint64) error { var consumerChainIDS []string @@ -80,38 +91,8 @@ func (h Hooks) AfterUnbondingInitiated(ctx sdk.Context, id uint64) error { return nil } -// ValidatorConsensusKeyInUse is called when a new validator is created -// in the x/staking module of cosmos-sdk. In case it panics, the TX aborts -// and thus, the validator is not created. See AfterValidatorCreated hook. -func ValidatorConsensusKeyInUse(k *Keeper, ctx sdk.Context, valAddr sdk.ValAddress) bool { - // Get the validator being added in the staking module. - val, found := k.stakingKeeper.GetValidator(ctx, valAddr) - if !found { - // Abort TX, do NOT allow validator to be created - panic("did not find newly created validator in staking module") - } - - // Get the consensus address of the validator being added - consensusAddr, err := val.GetConsAddr() - if err != nil { - // Abort TX, do NOT allow validator to be created - panic("could not get validator cons addr ") - } - - inUse := false - - for _, validatorConsumerAddrs := range k.GetAllValidatorsByConsumerAddr(ctx, nil) { - if sdk.ConsAddress(validatorConsumerAddrs.ConsumerAddr).Equals(consensusAddr) { - inUse = true - break - } - } - - return inUse -} - func (h Hooks) AfterValidatorCreated(ctx sdk.Context, valAddr sdk.ValAddress) error { - if ValidatorConsensusKeyInUse(h.k, ctx, valAddr) { + if h.k.ValidatorConsensusKeyInUse(ctx, valAddr) { // Abort TX, do NOT allow validator to be created panic("cannot create a validator with a consensus key that is already in use or was recently in use as an assigned consumer chain key") } @@ -168,6 +149,71 @@ func (h Hooks) BeforeDelegationRemoved(_ sdk.Context, _ sdk.AccAddress, _ sdk.Va return nil } +// +// gov hooks +// + +// AfterProposalSubmission - call hook if registered +// After a consumerAddition proposal submission, a record is created +// that maps the proposal ID to the consumer chain ID. +func (h Hooks) AfterProposalSubmission(ctx sdk.Context, proposalID uint64) { + if p, ok := h.GetConsumerAdditionLegacyPropFromProp(ctx, proposalID); ok { + h.k.SetProposedConsumerChain(ctx, p.ChainId, proposalID) + } +} + +// AfterProposalVotingPeriodEnded - call hook if registered +// After proposal voting ends, the consumer chainID in store is deleted. +// When a consumerAddition proposal passes, the consumer chainID is available in providerKeeper.GetAllPendingConsumerAdditionProps +// or providerKeeper.GetAllConsumerChains(ctx). +func (h Hooks) AfterProposalVotingPeriodEnded(ctx sdk.Context, proposalID uint64) { + if _, ok := h.GetConsumerAdditionLegacyPropFromProp(ctx, proposalID); ok { + h.k.DeleteProposedConsumerChainInStore(ctx, proposalID) + } +} + +func (h Hooks) AfterProposalDeposit(ctx sdk.Context, proposalID uint64, depositorAddr sdk.AccAddress) { +} + +func (h Hooks) AfterProposalVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress) { +} + +func (h Hooks) AfterProposalFailedMinDeposit(ctx sdk.Context, proposalID uint64) { +} + +// GetConsumerAdditionLegacyPropFromProp extracts a consumer addition legacy proposal from +// the proposal with the given ID +func (h Hooks) GetConsumerAdditionLegacyPropFromProp( + ctx sdk.Context, + proposalID uint64, +) (providertypes.ConsumerAdditionProposal, bool) { + p, ok := h.k.govKeeper.GetProposal(ctx, proposalID) + if !ok { + panic(fmt.Errorf("failed to get proposal %d from store", proposalID)) + } + + // Iterate over the messages in the proposal + // Note that it's assumed that at most ONE message can contain a consumer addition proposal + for _, msg := range p.GetMessages() { + sdkMsg, isLegacyProposal := msg.GetCachedValue().(*v1.MsgExecLegacyContent) + if !isLegacyProposal { + continue + } + + content, err := v1.LegacyContentFromMessage(sdkMsg) + if err != nil { + panic(fmt.Errorf("failed to get legacy proposal %d from prop message", proposalID)) + } + + // returns if legacy prop is of ConsumerAddition proposal type + prop, ok := content.(*providertypes.ConsumerAdditionProposal) + if ok { + return *prop, true + } + } + return providertypes.ConsumerAdditionProposal{}, false +} + func (h Hooks) BeforeTokenizeShareRecordRemoved(_ sdk.Context, _ uint64) error { return nil } diff --git a/x/ccv/provider/keeper/hooks_test.go b/x/ccv/provider/keeper/hooks_test.go index 83dbfe9622..47175eb907 100644 --- a/x/ccv/provider/keeper/hooks_test.go +++ b/x/ccv/provider/keeper/hooks_test.go @@ -2,10 +2,18 @@ package keeper_test import ( "testing" + "time" "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" + + "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + v1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" + "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" cryptotestutil "github.com/cosmos/interchain-security/v3/testutil/crypto" testkeeper "github.com/cosmos/interchain-security/v3/testutil/keeper" @@ -38,6 +46,7 @@ func TestValidatorConsensusKeyInUse(t *testing.T) { newValidator.ConsumerConsAddress(), anotherValidator0.ProviderConsAddress(), ) + k.SetConsumerClientId(ctx, "chainid", "clientID") }, expect: true, }, @@ -50,16 +59,20 @@ func TestValidatorConsensusKeyInUse(t *testing.T) { newValidator.ConsumerConsAddress(), anotherValidator0.ProviderConsAddress(), ) + k.SetConsumerClientId(ctx, "chainid0", "clientID0") + k.SetValidatorByConsumerAddr(ctx, "chainid1", anotherValidator1.ConsumerConsAddress(), anotherValidator1.ProviderConsAddress(), ) + k.SetConsumerClientId(ctx, "chainid1", "clientID1") }, expect: true, }, } for _, tt := range tests { - k, ctx, _, mocks := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + k, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() gomock.InOrder( mocks.MockStakingKeeper.EXPECT().GetValidator(ctx, @@ -70,9 +83,143 @@ func TestValidatorConsensusKeyInUse(t *testing.T) { tt.setup(ctx, k) t.Run(tt.name, func(t *testing.T) { - if actual := providerkeeper.ValidatorConsensusKeyInUse(&k, ctx, newValidator.SDKStakingValidator().GetOperator()); actual != tt.expect { + if actual := k.ValidatorConsensusKeyInUse(ctx, newValidator.SDKStakingValidator().GetOperator()); actual != tt.expect { t.Errorf("validatorConsensusKeyInUse() = %v, want %v", actual, tt.expect) } }) } } + +func TestAfterPropSubmissionAndVotingPeriodEnded(t *testing.T) { + acct := cryptotestutil.NewCryptoIdentityFromIntSeed(0) + + propMsg, err := v1.NewLegacyContent( + testkeeper.GetTestConsumerAdditionProp(), + authtypes.NewModuleAddress("gov").String(), + ) + require.NoError(t, err) + + prop, err := v1.NewProposal( + []sdk.Msg{propMsg}, + 0, + time.Now(), + time.Now(), + "", + "", + "", + sdk.AccAddress(acct.SDKValOpAddress()), + ) + require.NoError(t, err) + + k, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + // pass the prop to the mocked gov keeper, + // which is called by both the AfterProposalVotingPeriodEnded and + // AfterProposalSubmission gov hooks + gomock.InOrder( + mocks.MockGovKeeper.EXPECT().GetProposal(ctx, prop.Id).Return(prop, true).Times(2), + ) + + k.Hooks().AfterProposalSubmission(ctx, prop.Id) + // verify that the proposal ID is created + require.NotEmpty(t, k.GetProposedConsumerChain(ctx, prop.Id)) + + k.Hooks().AfterProposalVotingPeriodEnded(ctx, prop.Id) + // verify that the proposal ID is deleted + require.Empty(t, k.GetProposedConsumerChain(ctx, prop.Id)) +} + +func TestGetConsumerAdditionLegacyPropFromProp(t *testing.T) { + acct := cryptotestutil.NewCryptoIdentityFromIntSeed(0) + anotherAcct := cryptotestutil.NewCryptoIdentityFromIntSeed(1) + + // create a dummy bank send message + dummyMsg := &banktypes.MsgSend{ + FromAddress: sdk.AccAddress(acct.SDKValOpAddress()).String(), + ToAddress: sdk.AccAddress(anotherAcct.SDKValOpAddress()).String(), + Amount: sdk.NewCoins(sdk.NewCoin("stake", math.OneInt())), + } + + textProp, err := v1.NewLegacyContent( + v1beta1.NewTextProposal("a title", "a legacy text prop"), + authtypes.NewModuleAddress("gov").String(), + ) + require.NoError(t, err) + + consuProp, err := v1.NewLegacyContent( + testkeeper.GetTestConsumerAdditionProp(), + authtypes.NewModuleAddress("gov").String(), + ) + require.NoError(t, err) + + testCases := map[string]struct { + propMsg sdk.Msg + // setup func(sdk.Context, k providerkeeper, proposalID uint64) + expPanic bool + expConsuAddProp bool + }{ + "prop not found": { + propMsg: nil, + expPanic: true, + }, + "msgs in prop contain no legacy props": { + propMsg: dummyMsg, + }, + "msgs contain a legacy prop but not of ConsumerAdditionProposal type": { + propMsg: textProp, + }, + "msgs contain an invalid legacy prop": { + propMsg: &v1.MsgExecLegacyContent{}, + expPanic: true, + }, + "msg contains a prop of ConsumerAdditionProposal type - hook should create a new proposed chain": { + propMsg: consuProp, + expConsuAddProp: true, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + k, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + var ( + prop v1.Proposal + propFound bool + ) + + if tc.propMsg != nil { + propFound = true + prop, err = v1.NewProposal( + []sdk.Msg{tc.propMsg}, + 0, + time.Now(), + time.Now(), + "", + "", + "", + sdk.AccAddress(acct.SDKValOpAddress()), + ) + require.NoError(t, err) + } + + gomock.InOrder( + mocks.MockGovKeeper.EXPECT().GetProposal(ctx, prop.Id).Return(prop, propFound), + ) + + if tc.expPanic { + defer func() { + // fail test if not panic was recovered + if r := recover(); r == nil { + require.Fail(t, r.(string)) + } + }() + } + + // retrieve consumer addition proposal + _, ok := k.Hooks().GetConsumerAdditionLegacyPropFromProp(ctx, prop.Id) + require.Equal(t, tc.expConsuAddProp, ok) + }) + } +} diff --git a/x/ccv/provider/keeper/keeper.go b/x/ccv/provider/keeper/keeper.go index 0148354b93..cf2ba795d9 100644 --- a/x/ccv/provider/keeper/keeper.go +++ b/x/ccv/provider/keeper/keeper.go @@ -43,6 +43,7 @@ type Keeper struct { slashingKeeper ccv.SlashingKeeper distributionKeeper ccv.DistributionKeeper bankKeeper ccv.BankKeeper + govKeeper ccv.GovKeeper feeCollectorName string } @@ -52,9 +53,9 @@ func NewKeeper( channelKeeper ccv.ChannelKeeper, portKeeper ccv.PortKeeper, connectionKeeper ccv.ConnectionKeeper, clientKeeper ccv.ClientKeeper, stakingKeeper ccv.StakingKeeper, slashingKeeper ccv.SlashingKeeper, - accountKeeper ccv.AccountKeeper, distributionKeeper ccv.DistributionKeeper, - bankKeeper ccv.BankKeeper, - feeCollectorName string, + accountKeeper ccv.AccountKeeper, + distributionKeeper ccv.DistributionKeeper, bankKeeper ccv.BankKeeper, + govKeeper ccv.GovKeeper, feeCollectorName string, ) Keeper { // set KeyTable if it has not already been set if !paramSpace.HasKeyTable() { @@ -75,6 +76,7 @@ func NewKeeper( accountKeeper: accountKeeper, distributionKeeper: distributionKeeper, bankKeeper: bankKeeper, + govKeeper: govKeeper, feeCollectorName: feeCollectorName, } @@ -92,8 +94,8 @@ func (k *Keeper) SetParamSpace(ctx sdk.Context, ps paramtypes.Subspace) { // non-nil values for all its fields. Otherwise this method will panic. func (k Keeper) mustValidateFields() { // Ensures no fields are missed in this validation - if reflect.ValueOf(k).NumField() != 14 { - panic("number of fields in provider keeper is not 14") + if reflect.ValueOf(k).NumField() != 15 { + panic("number of fields in provider keeper is not 15") } ccv.PanicIfZeroOrNil(k.cdc, "cdc") // 1 @@ -109,7 +111,8 @@ func (k Keeper) mustValidateFields() { ccv.PanicIfZeroOrNil(k.slashingKeeper, "slashingKeeper") // 11 ccv.PanicIfZeroOrNil(k.distributionKeeper, "distributionKeeper") // 12 ccv.PanicIfZeroOrNil(k.bankKeeper, "bankKeeper") // 13 - ccv.PanicIfZeroOrNil(k.feeCollectorName, "feeCollectorName") // 14 + ccv.PanicIfZeroOrNil(k.govKeeper, "govKeeper") // 14 + ccv.PanicIfZeroOrNil(k.feeCollectorName, "feeCollectorName") // 15 } // Logger returns a module-specific logger. @@ -175,6 +178,60 @@ func (k Keeper) DeleteChainToChannel(ctx sdk.Context, chainID string) { store.Delete(types.ChainToChannelKey(chainID)) } +// SetProposedConsumerChain stores a consumer chainId corresponding to a submitted consumer addition proposal +// This consumer chainId is deleted once the voting period for the proposal ends. +func (k Keeper) SetProposedConsumerChain(ctx sdk.Context, chainID string, proposalID uint64) { + store := ctx.KVStore(k.storeKey) + store.Set(types.ProposedConsumerChainKey(proposalID), []byte(chainID)) +} + +// GetProposedConsumerChain returns the proposed chainID for the given consumerAddition proposal ID. +func (k Keeper) GetProposedConsumerChain(ctx sdk.Context, proposalID uint64) string { + store := ctx.KVStore(k.storeKey) + return string(store.Get(types.ProposedConsumerChainKey(proposalID))) +} + +// DeleteProposedConsumerChainInStore deletes the consumer chainID from store +// which is in gov consumerAddition proposal +func (k Keeper) DeleteProposedConsumerChainInStore(ctx sdk.Context, proposalID uint64) { + store := ctx.KVStore(k.storeKey) + store.Delete(types.ProposedConsumerChainKey(proposalID)) +} + +// GetAllProposedConsumerChainIDs returns the proposed chainID of all gov consumerAddition proposals that are still in the voting period. +func (k Keeper) GetAllProposedConsumerChainIDs(ctx sdk.Context) []types.ProposedChain { + store := ctx.KVStore(k.storeKey) + iterator := sdk.KVStorePrefixIterator(store, []byte{types.ProposedConsumerChainByteKey}) + defer iterator.Close() + + proposedChains := []types.ProposedChain{} + for ; iterator.Valid(); iterator.Next() { + proposalID, err := types.ParseProposedConsumerChainKey(types.ProposedConsumerChainByteKey, iterator.Key()) + if err != nil { + panic(fmt.Errorf("proposed chains cannot be parsed: %w", err)) + } + + proposedChains = append(proposedChains, types.ProposedChain{ + ChainID: string(iterator.Value()), + ProposalID: proposalID, + }) + + } + + return proposedChains +} + +// GetAllPendingConsumerChainIDs gets pending consumer chains have not reach spawn time +func (k Keeper) GetAllPendingConsumerChainIDs(ctx sdk.Context) []string { + chainIDs := []string{} + props := k.GetAllPendingConsumerAdditionProps(ctx) + for _, prop := range props { + chainIDs = append(chainIDs, prop.ChainId) + } + + return chainIDs +} + // GetAllConsumerChains gets all of the consumer chains, for which the provider module // created IBC clients. Consumer chains with created clients are also referred to as registered. // @@ -1063,3 +1120,19 @@ func (k Keeper) GetSlashLog( func (k Keeper) BondDenom(ctx sdk.Context) string { return k.stakingKeeper.BondDenom(ctx) } + +func (k Keeper) GetAllRegisteredAndProposedChainIDs(ctx sdk.Context) []string { + allConsumerChains := []string{} + consumerChains := k.GetAllConsumerChains(ctx) + for _, consumerChain := range consumerChains { + allConsumerChains = append(allConsumerChains, consumerChain.ChainId) + } + proposedChains := k.GetAllProposedConsumerChainIDs(ctx) + for _, proposedChain := range proposedChains { + allConsumerChains = append(allConsumerChains, proposedChain.ChainID) + } + pendingChainIDs := k.GetAllPendingConsumerChainIDs(ctx) + allConsumerChains = append(allConsumerChains, pendingChainIDs...) + + return allConsumerChains +} diff --git a/x/ccv/provider/keeper/keeper_test.go b/x/ccv/provider/keeper/keeper_test.go index be3ef4001c..14c86fe775 100644 --- a/x/ccv/provider/keeper/keeper_test.go +++ b/x/ccv/provider/keeper/keeper_test.go @@ -533,3 +533,98 @@ func TestSetSlashLog(t *testing.T) { require.True(t, providerKeeper.GetSlashLog(ctx, addrWithDoubleSigns)) require.False(t, providerKeeper.GetSlashLog(ctx, addrWithoutDoubleSigns)) } + +func TestSetProposedConsumerChains(t *testing.T) { + providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + tests := []struct { + chainID string + proposalID uint64 + }{ + {chainID: "1", proposalID: 1}, + {chainID: "some other ID", proposalID: 12}, + {chainID: "some other other chain ID", proposalID: 123}, + {chainID: "", proposalID: 1234}, + } + + for _, test := range tests { + providerKeeper.SetProposedConsumerChain(ctx, test.chainID, test.proposalID) + cID := providerKeeper.GetProposedConsumerChain(ctx, test.proposalID) + require.Equal(t, cID, test.chainID) + } +} + +func TestDeleteProposedConsumerChainInStore(t *testing.T) { + providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + + tests := []struct { + chainID string + proposalID uint64 + deleteProposalID uint64 + ok bool + }{ + {chainID: "1", proposalID: 1, deleteProposalID: 1, ok: true}, + {chainID: "", proposalID: 12, deleteProposalID: 12, ok: true}, + {chainID: "1", proposalID: 0, deleteProposalID: 1, ok: false}, + } + for _, test := range tests { + providerKeeper.SetProposedConsumerChain(ctx, test.chainID, test.proposalID) + providerKeeper.DeleteProposedConsumerChainInStore(ctx, test.deleteProposalID) + cID := providerKeeper.GetProposedConsumerChain(ctx, test.proposalID) + if test.ok { + require.Equal(t, cID, "") + } else { + require.Equal(t, cID, test.chainID) + } + } +} + +func TestGetAllProposedConsumerChainIDs(t *testing.T) { + providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) + defer ctrl.Finish() + tests := [][]types.ProposedChain{ + {}, + { + { + ChainID: "1", + ProposalID: 1, + }, + }, + { + { + ChainID: "1", + ProposalID: 1, + }, + { + ChainID: "2", + ProposalID: 2, + }, + { + ChainID: "", + ProposalID: 3, + }, + }, + } + + for _, test := range tests { + for _, tc := range test { + providerKeeper.SetProposedConsumerChain(ctx, tc.ChainID, tc.ProposalID) + } + + chains := providerKeeper.GetAllProposedConsumerChainIDs(ctx) + + sort.Slice(chains, func(i, j int) bool { + return chains[i].ProposalID < chains[j].ProposalID + }) + sort.Slice(test, func(i, j int) bool { + return test[i].ProposalID < test[j].ProposalID + }) + require.Equal(t, chains, test) + + for _, tc := range test { + providerKeeper.DeleteProposedConsumerChainInStore(ctx, tc.ProposalID) + } + } +} diff --git a/x/ccv/provider/keeper/key_assignment.go b/x/ccv/provider/keeper/key_assignment.go index d440848bbf..46447790df 100644 --- a/x/ccv/provider/keeper/key_assignment.go +++ b/x/ccv/provider/keeper/key_assignment.go @@ -370,13 +370,22 @@ func (k Keeper) DeleteConsumerAddrsToPrune(ctx sdk.Context, chainID string, vscI } // AssignConsumerKey assigns the consumerKey to the validator with providerAddr -// on the consumer chain with ID chainID +// on the consumer chain with ID chainID, if it is either registered or currently +// voted on in a ConsumerAddition governance proposal func (k Keeper) AssignConsumerKey( ctx sdk.Context, chainID string, validator stakingtypes.Validator, consumerKey tmprotocrypto.PublicKey, ) error { + // check that the consumer chain is either registered or that + // a ConsumerAdditionProposal was submitted. + if !k.IsConsumerProposedOrRegistered(ctx, chainID) { + return errorsmod.Wrapf( + types.ErrUnknownConsumerChainId, chainID, + ) + } + consAddrTmp, err := ccvtypes.TMCryptoPublicKeyToConsAddr(consumerKey) if err != nil { return err @@ -390,15 +399,15 @@ func (k Keeper) AssignConsumerKey( providerAddr := types.NewProviderConsAddress(consAddrTmp) if existingVal, found := k.stakingKeeper.GetValidatorByConsAddr(ctx, consumerAddr.ToSdkConsAddr()); found { - // If there is a validator using the consumer key to validate on the provider - // we prevent assigning the consumer key, unless the validator is assigning validator. - // This ensures that a validator joining the active set who has not explicitly assigned - // a consumer key, will be able to use their provider key as consumer key (as per default). + // If there is already a different validator using the consumer key to validate on the provider + // we prevent assigning the consumer key. if existingVal.OperatorAddress != validator.OperatorAddress { return errorsmod.Wrapf( types.ErrConsumerKeyInUse, "a different validator already uses the consumer key", ) } + // We prevent a validator from assigning the default provider key as a consumer key + // if it has not already assigned a different consumer key _, found := k.GetValidatorConsumerPubKey(ctx, chainID, providerAddr) if !found { return errorsmod.Wrapf( @@ -629,3 +638,47 @@ func (k Keeper) DeleteKeyAssignments(ctx sdk.Context, chainID string) { k.DeleteConsumerAddrsToPrune(ctx, chainID, consumerAddrsToPrune.VscId) } } + +// IsConsumerProposedOrRegistered checks if a consumer chain is either registered, meaning either already running +// or will run soon, or proposed its ConsumerAdditionProposal was submitted but the chain was not yet added to ICS yet. +func (k Keeper) IsConsumerProposedOrRegistered(ctx sdk.Context, chainID string) bool { + allConsumerChains := k.GetAllRegisteredAndProposedChainIDs(ctx) + for _, c := range allConsumerChains { + if c == chainID { + return true + } + } + + return false +} + +// ValidatorConsensusKeyInUse checks if the given consensus key is already +// used by validator in a consumer chain. +// Note that this method is called when a new validator is created in the x/staking module of cosmos-sdk. +// In case it panics, the TX aborts and thus, the validator is not created. See AfterValidatorCreated hook. +func (k Keeper) ValidatorConsensusKeyInUse(ctx sdk.Context, valAddr sdk.ValAddress) bool { + // Get the validator being added in the staking module. + val, found := k.stakingKeeper.GetValidator(ctx, valAddr) + if !found { + // Abort TX, do NOT allow validator to be created + panic("did not find newly created validator in staking module") + } + + // Get the consensus address of the validator being added + consensusAddr, err := val.GetConsAddr() + if err != nil { + // Abort TX, do NOT allow validator to be created + panic("could not get validator cons addr ") + } + + allConsumerChains := k.GetAllRegisteredAndProposedChainIDs(ctx) + for _, c := range allConsumerChains { + if _, exist := k.GetValidatorByConsumerAddr(ctx, + c, + types.NewConsumerConsAddress(consensusAddr), + ); exist { + return true + } + } + return false +} diff --git a/x/ccv/provider/keeper/key_assignment_test.go b/x/ccv/provider/keeper/key_assignment_test.go index e9cb1dd646..0009680864 100644 --- a/x/ccv/provider/keeper/key_assignment_test.go +++ b/x/ccv/provider/keeper/key_assignment_test.go @@ -389,17 +389,32 @@ func TestAssignConsensusKeyForConsumerChain(t *testing.T) { doActions func(sdk.Context, providerkeeper.Keeper) }{ /* - 0. Consumer registered: Assign PK0->CK0 and retrieve PK0->CK0 - 1. Consumer registered: Assign PK0->CK0, PK0->CK1 and retrieve PK0->CK1 - 2. Consumer registered: Assign PK0->CK0, PK1->CK0 and error - 3. Consumer registered: Assign PK1->PK0 and error - 4. Consumer not registered: Assign PK0->CK0 and retrieve PK0->CK0 - 5. Consumer not registered: Assign PK0->CK0, PK0->CK1 and retrieve PK0->CK1 - 6. Consumer not registered: Assign PK0->CK0, PK1->CK0 and error - 7. Consumer not registered: Assign PK1->PK0 and error + 0. Consumer not registered: Assign PK0->CK0 and error + 1. Consumer registered: Assign PK0->CK0 and retrieve PK0->CK0 + 2. Consumer registered: Assign PK0->CK0, PK0->CK1 and retrieve PK0->CK1 + 3. Consumer registered: Assign PK0->CK0, PK1->CK0 and error + 4. Consumer registered: Assign PK1->PK0 and error + 5. Consumer proposed: Assign Assign PK0->CK0 and retrieve PK0->CK0 + 6. Consumer proposed: Assign PK0->CK0, PK0->CK1 and retrieve PK0->CK1 + 7. Consumer proposed: Assign PK0->CK0, PK1->CK0 and error + 8. Consumer proposed: Assign PK1->PK0 and error */ { - name: "0", + name: "0", + mockSetup: func(ctx sdk.Context, k providerkeeper.Keeper, mocks testkeeper.MockedKeepers) {}, + doActions: func(ctx sdk.Context, k providerkeeper.Keeper) { + err := k.AssignConsumerKey(ctx, chainID, + providerIdentities[0].SDKStakingValidator(), + consumerIdentities[0].TMProtoCryptoPublicKey(), + ) + require.Error(t, err) + _, found := k.GetValidatorByConsumerAddr(ctx, chainID, + consumerIdentities[0].ConsumerConsAddress()) + require.False(t, found) + }, + }, + { + name: "1", mockSetup: func(ctx sdk.Context, k providerkeeper.Keeper, mocks testkeeper.MockedKeepers) { gomock.InOrder( mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, @@ -424,7 +439,7 @@ func TestAssignConsensusKeyForConsumerChain(t *testing.T) { }, }, { - name: "1", + name: "2", mockSetup: func(ctx sdk.Context, k providerkeeper.Keeper, mocks testkeeper.MockedKeepers) { gomock.InOrder( mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, @@ -460,7 +475,7 @@ func TestAssignConsensusKeyForConsumerChain(t *testing.T) { }, }, { - name: "2", + name: "3", mockSetup: func(ctx sdk.Context, k providerkeeper.Keeper, mocks testkeeper.MockedKeepers) { gomock.InOrder( mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, @@ -493,7 +508,7 @@ func TestAssignConsensusKeyForConsumerChain(t *testing.T) { }, }, { - name: "3", + name: "4", mockSetup: func(ctx sdk.Context, k providerkeeper.Keeper, mocks testkeeper.MockedKeepers) { gomock.InOrder( mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, @@ -511,7 +526,7 @@ func TestAssignConsensusKeyForConsumerChain(t *testing.T) { }, }, { - name: "4", + name: "5", mockSetup: func(ctx sdk.Context, k providerkeeper.Keeper, mocks testkeeper.MockedKeepers) { gomock.InOrder( mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, @@ -520,6 +535,7 @@ func TestAssignConsensusKeyForConsumerChain(t *testing.T) { ) }, doActions: func(ctx sdk.Context, k providerkeeper.Keeper) { + k.SetProposedConsumerChain(ctx, chainID, 0) err := k.AssignConsumerKey(ctx, chainID, providerIdentities[0].SDKStakingValidator(), consumerIdentities[0].TMProtoCryptoPublicKey(), @@ -532,7 +548,7 @@ func TestAssignConsensusKeyForConsumerChain(t *testing.T) { }, }, { - name: "5", + name: "6", mockSetup: func(ctx sdk.Context, k providerkeeper.Keeper, mocks testkeeper.MockedKeepers) { gomock.InOrder( mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, @@ -544,6 +560,7 @@ func TestAssignConsensusKeyForConsumerChain(t *testing.T) { ) }, doActions: func(ctx sdk.Context, k providerkeeper.Keeper) { + k.SetProposedConsumerChain(ctx, chainID, 0) err := k.AssignConsumerKey(ctx, chainID, providerIdentities[0].SDKStakingValidator(), consumerIdentities[0].TMProtoCryptoPublicKey(), @@ -561,7 +578,7 @@ func TestAssignConsensusKeyForConsumerChain(t *testing.T) { }, }, { - name: "6", + name: "7", mockSetup: func(ctx sdk.Context, k providerkeeper.Keeper, mocks testkeeper.MockedKeepers) { gomock.InOrder( mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, @@ -573,6 +590,7 @@ func TestAssignConsensusKeyForConsumerChain(t *testing.T) { ) }, doActions: func(ctx sdk.Context, k providerkeeper.Keeper) { + k.SetProposedConsumerChain(ctx, chainID, 0) err := k.AssignConsumerKey(ctx, chainID, providerIdentities[0].SDKStakingValidator(), consumerIdentities[0].TMProtoCryptoPublicKey(), @@ -590,7 +608,7 @@ func TestAssignConsensusKeyForConsumerChain(t *testing.T) { }, }, { - name: "7", + name: "8", mockSetup: func(ctx sdk.Context, k providerkeeper.Keeper, mocks testkeeper.MockedKeepers) { gomock.InOrder( mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, @@ -599,6 +617,7 @@ func TestAssignConsensusKeyForConsumerChain(t *testing.T) { ) }, doActions: func(ctx sdk.Context, k providerkeeper.Keeper) { + k.SetProposedConsumerChain(ctx, chainID, 0) err := k.AssignConsumerKey(ctx, chainID, providerIdentities[1].SDKStakingValidator(), providerIdentities[0].TMProtoCryptoPublicKey(), @@ -634,6 +653,10 @@ func TestCannotReassignDefaultKeyAssignment(t *testing.T) { providerKeeper, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t)) defer ctrl.Finish() + providerKeeper.SetPendingConsumerAdditionProp(ctx, &types.ConsumerAdditionProposal{ + ChainId: "chain", + }) + // Mock that the validator is validating with the single key, as confirmed by provider's staking keeper gomock.InOrder( mocks.MockStakingKeeper.EXPECT().GetValidatorByConsAddr(ctx, diff --git a/x/ccv/provider/keeper/msg_server.go b/x/ccv/provider/keeper/msg_server.go index 03b98403bc..cd9b2c2c07 100644 --- a/x/ccv/provider/keeper/msg_server.go +++ b/x/ccv/provider/keeper/msg_server.go @@ -29,15 +29,11 @@ func NewMsgServerImpl(keeper *Keeper) types.MsgServer { var _ types.MsgServer = msgServer{} +// AssignConsumerKey defines a method to assign a consensus key on a consumer chain +// for a given validator on the provider func (k msgServer) AssignConsumerKey(goCtx context.Context, msg *types.MsgAssignConsumerKey) (*types.MsgAssignConsumerKeyResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) - // It is possible to assign keys for consumer chains that are not yet approved. - // TODO: In future, a mechanism will be added to limit assigning keys to chains - // which are approved or pending approval, only. - // Note that current attack potential is restricted because validators must sign - // the transaction, and the chainID size is limited. - providerValidatorAddr, err := sdk.ValAddressFromBech32(msg.ProviderAddr) if err != nil { return nil, err diff --git a/x/ccv/provider/types/keys.go b/x/ccv/provider/types/keys.go index 95b5b4b820..d2845d094a 100644 --- a/x/ccv/provider/types/keys.go +++ b/x/ccv/provider/types/keys.go @@ -142,6 +142,9 @@ const ( // to the minimum height of a valid consumer equivocation evidence EquivocationEvidenceMinHeightBytePrefix + // ProposedConsumerChainByteKey is the byte prefix storing the consumer chainId in consumerAddition gov proposal submitted before voting finishes + ProposedConsumerChainByteKey + // NOTE: DO NOT ADD NEW BYTE PREFIXES HERE WITHOUT ADDING THEM TO getAllKeyPrefixes() IN keys_test.go ) @@ -494,6 +497,26 @@ func VSCMaturedHandledThisBlockKey() []byte { return []byte{VSCMaturedHandledThisBlockBytePrefix} } +// ProposedConsumerChainKey returns the key of proposed consumer chainId in consumerAddition gov proposal before voting finishes, the stored key format is prefix|proposalID, value is chainID +func ProposedConsumerChainKey(proposalID uint64) []byte { + return ccvtypes.AppendMany( + []byte{ProposedConsumerChainByteKey}, + sdk.Uint64ToBigEndian(proposalID), + ) +} + +// ParseProposedConsumerChainKey get the proposalID in the key +func ParseProposedConsumerChainKey(prefix byte, bz []byte) (uint64, error) { + expectedPrefix := []byte{prefix} + prefixL := len(expectedPrefix) + if prefix := bz[:prefixL]; !bytes.Equal(prefix, expectedPrefix) { + return 0, fmt.Errorf("invalid prefix; expected: %X, got: %X", expectedPrefix, prefix) + } + proposalID := sdk.BigEndianToUint64(bz[prefixL:]) + + return proposalID, nil +} + // // End of generic helpers section // diff --git a/x/ccv/provider/types/keys_test.go b/x/ccv/provider/types/keys_test.go index 6b263a4fdb..c1c9394764 100644 --- a/x/ccv/provider/types/keys_test.go +++ b/x/ccv/provider/types/keys_test.go @@ -55,6 +55,7 @@ func getAllKeyPrefixes() []byte { providertypes.SlashLogBytePrefix, providertypes.VSCMaturedHandledThisBlockBytePrefix, providertypes.EquivocationEvidenceMinHeightBytePrefix, + providertypes.ProposedConsumerChainByteKey, } } @@ -311,3 +312,22 @@ func TestKeysWithUint64Payload(t *testing.T) { } } } + +func TestParseProposedConsumerChainKey(t *testing.T) { + tests := []struct { + chainID string + proposalID uint64 + }{ + {chainID: "1", proposalID: 1}, + {chainID: "some other ID", proposalID: 12}, + {chainID: "some other other chain ID", proposalID: 123}, + } + + for _, test := range tests { + key := providertypes.ProposedConsumerChainKey(test.proposalID) + pID, err := providertypes.ParseProposedConsumerChainKey( + providertypes.ProposedConsumerChainByteKey, key) + require.NoError(t, err) + require.Equal(t, pID, test.proposalID) + } +} diff --git a/x/ccv/provider/types/msg.go b/x/ccv/provider/types/msg.go index c2551f9ceb..f7ee11325c 100644 --- a/x/ccv/provider/types/msg.go +++ b/x/ccv/provider/types/msg.go @@ -77,8 +77,7 @@ func (msg MsgAssignConsumerKey) ValidateBasic() error { // It is possible to assign keys for consumer chains that are not yet approved. // This can only be done by a signing validator, but it is still sensible // to limit the chainID size to prevent abuse. - // TODO: In future, a mechanism will be added to limit assigning keys to chains - // which are approved or pending approval, only. + if 128 < len(msg.ChainId) { return ErrBlankConsumerChainID } diff --git a/x/ccv/provider/types/query.pb.go b/x/ccv/provider/types/query.pb.go index 37ef05d20f..f5f9852846 100644 --- a/x/ccv/provider/types/query.pb.go +++ b/x/ccv/provider/types/query.pb.go @@ -781,6 +781,138 @@ func (m *QueryRegisteredConsumerRewardDenomsResponse) GetDenoms() []string { return nil } +type QueryProposedChainIDsRequest struct { +} + +func (m *QueryProposedChainIDsRequest) Reset() { *m = QueryProposedChainIDsRequest{} } +func (m *QueryProposedChainIDsRequest) String() string { return proto.CompactTextString(m) } +func (*QueryProposedChainIDsRequest) ProtoMessage() {} +func (*QueryProposedChainIDsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_422512d7b7586cd7, []int{17} +} +func (m *QueryProposedChainIDsRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryProposedChainIDsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryProposedChainIDsRequest.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 *QueryProposedChainIDsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryProposedChainIDsRequest.Merge(m, src) +} +func (m *QueryProposedChainIDsRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryProposedChainIDsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryProposedChainIDsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryProposedChainIDsRequest proto.InternalMessageInfo + +type QueryProposedChainIDsResponse struct { + ProposedChains []ProposedChain `protobuf:"bytes,1,rep,name=proposedChains,proto3" json:"proposedChains"` +} + +func (m *QueryProposedChainIDsResponse) Reset() { *m = QueryProposedChainIDsResponse{} } +func (m *QueryProposedChainIDsResponse) String() string { return proto.CompactTextString(m) } +func (*QueryProposedChainIDsResponse) ProtoMessage() {} +func (*QueryProposedChainIDsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_422512d7b7586cd7, []int{18} +} +func (m *QueryProposedChainIDsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryProposedChainIDsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryProposedChainIDsResponse.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 *QueryProposedChainIDsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryProposedChainIDsResponse.Merge(m, src) +} +func (m *QueryProposedChainIDsResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryProposedChainIDsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryProposedChainIDsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryProposedChainIDsResponse proto.InternalMessageInfo + +func (m *QueryProposedChainIDsResponse) GetProposedChains() []ProposedChain { + if m != nil { + return m.ProposedChains + } + return nil +} + +type ProposedChain struct { + ChainID string `protobuf:"bytes,1,opt,name=chainID,proto3" json:"chainID,omitempty"` + ProposalID uint64 `protobuf:"varint,2,opt,name=proposalID,proto3" json:"proposalID,omitempty"` +} + +func (m *ProposedChain) Reset() { *m = ProposedChain{} } +func (m *ProposedChain) String() string { return proto.CompactTextString(m) } +func (*ProposedChain) ProtoMessage() {} +func (*ProposedChain) Descriptor() ([]byte, []int) { + return fileDescriptor_422512d7b7586cd7, []int{19} +} +func (m *ProposedChain) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ProposedChain) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ProposedChain.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 *ProposedChain) XXX_Merge(src proto.Message) { + xxx_messageInfo_ProposedChain.Merge(m, src) +} +func (m *ProposedChain) XXX_Size() int { + return m.Size() +} +func (m *ProposedChain) XXX_DiscardUnknown() { + xxx_messageInfo_ProposedChain.DiscardUnknown(m) +} + +var xxx_messageInfo_ProposedChain proto.InternalMessageInfo + +func (m *ProposedChain) GetChainID() string { + if m != nil { + return m.ChainID + } + return "" +} + +func (m *ProposedChain) GetProposalID() uint64 { + if m != nil { + return m.ProposalID + } + return 0 +} + func init() { proto.RegisterType((*QueryConsumerGenesisRequest)(nil), "interchain_security.ccv.provider.v1.QueryConsumerGenesisRequest") proto.RegisterType((*QueryConsumerGenesisResponse)(nil), "interchain_security.ccv.provider.v1.QueryConsumerGenesisResponse") @@ -799,6 +931,9 @@ func init() { proto.RegisterType((*QueryThrottleStateResponse)(nil), "interchain_security.ccv.provider.v1.QueryThrottleStateResponse") proto.RegisterType((*QueryRegisteredConsumerRewardDenomsRequest)(nil), "interchain_security.ccv.provider.v1.QueryRegisteredConsumerRewardDenomsRequest") proto.RegisterType((*QueryRegisteredConsumerRewardDenomsResponse)(nil), "interchain_security.ccv.provider.v1.QueryRegisteredConsumerRewardDenomsResponse") + proto.RegisterType((*QueryProposedChainIDsRequest)(nil), "interchain_security.ccv.provider.v1.QueryProposedChainIDsRequest") + proto.RegisterType((*QueryProposedChainIDsResponse)(nil), "interchain_security.ccv.provider.v1.QueryProposedChainIDsResponse") + proto.RegisterType((*ProposedChain)(nil), "interchain_security.ccv.provider.v1.ProposedChain") } func init() { @@ -806,74 +941,81 @@ func init() { } var fileDescriptor_422512d7b7586cd7 = []byte{ - // 1060 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x57, 0xcf, 0x6f, 0xdc, 0x44, - 0x14, 0x5e, 0x27, 0x34, 0x24, 0x13, 0x20, 0xd5, 0xb4, 0x94, 0xad, 0x13, 0xed, 0x16, 0x57, 0x40, - 0x5a, 0xc0, 0xce, 0x6e, 0x2e, 0x6d, 0x51, 0xba, 0xd9, 0x0d, 0x21, 0x54, 0x6d, 0xd5, 0xe0, 0x56, - 0x20, 0x01, 0xc2, 0x9a, 0xd8, 0xc3, 0xae, 0x25, 0xaf, 0xc7, 0x9d, 0x99, 0x75, 0x1a, 0x55, 0x1c, - 0xe0, 0x00, 0x3d, 0x56, 0x42, 0x70, 0xee, 0x9f, 0xd3, 0x1b, 0x45, 0xbd, 0x70, 0x2a, 0x28, 0xe1, - 0xc0, 0x11, 0x71, 0x47, 0xaa, 0x3c, 0x1e, 0x7b, 0x7f, 0x39, 0xbb, 0xce, 0x26, 0x37, 0x7b, 0xe6, - 0xbd, 0xef, 0x7d, 0xdf, 0xdb, 0x37, 0xf3, 0x79, 0x81, 0xe1, 0xfa, 0x1c, 0x53, 0xbb, 0x85, 0x5c, - 0xdf, 0x62, 0xd8, 0xee, 0x50, 0x97, 0xef, 0x19, 0xb6, 0x1d, 0x1a, 0x01, 0x25, 0xa1, 0xeb, 0x60, - 0x6a, 0x84, 0x15, 0xe3, 0x7e, 0x07, 0xd3, 0x3d, 0x3d, 0xa0, 0x84, 0x13, 0x78, 0x31, 0x23, 0x41, - 0xb7, 0xed, 0x50, 0x4f, 0x12, 0xf4, 0xb0, 0xa2, 0x2e, 0x35, 0x09, 0x69, 0x7a, 0xd8, 0x40, 0x81, - 0x6b, 0x20, 0xdf, 0x27, 0x1c, 0x71, 0x97, 0xf8, 0x2c, 0x86, 0x50, 0xcf, 0x36, 0x49, 0x93, 0x88, - 0x47, 0x23, 0x7a, 0x92, 0xab, 0x65, 0x99, 0x23, 0xde, 0x76, 0x3a, 0xdf, 0x1a, 0xdc, 0x6d, 0x63, - 0xc6, 0x51, 0x3b, 0x90, 0x01, 0xd5, 0x3c, 0x54, 0x53, 0x16, 0x71, 0xce, 0xca, 0x61, 0x39, 0x61, - 0xc5, 0x60, 0x2d, 0x44, 0xb1, 0x63, 0xd9, 0xc4, 0x67, 0x9d, 0x76, 0x9a, 0xf1, 0xce, 0x88, 0x8c, - 0x5d, 0x97, 0xe2, 0x38, 0x4c, 0xbb, 0x02, 0x16, 0x3f, 0x8b, 0xba, 0xb2, 0x21, 0xb3, 0xb7, 0xb0, - 0x8f, 0x99, 0xcb, 0x4c, 0x7c, 0xbf, 0x83, 0x19, 0x87, 0xe7, 0xc1, 0x6c, 0x0c, 0xe1, 0x3a, 0x45, - 0xe5, 0x82, 0xb2, 0x3c, 0x67, 0xbe, 0x2a, 0xde, 0x6f, 0x38, 0xda, 0x43, 0xb0, 0x94, 0x9d, 0xc9, - 0x02, 0xe2, 0x33, 0x0c, 0xbf, 0x02, 0xaf, 0x37, 0xe3, 0x25, 0x8b, 0x71, 0xc4, 0xb1, 0xc8, 0x9f, - 0xaf, 0xae, 0xe8, 0x87, 0x35, 0x3e, 0xac, 0xe8, 0x03, 0x58, 0x77, 0xa3, 0xbc, 0xc6, 0x2b, 0x4f, - 0x5f, 0x94, 0x0b, 0xe6, 0x6b, 0xcd, 0x9e, 0x35, 0x6d, 0x09, 0xa8, 0x7d, 0xc5, 0x37, 0x22, 0xb8, - 0x84, 0xb5, 0x86, 0x06, 0x44, 0x25, 0xbb, 0x92, 0x59, 0x03, 0xcc, 0x88, 0xf2, 0xac, 0xa8, 0x5c, - 0x98, 0x5e, 0x9e, 0xaf, 0x5e, 0xd6, 0x73, 0xcc, 0x82, 0x2e, 0x40, 0x4c, 0x99, 0xa9, 0x5d, 0x02, - 0xef, 0x0d, 0x97, 0xb8, 0xcb, 0x11, 0xe5, 0xdb, 0x94, 0x04, 0x84, 0x21, 0x2f, 0x65, 0xf3, 0x48, - 0x01, 0xcb, 0xe3, 0x63, 0x25, 0xb7, 0xaf, 0xc1, 0x5c, 0x90, 0x2c, 0xca, 0x8e, 0x5d, 0xcf, 0x47, - 0x4f, 0x82, 0xd7, 0x1d, 0xc7, 0x8d, 0x86, 0xb4, 0x0b, 0xdd, 0x05, 0xd4, 0x96, 0xc1, 0xbb, 0x59, - 0x4c, 0x48, 0x30, 0x44, 0xfa, 0x47, 0x25, 0x5b, 0x60, 0x5f, 0x68, 0xfa, 0x4b, 0x0f, 0x71, 0x5e, - 0x3b, 0x12, 0x67, 0x13, 0xb7, 0x49, 0x88, 0xbc, 0x4c, 0xca, 0x35, 0x70, 0x4a, 0x94, 0x1e, 0x31, - 0x8a, 0x70, 0x11, 0xcc, 0xd9, 0x9e, 0x8b, 0x7d, 0x1e, 0xed, 0x4d, 0x89, 0xbd, 0xd9, 0x78, 0xe1, - 0x86, 0xa3, 0xfd, 0xa4, 0x80, 0xb7, 0x85, 0x92, 0xcf, 0x91, 0xe7, 0x3a, 0x88, 0x13, 0xda, 0xd3, - 0x2a, 0x3a, 0x7e, 0xd0, 0xe1, 0x1a, 0x38, 0x9d, 0x90, 0xb6, 0x90, 0xe3, 0x50, 0xcc, 0x58, 0x5c, - 0xa4, 0x01, 0xff, 0x7b, 0x51, 0x7e, 0x63, 0x0f, 0xb5, 0xbd, 0x6b, 0x9a, 0xdc, 0xd0, 0xcc, 0x85, - 0x24, 0xb6, 0x1e, 0xaf, 0x5c, 0x9b, 0x7d, 0xf4, 0xa4, 0x5c, 0xf8, 0xe7, 0x49, 0xb9, 0xa0, 0xdd, - 0x01, 0xda, 0x28, 0x22, 0xb2, 0x9b, 0x97, 0xc0, 0xe9, 0xe4, 0x28, 0xa7, 0xe5, 0x62, 0x46, 0x0b, - 0x76, 0x4f, 0x7c, 0x54, 0x6c, 0x58, 0xda, 0x76, 0x4f, 0xf1, 0x7c, 0xd2, 0x86, 0x6a, 0x8d, 0x90, - 0x36, 0x50, 0x7f, 0x94, 0xb4, 0x7e, 0x22, 0x5d, 0x69, 0x43, 0x9d, 0x94, 0xd2, 0x06, 0xba, 0xa6, - 0x2d, 0x82, 0xf3, 0x02, 0xf0, 0x5e, 0x8b, 0x12, 0xce, 0x3d, 0x2c, 0x8e, 0x7d, 0x32, 0x9c, 0xbf, - 0x2b, 0xf2, 0xf8, 0x0f, 0xec, 0xca, 0x32, 0x65, 0x30, 0xcf, 0x3c, 0xc4, 0x5a, 0x56, 0x1b, 0x73, - 0x4c, 0x45, 0x85, 0x69, 0x13, 0x88, 0xa5, 0xdb, 0xd1, 0x0a, 0xac, 0x82, 0x37, 0x7b, 0x02, 0x2c, - 0xe4, 0x79, 0x64, 0x17, 0xf9, 0x36, 0x16, 0xda, 0xa7, 0xcd, 0x33, 0xdd, 0xd0, 0x7a, 0xb2, 0x05, - 0xbf, 0x01, 0x45, 0x1f, 0x3f, 0xe0, 0x16, 0xc5, 0x81, 0x87, 0x7d, 0x97, 0xb5, 0x2c, 0x1b, 0xf9, - 0x4e, 0x24, 0x16, 0x17, 0xa7, 0xc5, 0xcc, 0xab, 0x7a, 0x7c, 0xf3, 0xeb, 0xc9, 0xcd, 0xaf, 0xdf, - 0x4b, 0x6e, 0xfe, 0xc6, 0x6c, 0x74, 0x87, 0x3d, 0xfe, 0xb3, 0xac, 0x98, 0xe7, 0x22, 0x14, 0x33, - 0x01, 0xd9, 0x48, 0x30, 0xb4, 0x0f, 0xc0, 0x65, 0x21, 0xc9, 0xc4, 0x4d, 0x97, 0x71, 0x4c, 0xb1, - 0xd3, 0x3d, 0x1d, 0xbb, 0x88, 0x3a, 0x1f, 0x63, 0x9f, 0xb4, 0xd3, 0xe3, 0xb9, 0x09, 0xde, 0xcf, - 0x15, 0x2d, 0x3b, 0x72, 0x0e, 0xcc, 0x38, 0x62, 0x45, 0xdc, 0x78, 0x73, 0xa6, 0x7c, 0xab, 0xfe, - 0xba, 0x00, 0x4e, 0x09, 0x1c, 0xb8, 0xaf, 0x80, 0xb3, 0x59, 0xd7, 0x39, 0x5c, 0xcf, 0x75, 0x92, - 0x47, 0x78, 0x88, 0x5a, 0x3f, 0x06, 0x42, 0xcc, 0x5f, 0xdb, 0xfc, 0xe1, 0xf9, 0xdf, 0x3f, 0x4f, - 0xd5, 0xe0, 0xda, 0x78, 0x9b, 0x4f, 0xe7, 0x59, 0xfa, 0x85, 0xf1, 0x30, 0x19, 0xfe, 0xef, 0xe0, - 0x73, 0x05, 0x9c, 0xc9, 0x30, 0x06, 0x58, 0x3b, 0x3a, 0xc3, 0x3e, 0xc3, 0x51, 0xd7, 0x27, 0x07, - 0x90, 0x0a, 0xaf, 0x0a, 0x85, 0xab, 0xb0, 0x72, 0x04, 0x85, 0xb1, 0x15, 0xc1, 0xef, 0xa7, 0x40, - 0xf1, 0x10, 0x7f, 0x61, 0xf0, 0xd6, 0x84, 0xcc, 0x32, 0xad, 0x4c, 0xbd, 0x7d, 0x42, 0x68, 0x52, - 0xf4, 0xa7, 0x42, 0x74, 0x03, 0xae, 0x1f, 0x55, 0x74, 0xf4, 0x45, 0x41, 0xb9, 0x95, 0xba, 0x04, - 0xfc, 0x5f, 0x01, 0x6f, 0x65, 0xdb, 0x15, 0x83, 0x37, 0x27, 0x26, 0x3d, 0xec, 0x8b, 0xea, 0xad, - 0x93, 0x01, 0x93, 0x0d, 0xd8, 0x12, 0x0d, 0xa8, 0xc3, 0xda, 0x04, 0x0d, 0x20, 0x41, 0x8f, 0xfe, - 0x7f, 0x93, 0x1b, 0x31, 0xd3, 0x5b, 0xe0, 0x27, 0xf9, 0x59, 0x8f, 0x72, 0x49, 0x75, 0xeb, 0xd8, - 0x38, 0x52, 0x78, 0x5d, 0x08, 0xff, 0x08, 0x5e, 0xcd, 0xf1, 0xdd, 0x9e, 0x00, 0x59, 0x7d, 0x56, - 0x95, 0x21, 0xb9, 0xd7, 0x73, 0x26, 0x92, 0x9c, 0xe1, 0x9e, 0x13, 0x49, 0xce, 0x32, 0xbf, 0xc9, - 0x24, 0xf7, 0xd9, 0x25, 0xfc, 0x4d, 0x01, 0x70, 0xd8, 0xf7, 0xe0, 0xf5, 0xfc, 0x14, 0xb3, 0xec, - 0x54, 0xad, 0x4d, 0x9c, 0x2f, 0xa5, 0x5d, 0x11, 0xd2, 0xaa, 0x70, 0x65, 0xbc, 0x34, 0x2e, 0x01, - 0xe2, 0xff, 0x04, 0xf0, 0x97, 0x29, 0x70, 0x31, 0x87, 0x91, 0xc1, 0x3b, 0xf9, 0x29, 0xe6, 0x32, - 0x50, 0x75, 0xfb, 0xe4, 0x00, 0x65, 0x13, 0x6e, 0x8a, 0x26, 0x6c, 0xc2, 0x8d, 0xf1, 0x4d, 0xa0, - 0x29, 0x62, 0x77, 0xa6, 0xa9, 0xc0, 0xb4, 0x62, 0x63, 0x6e, 0x7c, 0xf1, 0x74, 0xbf, 0xa4, 0x3c, - 0xdb, 0x2f, 0x29, 0x7f, 0xed, 0x97, 0x94, 0xc7, 0x07, 0xa5, 0xc2, 0xb3, 0x83, 0x52, 0xe1, 0x8f, - 0x83, 0x52, 0xe1, 0xcb, 0xb5, 0xa6, 0xcb, 0x5b, 0x9d, 0x1d, 0xdd, 0x26, 0x6d, 0xc3, 0x26, 0xac, - 0x4d, 0x58, 0x4f, 0xbd, 0x0f, 0xd3, 0x7a, 0xe1, 0xaa, 0xf1, 0x60, 0xa0, 0xf3, 0x7b, 0x01, 0x66, - 0x3b, 0x33, 0xe2, 0xe3, 0x64, 0xf5, 0x65, 0x00, 0x00, 0x00, 0xff, 0xff, 0xad, 0x57, 0x98, 0xb7, - 0x30, 0x0f, 0x00, 0x00, + // 1170 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x57, 0xcf, 0x6f, 0x1b, 0xc5, + 0x17, 0xf7, 0x26, 0x6d, 0xbe, 0xc9, 0xe4, 0xdb, 0x1f, 0x4c, 0x4b, 0x71, 0x37, 0xc1, 0x2e, 0x5b, + 0x01, 0x69, 0x81, 0xdd, 0xc4, 0xb9, 0xb4, 0x81, 0x34, 0xb1, 0xe3, 0x10, 0xac, 0xb6, 0x6a, 0xd8, + 0x56, 0x20, 0x01, 0x62, 0xd9, 0xec, 0x0e, 0xf6, 0x4a, 0xeb, 0x9d, 0xed, 0xcc, 0xd8, 0x69, 0x54, + 0x71, 0x28, 0x48, 0xd0, 0x63, 0x25, 0xc4, 0xbd, 0x7f, 0x4e, 0x6f, 0x14, 0xf5, 0xc2, 0xa9, 0xa0, + 0x84, 0x03, 0xe2, 0x84, 0xb8, 0x23, 0xa1, 0x9d, 0x9d, 0x5d, 0xaf, 0xed, 0x8d, 0xbd, 0x76, 0x72, + 0xb3, 0x67, 0xde, 0xfb, 0xbc, 0xcf, 0xe7, 0xf9, 0xcd, 0x7b, 0xcf, 0x40, 0x73, 0x3c, 0x86, 0x88, + 0xd5, 0x30, 0x1d, 0xcf, 0xa0, 0xc8, 0x6a, 0x11, 0x87, 0xed, 0x69, 0x96, 0xd5, 0xd6, 0x7c, 0x82, + 0xdb, 0x8e, 0x8d, 0x88, 0xd6, 0x5e, 0xd2, 0xee, 0xb7, 0x10, 0xd9, 0x53, 0x7d, 0x82, 0x19, 0x86, + 0x97, 0x53, 0x1c, 0x54, 0xcb, 0x6a, 0xab, 0x91, 0x83, 0xda, 0x5e, 0x92, 0xe7, 0xeb, 0x18, 0xd7, + 0x5d, 0xa4, 0x99, 0xbe, 0xa3, 0x99, 0x9e, 0x87, 0x99, 0xc9, 0x1c, 0xec, 0xd1, 0x10, 0x42, 0x3e, + 0x5f, 0xc7, 0x75, 0xcc, 0x3f, 0x6a, 0xc1, 0x27, 0x71, 0x5a, 0x14, 0x3e, 0xfc, 0xdb, 0x4e, 0xeb, + 0x6b, 0x8d, 0x39, 0x4d, 0x44, 0x99, 0xd9, 0xf4, 0x85, 0x41, 0x29, 0x0b, 0xd5, 0x98, 0x45, 0xe8, + 0xb3, 0x78, 0x98, 0x4f, 0x7b, 0x49, 0xa3, 0x0d, 0x93, 0x20, 0xdb, 0xb0, 0xb0, 0x47, 0x5b, 0xcd, + 0xd8, 0xe3, 0xcd, 0x01, 0x1e, 0xbb, 0x0e, 0x41, 0xa1, 0x99, 0x72, 0x0d, 0xcc, 0x7d, 0x1c, 0x64, + 0x65, 0x43, 0x78, 0x6f, 0x21, 0x0f, 0x51, 0x87, 0xea, 0xe8, 0x7e, 0x0b, 0x51, 0x06, 0x2f, 0x82, + 0xe9, 0x10, 0xc2, 0xb1, 0xf3, 0xd2, 0x25, 0x69, 0x61, 0x46, 0xff, 0x1f, 0xff, 0x5e, 0xb3, 0x95, + 0x87, 0x60, 0x3e, 0xdd, 0x93, 0xfa, 0xd8, 0xa3, 0x08, 0x7e, 0x0e, 0x4e, 0xd5, 0xc3, 0x23, 0x83, + 0x32, 0x93, 0x21, 0xee, 0x3f, 0x5b, 0x5a, 0x54, 0x0f, 0x4b, 0x7c, 0x7b, 0x49, 0xed, 0xc1, 0xba, + 0x1b, 0xf8, 0x55, 0x4e, 0x3c, 0x7b, 0x59, 0xcc, 0xe9, 0xff, 0xaf, 0x27, 0xce, 0x94, 0x79, 0x20, + 0x77, 0x05, 0xdf, 0x08, 0xe0, 0x22, 0xd6, 0x8a, 0xd9, 0x23, 0x2a, 0xba, 0x15, 0xcc, 0x2a, 0x60, + 0x8a, 0x87, 0xa7, 0x79, 0xe9, 0xd2, 0xe4, 0xc2, 0x6c, 0xe9, 0xaa, 0x9a, 0xa1, 0x16, 0x54, 0x0e, + 0xa2, 0x0b, 0x4f, 0xe5, 0x0a, 0x78, 0xbb, 0x3f, 0xc4, 0x5d, 0x66, 0x12, 0xb6, 0x4d, 0xb0, 0x8f, + 0xa9, 0xe9, 0xc6, 0x6c, 0x1e, 0x4b, 0x60, 0x61, 0xb8, 0xad, 0xe0, 0xf6, 0x05, 0x98, 0xf1, 0xa3, + 0x43, 0x91, 0xb1, 0x1b, 0xd9, 0xe8, 0x09, 0xf0, 0xb2, 0x6d, 0x3b, 0x41, 0x91, 0x76, 0xa0, 0x3b, + 0x80, 0xca, 0x02, 0x78, 0x2b, 0x8d, 0x09, 0xf6, 0xfb, 0x48, 0x7f, 0x2f, 0xa5, 0x0b, 0xec, 0x32, + 0x8d, 0x7f, 0xe9, 0x3e, 0xce, 0xab, 0x23, 0x71, 0xd6, 0x51, 0x13, 0xb7, 0x4d, 0x37, 0x95, 0xf2, + 0x1a, 0x38, 0xc9, 0x43, 0x0f, 0x28, 0x45, 0x38, 0x07, 0x66, 0x2c, 0xd7, 0x41, 0x1e, 0x0b, 0xee, + 0x26, 0xf8, 0xdd, 0x74, 0x78, 0x50, 0xb3, 0x95, 0x1f, 0x24, 0xf0, 0x06, 0x57, 0xf2, 0x89, 0xe9, + 0x3a, 0xb6, 0xc9, 0x30, 0x49, 0xa4, 0x8a, 0x0c, 0x2f, 0x74, 0xb8, 0x0a, 0xce, 0x46, 0xa4, 0x0d, + 0xd3, 0xb6, 0x09, 0xa2, 0x34, 0x0c, 0x52, 0x81, 0xff, 0xbc, 0x2c, 0x9e, 0xde, 0x33, 0x9b, 0xee, + 0x8a, 0x22, 0x2e, 0x14, 0xfd, 0x4c, 0x64, 0x5b, 0x0e, 0x4f, 0x56, 0xa6, 0x1f, 0x3f, 0x2d, 0xe6, + 0xfe, 0x7c, 0x5a, 0xcc, 0x29, 0x77, 0x80, 0x32, 0x88, 0x88, 0xc8, 0xe6, 0x15, 0x70, 0x36, 0x7a, + 0xca, 0x71, 0xb8, 0x90, 0xd1, 0x19, 0x2b, 0x61, 0x1f, 0x04, 0xeb, 0x97, 0xb6, 0x9d, 0x08, 0x9e, + 0x4d, 0x5a, 0x5f, 0xac, 0x01, 0xd2, 0x7a, 0xe2, 0x0f, 0x92, 0xd6, 0x4d, 0xa4, 0x23, 0xad, 0x2f, + 0x93, 0x42, 0x5a, 0x4f, 0xd6, 0x94, 0x39, 0x70, 0x91, 0x03, 0xde, 0x6b, 0x10, 0xcc, 0x98, 0x8b, + 0xf8, 0xb3, 0x8f, 0x8a, 0xf3, 0x17, 0x49, 0x3c, 0xff, 0x9e, 0x5b, 0x11, 0xa6, 0x08, 0x66, 0xa9, + 0x6b, 0xd2, 0x86, 0xd1, 0x44, 0x0c, 0x11, 0x1e, 0x61, 0x52, 0x07, 0xfc, 0xe8, 0x76, 0x70, 0x02, + 0x4b, 0xe0, 0xd5, 0x84, 0x81, 0x61, 0xba, 0x2e, 0xde, 0x35, 0x3d, 0x0b, 0x71, 0xed, 0x93, 0xfa, + 0xb9, 0x8e, 0x69, 0x39, 0xba, 0x82, 0x5f, 0x82, 0xbc, 0x87, 0x1e, 0x30, 0x83, 0x20, 0xdf, 0x45, + 0x9e, 0x43, 0x1b, 0x86, 0x65, 0x7a, 0x76, 0x20, 0x16, 0xe5, 0x27, 0x79, 0xcd, 0xcb, 0x6a, 0xd8, + 0xf9, 0xd5, 0xa8, 0xf3, 0xab, 0xf7, 0xa2, 0xce, 0x5f, 0x99, 0x0e, 0x7a, 0xd8, 0x93, 0xdf, 0x8a, + 0x92, 0x7e, 0x21, 0x40, 0xd1, 0x23, 0x90, 0x8d, 0x08, 0x43, 0x79, 0x17, 0x5c, 0xe5, 0x92, 0x74, + 0x54, 0x77, 0x28, 0x43, 0x04, 0xd9, 0x9d, 0xd7, 0xb1, 0x6b, 0x12, 0xbb, 0x8a, 0x3c, 0xdc, 0x8c, + 0x9f, 0xe7, 0x26, 0x78, 0x27, 0x93, 0xb5, 0xc8, 0xc8, 0x05, 0x30, 0x65, 0xf3, 0x13, 0xde, 0xf1, + 0x66, 0x74, 0xf1, 0x4d, 0x29, 0x88, 0x1e, 0x1e, 0xbe, 0x3c, 0x64, 0xf3, 0x97, 0x56, 0xab, 0xc6, + 0x61, 0x1e, 0x49, 0xe0, 0xf5, 0x43, 0x0c, 0x04, 0xf2, 0x57, 0xe0, 0xb4, 0x9f, 0xbc, 0x8b, 0x7a, + 0x6a, 0x29, 0x53, 0x03, 0xe8, 0x82, 0x15, 0x8d, 0xbe, 0x07, 0x4f, 0xa9, 0x81, 0x53, 0x5d, 0x66, + 0x30, 0x0f, 0x44, 0xfd, 0x56, 0xbb, 0xcb, 0xb9, 0x0a, 0x0b, 0x00, 0x44, 0x8d, 0xa3, 0x56, 0xe5, + 0x3f, 0xe6, 0x09, 0x3d, 0x71, 0x52, 0xfa, 0xee, 0x15, 0x70, 0x92, 0xcb, 0x81, 0xfb, 0x12, 0x38, + 0x9f, 0x36, 0xbd, 0xe0, 0x7a, 0x26, 0xde, 0x03, 0x46, 0xa6, 0x5c, 0x3e, 0x02, 0x42, 0x98, 0x54, + 0x65, 0xf3, 0xdb, 0x17, 0x7f, 0xfc, 0x38, 0xb1, 0x06, 0x57, 0x87, 0x6f, 0x35, 0xf1, 0xf3, 0x15, + 0xe3, 0x51, 0x7b, 0x18, 0xbd, 0xf5, 0x6f, 0xe0, 0x0b, 0x09, 0x9c, 0x4b, 0x99, 0x83, 0x70, 0x6d, + 0x74, 0x86, 0x5d, 0xf3, 0x55, 0x5e, 0x1f, 0x1f, 0x40, 0x28, 0xbc, 0xce, 0x15, 0x2e, 0xc3, 0xa5, + 0x11, 0x14, 0x86, 0x93, 0x17, 0x3e, 0x9a, 0x00, 0xf9, 0x43, 0xc6, 0x29, 0x85, 0xb7, 0xc6, 0x64, + 0x96, 0x3a, 0xb9, 0xe5, 0xdb, 0xc7, 0x84, 0x26, 0x44, 0x7f, 0xc4, 0x45, 0x57, 0xe0, 0xfa, 0xa8, + 0xa2, 0x83, 0x05, 0x8a, 0x30, 0x23, 0x1e, 0x8a, 0xf0, 0x5f, 0x09, 0xbc, 0x96, 0x3e, 0x9d, 0x29, + 0xbc, 0x39, 0x36, 0xe9, 0xfe, 0x35, 0x40, 0xbe, 0x75, 0x3c, 0x60, 0x22, 0x01, 0x5b, 0x3c, 0x01, + 0x65, 0xb8, 0x36, 0x46, 0x02, 0xb0, 0x9f, 0xd0, 0xff, 0x77, 0x34, 0x00, 0x52, 0x47, 0x29, 0xfc, + 0x30, 0x3b, 0xeb, 0x41, 0x4b, 0x81, 0xbc, 0x75, 0x64, 0x1c, 0x21, 0xbc, 0xcc, 0x85, 0xbf, 0x0f, + 0xaf, 0x67, 0xf8, 0x9b, 0x12, 0x01, 0x19, 0x5d, 0x93, 0x39, 0x45, 0x72, 0x72, 0xc4, 0x8e, 0x25, + 0x39, 0x65, 0x59, 0x18, 0x4b, 0x72, 0xda, 0xac, 0x1f, 0x4f, 0x72, 0xd7, 0x76, 0x00, 0x7f, 0x96, + 0x00, 0xec, 0x1f, 0xf3, 0xf0, 0x46, 0x76, 0x8a, 0x69, 0xdb, 0x83, 0xbc, 0x36, 0xb6, 0xbf, 0x90, + 0x76, 0x8d, 0x4b, 0x2b, 0xc1, 0xc5, 0xe1, 0xd2, 0x98, 0x00, 0x08, 0xff, 0x02, 0xc1, 0x9f, 0x26, + 0xc0, 0xe5, 0x0c, 0x73, 0x1b, 0xde, 0xc9, 0x4e, 0x31, 0xd3, 0xbe, 0x20, 0x6f, 0x1f, 0x1f, 0xa0, + 0x48, 0xc2, 0x4d, 0x9e, 0x84, 0x4d, 0xb8, 0x31, 0x3c, 0x09, 0x24, 0x46, 0xec, 0xd4, 0x34, 0xe1, + 0x98, 0x46, 0xb8, 0x87, 0xc0, 0xbf, 0xfa, 0xf6, 0x8c, 0x64, 0x33, 0xa9, 0x55, 0x29, 0x1c, 0x61, + 0xaa, 0x1e, 0xb2, 0xcc, 0xc8, 0x95, 0xa3, 0x40, 0x08, 0xd5, 0x15, 0xae, 0xfa, 0x03, 0xb8, 0x32, + 0x5c, 0x75, 0xb4, 0xc6, 0x18, 0x3d, 0x03, 0xac, 0xf2, 0xe9, 0xb3, 0xfd, 0x82, 0xf4, 0x7c, 0xbf, + 0x20, 0xfd, 0xbe, 0x5f, 0x90, 0x9e, 0x1c, 0x14, 0x72, 0xcf, 0x0f, 0x0a, 0xb9, 0x5f, 0x0f, 0x0a, + 0xb9, 0xcf, 0x56, 0xeb, 0x0e, 0x6b, 0xb4, 0x76, 0x54, 0x0b, 0x37, 0x35, 0x0b, 0xd3, 0x26, 0xa6, + 0x89, 0x30, 0xef, 0xc5, 0x61, 0xda, 0xcb, 0xda, 0x83, 0x9e, 0x32, 0xdb, 0xf3, 0x11, 0xdd, 0x99, + 0xe2, 0x8b, 0xe7, 0xf2, 0x7f, 0x01, 0x00, 0x00, 0xff, 0xff, 0xc2, 0x02, 0x37, 0x01, 0x0c, 0x11, + 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -910,6 +1052,9 @@ type QueryClient interface { // QueryRegisteredConsumerRewardDenoms returns a list of consumer reward // denoms that are registered QueryRegisteredConsumerRewardDenoms(ctx context.Context, in *QueryRegisteredConsumerRewardDenomsRequest, opts ...grpc.CallOption) (*QueryRegisteredConsumerRewardDenomsResponse, error) + // QueryProposedConsumerChainIDs returns the chain IDs of the proposed consumer chain addition proposals + // that are still in the voting period + QueryProposedConsumerChainIDs(ctx context.Context, in *QueryProposedChainIDsRequest, opts ...grpc.CallOption) (*QueryProposedChainIDsResponse, error) } type queryClient struct { @@ -992,6 +1137,15 @@ func (c *queryClient) QueryRegisteredConsumerRewardDenoms(ctx context.Context, i return out, nil } +func (c *queryClient) QueryProposedConsumerChainIDs(ctx context.Context, in *QueryProposedChainIDsRequest, opts ...grpc.CallOption) (*QueryProposedChainIDsResponse, error) { + out := new(QueryProposedChainIDsResponse) + err := c.cc.Invoke(ctx, "/interchain_security.ccv.provider.v1.Query/QueryProposedConsumerChainIDs", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // QueryServer is the server API for Query service. type QueryServer interface { // ConsumerGenesis queries the genesis state needed to start a consumer chain @@ -1016,6 +1170,9 @@ type QueryServer interface { // QueryRegisteredConsumerRewardDenoms returns a list of consumer reward // denoms that are registered QueryRegisteredConsumerRewardDenoms(context.Context, *QueryRegisteredConsumerRewardDenomsRequest) (*QueryRegisteredConsumerRewardDenomsResponse, error) + // QueryProposedConsumerChainIDs returns the chain IDs of the proposed consumer chain addition proposals + // that are still in the voting period + QueryProposedConsumerChainIDs(context.Context, *QueryProposedChainIDsRequest) (*QueryProposedChainIDsResponse, error) } // UnimplementedQueryServer can be embedded to have forward compatible implementations. @@ -1046,6 +1203,9 @@ func (*UnimplementedQueryServer) QueryThrottleState(ctx context.Context, req *Qu func (*UnimplementedQueryServer) QueryRegisteredConsumerRewardDenoms(ctx context.Context, req *QueryRegisteredConsumerRewardDenomsRequest) (*QueryRegisteredConsumerRewardDenomsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method QueryRegisteredConsumerRewardDenoms not implemented") } +func (*UnimplementedQueryServer) QueryProposedConsumerChainIDs(ctx context.Context, req *QueryProposedChainIDsRequest) (*QueryProposedChainIDsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method QueryProposedConsumerChainIDs not implemented") +} func RegisterQueryServer(s grpc1.Server, srv QueryServer) { s.RegisterService(&_Query_serviceDesc, srv) @@ -1195,6 +1355,24 @@ func _Query_QueryRegisteredConsumerRewardDenoms_Handler(srv interface{}, ctx con return interceptor(ctx, in, info, handler) } +func _Query_QueryProposedConsumerChainIDs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryProposedChainIDsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).QueryProposedConsumerChainIDs(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/interchain_security.ccv.provider.v1.Query/QueryProposedConsumerChainIDs", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).QueryProposedConsumerChainIDs(ctx, req.(*QueryProposedChainIDsRequest)) + } + return interceptor(ctx, in, info, handler) +} + var _Query_serviceDesc = grpc.ServiceDesc{ ServiceName: "interchain_security.ccv.provider.v1.Query", HandlerType: (*QueryServer)(nil), @@ -1231,6 +1409,10 @@ var _Query_serviceDesc = grpc.ServiceDesc{ MethodName: "QueryRegisteredConsumerRewardDenoms", Handler: _Query_QueryRegisteredConsumerRewardDenoms_Handler, }, + { + MethodName: "QueryProposedConsumerChainIDs", + Handler: _Query_QueryProposedConsumerChainIDs_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "interchain_security/ccv/provider/v1/query.proto", @@ -1765,6 +1947,101 @@ func (m *QueryRegisteredConsumerRewardDenomsResponse) MarshalToSizedBuffer(dAtA return len(dAtA) - i, nil } +func (m *QueryProposedChainIDsRequest) 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 *QueryProposedChainIDsRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryProposedChainIDsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *QueryProposedChainIDsResponse) 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 *QueryProposedChainIDsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryProposedChainIDsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ProposedChains) > 0 { + for iNdEx := len(m.ProposedChains) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.ProposedChains[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *ProposedChain) 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 *ProposedChain) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ProposedChain) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.ProposalID != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.ProposalID)) + i-- + dAtA[i] = 0x10 + } + if len(m.ChainID) > 0 { + i -= len(m.ChainID) + copy(dAtA[i:], m.ChainID) + i = encodeVarintQuery(dAtA, i, uint64(len(m.ChainID))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { offset -= sovQuery(v) base := offset @@ -1995,6 +2272,46 @@ func (m *QueryRegisteredConsumerRewardDenomsResponse) Size() (n int) { return n } +func (m *QueryProposedChainIDsRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *QueryProposedChainIDsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.ProposedChains) > 0 { + for _, e := range m.ProposedChains { + l = e.Size() + n += 1 + l + sovQuery(uint64(l)) + } + } + return n +} + +func (m *ProposedChain) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ChainID) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + if m.ProposalID != 0 { + n += 1 + sovQuery(uint64(m.ProposalID)) + } + return n +} + func sovQuery(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -3381,6 +3698,241 @@ func (m *QueryRegisteredConsumerRewardDenomsResponse) Unmarshal(dAtA []byte) err } return nil } +func (m *QueryProposedChainIDsRequest) 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 ErrIntOverflowQuery + } + 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: QueryProposedChainIDsRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryProposedChainIDsRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryProposedChainIDsResponse) 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 ErrIntOverflowQuery + } + 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: QueryProposedChainIDsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryProposedChainIDsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProposedChains", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ProposedChains = append(m.ProposedChains, ProposedChain{}) + if err := m.ProposedChains[len(m.ProposedChains)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ProposedChain) 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 ErrIntOverflowQuery + } + 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: ProposedChain: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ProposedChain: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ChainID", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + 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 ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ChainID = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ProposalID", wireType) + } + m.ProposalID = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ProposalID |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipQuery(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/ccv/provider/types/query.pb.gw.go b/x/ccv/provider/types/query.pb.gw.go index 1a4b418158..f16dc6181d 100644 --- a/x/ccv/provider/types/query.pb.gw.go +++ b/x/ccv/provider/types/query.pb.gw.go @@ -249,6 +249,24 @@ func local_request_Query_QueryRegisteredConsumerRewardDenoms_0(ctx context.Conte } +func request_Query_QueryProposedConsumerChainIDs_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryProposedChainIDsRequest + var metadata runtime.ServerMetadata + + msg, err := client.QueryProposedConsumerChainIDs(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_QueryProposedConsumerChainIDs_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryProposedChainIDsRequest + var metadata runtime.ServerMetadata + + msg, err := server.QueryProposedConsumerChainIDs(ctx, &protoReq) + return msg, metadata, err + +} + // RegisterQueryHandlerServer registers the http handlers for service Query to "mux". // UnaryRPC :call QueryServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. @@ -439,6 +457,29 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv }) + mux.Handle("GET", pattern_Query_QueryProposedConsumerChainIDs_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_QueryProposedConsumerChainIDs_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_QueryProposedConsumerChainIDs_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -640,6 +681,26 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie }) + mux.Handle("GET", pattern_Query_QueryProposedConsumerChainIDs_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_QueryProposedConsumerChainIDs_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_QueryProposedConsumerChainIDs_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -659,6 +720,8 @@ var ( pattern_Query_QueryThrottleState_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"interchain_security", "ccv", "provider", "throttle_state"}, "", runtime.AssumeColonVerbOpt(false))) pattern_Query_QueryRegisteredConsumerRewardDenoms_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"interchain_security", "ccv", "provider", "registered_consumer_reward_denoms"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_QueryProposedConsumerChainIDs_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"interchain_security", "ccv", "provider", "proposed_consumer_chains"}, "", runtime.AssumeColonVerbOpt(false))) ) var ( @@ -677,4 +740,6 @@ var ( forward_Query_QueryThrottleState_0 = runtime.ForwardResponseMessage forward_Query_QueryRegisteredConsumerRewardDenoms_0 = runtime.ForwardResponseMessage + + forward_Query_QueryProposedConsumerChainIDs_0 = runtime.ForwardResponseMessage ) diff --git a/x/ccv/types/expected_keepers.go b/x/ccv/types/expected_keepers.go index 5b2f3f7a53..10546dff21 100644 --- a/x/ccv/types/expected_keepers.go +++ b/x/ccv/types/expected_keepers.go @@ -15,6 +15,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" auth "github.com/cosmos/cosmos-sdk/x/auth/types" capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" + v1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" @@ -146,3 +147,7 @@ type ScopedKeeper interface { AuthenticateCapability(ctx sdk.Context, cap *capabilitytypes.Capability, name string) bool ClaimCapability(ctx sdk.Context, cap *capabilitytypes.Capability, name string) error } + +type GovKeeper interface { + GetProposal(ctx sdk.Context, proposalID uint64) (v1.Proposal, bool) +} From 91d804af7c136de354ad318412c9cf811a51056f Mon Sep 17 00:00:00 2001 From: bernd-m <43466467+bermuell@users.noreply.github.com> Date: Tue, 28 Nov 2023 15:16:56 +0100 Subject: [PATCH 16/19] fix: cli for consumer genesis transformation (#1458) Fix consumer genesis transformation cli for retry_delay_period --- app/consumer/genesis.go | 5 +++++ app/consumer/genesis_test.go | 2 ++ 2 files changed, 7 insertions(+) diff --git a/app/consumer/genesis.go b/app/consumer/genesis.go index 27292feaa1..f6641f45a7 100644 --- a/app/consumer/genesis.go +++ b/app/consumer/genesis.go @@ -50,6 +50,11 @@ func transform(jsonRaw []byte, ctx client.Context) (json.RawMessage, error) { return nil, fmt.Errorf("invalid source version. Unexpected element 'provider.consensus_state'") } + // Use DefaultRetryDelayPeriod if not set + if oldConsumerGenesis.Params.RetryDelayPeriod == 0 { + oldConsumerGenesis.Params.RetryDelayPeriod = types.DefaultRetryDelayPeriod + } + // Version 2 of provider genesis data fills up deprecated fields // ProviderClientState, ConsensusState and InitialValSet newGenesis := types.ConsumerGenesisState{ diff --git a/app/consumer/genesis_test.go b/app/consumer/genesis_test.go index f23e4c58d1..82292fbdbb 100644 --- a/app/consumer/genesis_test.go +++ b/app/consumer/genesis_test.go @@ -17,6 +17,7 @@ import ( app "github.com/cosmos/interchain-security/v3/app/consumer" consumerTypes "github.com/cosmos/interchain-security/v3/x/ccv/consumer/types" + ccvtypes "github.com/cosmos/interchain-security/v3/x/ccv/types" ) // Testdata mapping consumer genesis exports to a provider module version as @@ -210,4 +211,5 @@ func TestConsumerGenesisTransformationV2(t *testing.T) { require.Empty(t, consumerGenesis.InitialValSet) require.NotEmpty(t, consumerGenesis.Provider.InitialValSet) + require.Equal(t, consumerGenesis.Params.RetryDelayPeriod, ccvtypes.DefaultRetryDelayPeriod) } From b73884f1a8709d3091081d047065103283e64e68 Mon Sep 17 00:00:00 2001 From: Marius Poke Date: Wed, 29 Nov 2023 10:31:21 +0100 Subject: [PATCH 17/19] docs: update docs for jail throttling v2 (#1443) * refactor adr 002 for better understanding * refactor ADR 008 * update throttling params * add docstring to TestBasicSlashPacketThrottling * update features and releases * add comments to TestMultiConsumerSlashPacketThrottling * Update docs/docs/adrs/adr-008-throttle-retries.md Co-authored-by: MSalopek * add review suggestions * replace trailing with subsequent * add upcoming versions * add notes on backward compatibility --------- Co-authored-by: MSalopek --- FEATURES.md | 29 ++-- RELEASES.md | 28 +++- docs/docs/adrs/adr-002-throttle.md | 158 +++++++++++++-------- docs/docs/adrs/adr-008-throttle-retries.md | 104 +++++++++----- docs/docs/introduction/params.md | 34 +++-- tests/integration/throttle.go | 36 ++++- 6 files changed, 261 insertions(+), 128 deletions(-) diff --git a/FEATURES.md b/FEATURES.md index d536a58643..97e36f643e 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -2,17 +2,18 @@ The following table indicates the major ICS features available in the [currently active releases](./RELEASES.md#version-matrix): -| Feature | Release | `v1.2.0-multiden` | `v2.0.0` | `v2.1.0-provider-lsm` | `v2.4.0-lsm` | `v3.1.0` | -|---------|---------|------------------:|---------:|----------------------:|-------------:|---------:| -| [Channel initialization: new chains](https://github.com/cosmos/ibc/blob/main/spec/app/ics-028-cross-chain-validation/overview_and_basic_concepts.md#channel-initialization-new-chains) | | ✅ | ✅ | ✅ | ✅ | ✅ | -| [Validator set update](https://github.com/cosmos/ibc/blob/main/spec/app/ics-028-cross-chain-validation/overview_and_basic_concepts.md#validator-set-update) | | ✅ | ✅ | ✅ | ✅ | ✅ | -| [Completion of unbonding operations](https://github.com/cosmos/ibc/blob/main/spec/app/ics-028-cross-chain-validation/overview_and_basic_concepts.md#completion-of-unbonding-operations) | | ✅ | ✅ | ✅ | ✅ | ✅ | -| [Consumer initiated slashing](https://github.com/cosmos/ibc/blob/main/spec/app/ics-028-cross-chain-validation/overview_and_basic_concepts.md#consumer-initiated-slashing) | | ✅ | ✅ | ✅ | ✅ | ✅ | -| [Reward distribution](https://github.com/cosmos/ibc/blob/main/spec/app/ics-028-cross-chain-validation/overview_and_basic_concepts.md#reward-distribution) | | ✅ | ✅ | ✅ | ✅ | ✅ | -| [Consumer chain removal](https://github.com/cosmos/ibc/blob/main/spec/app/ics-028-cross-chain-validation/methods.md#consumer-chain-removal) | | ✅ | ✅ | ✅ | ✅ | ✅ | -| [Key assignment](https://github.com/cosmos/interchain-security/issues/26) | | ✅ | ✅ | ✅ | ✅ | ✅ | -| [Jail throttling](https://github.com/cosmos/interchain-security/issues/404) | | ✅ | ✅ | ✅ | ✅ | ✅ | -| [Soft opt-out](https://github.com/cosmos/interchain-security/issues/851) | | ✅ | ✅ | ✅ | ✅ | ✅ | -| [Channel initialization: existing chains](https://github.com/cosmos/ibc/blob/main/spec/app/ics-028-cross-chain-validation/overview_and_basic_concepts.md#channel-initialization-existing-chains) (aka [Standalone to consumer changeover](https://github.com/cosmos/interchain-security/issues/756))| | ❌ | ✅ | ✅ | ✅ | ✅ | -| [Cryptographic verification of equivocation](https://github.com/cosmos/interchain-security/issues/732) | | ❌ | ❌ | ❌ | ✅ | ❌ | - +| Feature | `v1.2.0-multiden` | `v2.0.0` | `v2.1.0-provider-lsm` | `v2.4.0-lsm` | `v3.1.0` | `v3.2.0` | `v3.3.0` | `v4.0.0` | +|---------|------------------:|---------:|----------------------:|-------------:|---------:|---------:|---------:|---------:| +| [Channel initialization: new chains](https://github.com/cosmos/ibc/blob/main/spec/app/ics-028-cross-chain-validation/overview_and_basic_concepts.md#channel-initialization-new-chains) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| [Validator set update](https://github.com/cosmos/ibc/blob/main/spec/app/ics-028-cross-chain-validation/overview_and_basic_concepts.md#validator-set-update) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| [Completion of unbonding operations](https://github.com/cosmos/ibc/blob/main/spec/app/ics-028-cross-chain-validation/overview_and_basic_concepts.md#completion-of-unbonding-operations) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| [Consumer initiated slashing](https://github.com/cosmos/ibc/blob/main/spec/app/ics-028-cross-chain-validation/overview_and_basic_concepts.md#consumer-initiated-slashing) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| [Reward distribution](https://github.com/cosmos/ibc/blob/main/spec/app/ics-028-cross-chain-validation/overview_and_basic_concepts.md#reward-distribution) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| [Consumer chain removal](https://github.com/cosmos/ibc/blob/main/spec/app/ics-028-cross-chain-validation/methods.md#consumer-chain-removal) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| [Key assignment](https://github.com/cosmos/interchain-security/issues/26) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| [Jail throttling](https://github.com/cosmos/interchain-security/issues/404) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| [Soft opt-out](https://github.com/cosmos/interchain-security/issues/851) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| [Channel initialization: existing chains](https://github.com/cosmos/ibc/blob/main/spec/app/ics-028-cross-chain-validation/overview_and_basic_concepts.md#channel-initialization-existing-chains) (aka [Standalone to consumer changeover](https://github.com/cosmos/interchain-security/issues/756)) | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| [Cryptographic verification of equivocation](https://github.com/cosmos/interchain-security/issues/732) | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ✅ | ✅ | +| [Jail throttling with retries](https://github.com/cosmos/interchain-security/issues/713) - consumer-side changes | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ | +| [Jail throttling with retries](https://github.com/cosmos/interchain-security/issues/713) - [provider-side changes](https://github.com/cosmos/interchain-security/issues/1102) | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | diff --git a/RELEASES.md b/RELEASES.md index ee382b0309..9716546410 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -7,6 +7,7 @@ - [Stable Release Policy](#stable-release-policy) - [Version Matrix](#version-matrix) - [Backwards Compatibility](#backwards-compatibility) + - [Notes](#notes) ## Semantic Versioning @@ -64,6 +65,8 @@ All missing minor release versions have been discontinued. | `v2.4.x-lsm` | June 09, 2024 | | `v3.1.x` | July 10, 2024 | | `v3.2.x` | July 10, 2024 | +| `v3.3.x` | July 10, 2024 | +| `v4.0.x` | TBA | **Note**: As of [Gaia v12.0.0](https://github.com/cosmos/gaia/releases/tag/v12.0.0), the Cosmos Hub uses a fork of Cosmos SDK ([v0.45.16-ics-lsm](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.45.16-ics-lsm)) @@ -83,6 +86,9 @@ Versions of Golang, IBC, Cosmos SDK and CometBFT used by ICS in the currently ac | [v2.1.0-provider-lsm](https://github.com/cosmos/interchain-security/releases/tag/v2.1.0-provider-lsm) | 1.19 | v4.4.2 | v0.45.16-ics-lsm | v0.34.28 | Provider only (Cosmos Hub specific) | | [v2.4.0-lsm](https://github.com/cosmos/interchain-security/releases/tag/v2.4.0-lsm) | 1.19 | v4.4.2 | v0.45.16-ics-lsm | v0.34.28 | Provider only (Cosmos Hub specific) | | [v3.1.0](https://github.com/cosmos/interchain-security/releases/tag/v3.1.0) | 1.20 | v7.1.0 | v0.47.3 | v0.37.2 | +| v3.2.0 | 1.20 | v7.3.0 | v0.47.5 | v0.37.2 | +| v3.3.0 | 1.20 | v7.3.0 | v0.47.5 | v0.37.2 | +| v4.0.0 | 1.20 | v7.3.0 | v0.47.5 | v0.37.2 | Provider on >= v4.0.0 backwards compatible with consumers >= v3.2.0 | **Note:** For a list of major ICS features available in the currently active releases, see [FEATURES.md](./FEATURES.md). @@ -92,8 +98,20 @@ A MAJOR version of ICS will always be backwards compatible with the previous MAJ The following table indicates the compatibility of currently active releases: -| Consumer | Provider | `v2.0.0` | `v2.1.0-provider-lsm` | `v2.4.0-lsm` | `v3.1.0` | -|----------|----------|--------:|----------------------:|----------------------:|---------:| -| `v1.2.0-multiden` || ✅ | ✅ | ✅ | ✅ | -| `v2.0.0` || ✅ | ✅ | ✅ | ✅ | -| `v3.1.0` || ✅ | ✅ | ✅ | ✅ | +| Consumer | Provider | `v2.0.0` | `v2.1.0-provider-lsm` | `v2.4.0-lsm` | `v3.1.0` | `v3.2.0` | `v3.3.0` | `v4.0.0` | +|----------|----------|----------|-----------------------|--------------|----------|----------|----------|----------| +| `v1.2.0-multiden` || ✅ (1) | ✅ (1) | ✅ (1) | ✅ (1),(2) | ✅ (1),(2) | ✅ (1),(2),(4) | ❌ | +| `v2.0.0` || ✅ | ✅ | ✅ | ✅ (2) | ✅ (2) | ✅ (2),(4) | ❌ | +| `v3.1.0` || ✅ | ✅ | ✅ | ✅ | ✅ | ✅ (4) | ❌ | +| `v3.2.0` || ✅ | ✅ | ✅ | ✅ | ✅ | ✅ (4) | ✅ | +| `v3.3.0` || ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| `v4.0.0` || ✅ (3) | ✅ (3) | ✅ (3) | ✅ (3) | ✅ | ✅ | ✅ | + +#### Notes + +The following adjustments must be made to the CCV consumer genesis state that is obtained from the provider chain after the spawn time is reached in order for the consumer chain to start without errors. + +- (1) Remove “preCCV” key +- (2) Remove “prehash_key_before_comparison” keys +- (3) Add .params.retry_delay_period: “3600s” +- (4) Use `interchain-security-cd genesis transform` to transform the consumer genesis file obtained from the provider. \ No newline at end of file diff --git a/docs/docs/adrs/adr-002-throttle.md b/docs/docs/adrs/adr-002-throttle.md index e60c619ca9..fe3d0a30b0 100644 --- a/docs/docs/adrs/adr-002-throttle.md +++ b/docs/docs/adrs/adr-002-throttle.md @@ -9,6 +9,7 @@ title: Jail Throttling * 2023-01-26: Initial Draft * 2023-02-07: Property refined, ADR ready to review/merge +* 2023-11-22: Refactor for better understanding ## Status @@ -16,58 +17,97 @@ Accepted ## Context -The CCV spec is based around the assumption that the provider binary and all consumers binaries are non-malicious, and follow the defined protocols. In practice, this assumption may not hold. A malicious consumer binary could potentially include code which is able to send many slash/jail packets at once to the provider. +The CCV spec is based around the assumption that the provider binary and all consumers binaries are non-malicious, and follow the defined protocols. +In practice, this assumption may not hold. +A malicious consumer binary could potentially include code which is able to send many slash/jail packets at once to the provider. -Before the throttling feature was implemented, the following attack was possible. Attacker(s) would create provider validators just below the provider's active set. Using a malicious consumer binary, slash packets would be relayed to the provider, that would slash/jail a significant portion (or all) of honest validator at once. Control of the provider would then pass over to the attackers' validators. This enables the attacker(s) to halt the provider. Or even worse, commit arbitrary state on the provider, potentially stealing all tokens bridged to the provider over IBC. +Before the throttling feature was implemented, the following attack was possible. +Attacker(s) would create provider validators just below the provider's active set. +Using a malicious consumer binary, slash packets would be relayed to the provider, that would slash/jail a significant portion (or all) of honest validator at once. +Control of the provider would then pass over to the attackers' validators. +This enables the attacker(s) to halt the provider. +Or even worse, commit arbitrary state on the provider, potentially stealing all tokens bridged to the provider over IBC. ## Decision -The throttling feature was designed to slow down the mentioned attack from above, allowing validators and the community to appropriately respond to the attack. Ie. this feature limits (enforced by on-chain params) the rate that the provider validator set can be jailed over time. +The throttling feature was designed to slow down the mentioned attack from above, allowing validators and the community to appropriately respond to the attack, +i.e., this feature limits (enforced by on-chain params) the rate that the provider validator set can be jailed over time. -### State Required - Slash Meter +### Required State -There exists one slash meter on the provider which stores an amount of voting power (integer), corresponding to an allowance of validators that can be jailed over time. This meter is initialized to a certain value on genesis, decremented by the amount of voting power jailed whenever a slash packet is handled, and periodically replenished as decided by on-chain params. +**Slash meter:** There exists one slash meter on the provider which stores an amount of voting power (integer), corresponding to an allowance of validators that can be jailed over time. +This meter is initialized to a certain value on genesis, decremented by the amount of voting power jailed whenever a slash packet is handled, and periodically replenished as decided by on-chain params. -### State Required - Global entry queue +**Global entry queue:** There exists a single queue which stores "global slash entries". +These entries allow the provider to appropriately handle slash packets sent from any consumer in FIFO ordering. +This queue is responsible for coordinating the order that slash packets (from multiple chains) are handled over time. -There exists a single queue which stores "global slash entries". These entries allow the provider to appropriately handle slash packets sent from any consumer in FIFO ordering. This queue is responsible for coordinating the order that slash packets (from multiple chains) are handled over time. +**Per-chain data queue:** For each established consumer, there exists a queue which stores "throttled packet data", +i.e.,pending slash packet data is queued together with pending VSC matured packet data in FIFO ordering. +Order is enforced by IBC sequence number. +These "per-chain" queues are responsible for coordinating the order that slash packets are handled in relation to VSC matured packets from the same chain. -### State Required - Per-chain data queue +_Note:_ The reason for a multiple-queue design is the _VSC Maturity and Slashing Order_ property (see [spec](https://github.com/cosmos/ibc/blob/main/spec/app/ics-028-cross-chain-validation/system_model_and_properties.md#consumer-initiated-slashing)). +There are other ways to ensure such a property (like a queue of linked lists, etc.), but the proposed approach seemed to be the most understandable and easiest to implement with a KV store. -For each established consumer, there exists a queue which stores "throttled packet data". Ie. pending slash packet data is queued together with pending VSC matured packet data in FIFO ordering. Order is enforced by IBC sequence number. These "per-chain" queues are responsible for coordinating the order that slash packets are handled in relation to VSC matured packets from the same chain. +### Params -### Reasoning - Multiple queues +`SlashMeterReplenishPeriod` -- the period after which the slash meter is replenished. -For reasoning on why this feature was implemented with multiple queues, see [spec](https://github.com/cosmos/ibc/blob/main/spec/app/ics-028-cross-chain-validation/system_model_and_properties.md#consumer-initiated-slashing). Specifically the section on _VSC Maturity and Slashing Order_. There are other ways to ensure such a property (like a queue of linked lists, etc.), but the implemented protocol seemed to be the most understandable and easiest to implement with a KV store. +`SlashMeterReplenishFraction` -- the portion (in range [0, 1]) of total voting power that is replenished to the slash meter when a replenishment occurs. This param also serves as a maximum fraction of total voting power that the slash meter can hold. -### Protocol Overview - OnRecvSlashPacket +`MaxThrottledPackets` -- the maximum amount of throttled slash or vsc matured packets that can be queued from a single consumer before the provider chain halts, it should be set to a large value. +This param would allow provider binaries to panic deterministically in the event that packet throttling results in a large amount of state-bloat. In such a scenario, packet throttling could prevent a violation of safety caused by a malicious consumer, at the cost of provider liveness. + +### Protocol Overview + +#### OnRecvSlashPacket Upon the provider receiving a slash packet from any of the established consumers during block execution, two things occur: 1. A global slash entry is queued. 2. The data of such a packet is added to the per-chain queue. -### Protocol Overview - OnRecvVSCMaturedPacket +#### OnRecvVSCMaturedPacket Upon the provider receiving a VSCMatured packet from any of the established consumers during block execution, the VSCMatured packet data is added to the per-chain queue. -### Endblocker Step 1 - Slash Meter Replenishment +#### Endblocker + +In the `EndBlock` of the provider CCV module, there are three actions performed: + +- replenish the slash meter; +- handle the leading `VSCMaturedPackets`; +- and handle the throttle queues. + +##### Slash Meter Replenishment -Once the slash meter becomes not full, it'll be replenished after `SlashMeterReplenishPeriod (param)` by incrementing the meter with its allowance for the replenishment block, where `allowance` = `SlashMeterReplenishFraction (param)` * `currentTotalVotingPower`. The slash meter will never exceed its current allowance (fn of the total voting power for the block) in value. Note a few things: +Once the slash meter becomes not full, it'll be replenished after `SlashMeterReplenishPeriod` by incrementing the meter with its allowance for the replenishment block, where `allowance` = `SlashMeterReplenishFraction` * `currentTotalVotingPower`. +The slash meter will never exceed its current allowance (function of the total voting power for the block) in value. -1. The slash meter can go negative in value, and will do so when handling a single slash packet that jails a validator with significant voting power. In such a scenario, the slash meter may take multiple replenishment periods to once again reach a positive value (or 0), meaning no other slash packets may be handled for multiple replenishment periods. -2. Total voting power of a chain changes over time, especially as validators are jailed. As validators are jailed, total voting power decreases, and so does the jailing allowance. See below for more detailed throttling property discussion. -3. The voting power allowance added to the slash meter during replenishment will always be greater than or equal to 1. If the `SlashMeterReplenishFraction (param)` is set too low, integer rounding will put this minimum value into effect. That is, if `SlashMeterReplenishFraction` * `currentTotalVotingPower` < 1, then the effective allowance would be 1. This min value of allowance ensures that there's some packets handled over time, even if that is a very long time. It's a crude solution to an edge case caused by too small of a replenishment fraction. +Note a few things: -The behavior described above is achieved by executing `CheckForSlashMeterReplenishment()` every endblock, BEFORE `HandleThrottleQueues()` is executed. +1. The slash meter can go negative in value, and will do so when handling a single slash packet that jails a validator with significant voting power. + In such a scenario, the slash meter may take multiple replenishment periods to once again reach a positive value (or 0), meaning no other slash packets may be handled for multiple replenishment periods. +2. Total voting power of a chain changes over time, especially as validators are jailed. + As validators are jailed, total voting power decreases, and so does the jailing allowance. + See below for more detailed throttling property discussion. +3. The voting power allowance added to the slash meter during replenishment will always be greater than or equal to 1. + If the `SlashMeterReplenishFraction` is set too low, integer rounding will put this minimum value into effect. + That is, if `SlashMeterReplenishFraction` * `currentTotalVotingPower` < 1, then the effective allowance would be 1. + This min value of allowance ensures that there's some packets handled over time, even if that is a very long time. + It's a crude solution to an edge case caused by too small of a replenishment fraction. -### Endblocker Step 2 - HandleLeadingVSCMaturedPackets +The behavior described above is achieved by executing `CheckForSlashMeterReplenishment()` every `EndBlock`, BEFORE `HandleThrottleQueues()` is executed. -Every block it is possible that VSCMatured packet data was queued before any slash packet data. Since this "leading" VSCMatured packet data does not have to be throttled (see _VSC Maturity and Slashing Order_), we can handle all VSCMatured packet data at the head of the queue, before the any throttling or packet data handling logic executes. +##### Handle Leading VSCMaturedPackets -### Endblocker Step 3 - HandleThrottleQueues +In every block, it is possible that `VSCMaturedPacket` data was queued before any slash packet data. +Since this "leading" VSCMatured packet data does not have to be throttled (see _VSC Maturity and Slashing Order_), we can handle all VSCMatured packet data at the head of the queue, before the any throttling or packet data handling logic executes. -Every endblocker the following pseudo-code is executed to handle data from the throttle queues. +##### Handle Throttle Queues + +In every `EndBlock`, the following logic is executed to handle data from the throttle queues. ```typescript meter := getSlashMeter() @@ -91,55 +131,59 @@ while meter.IsPositiveOrZero() && entriesExist() { ### System Properties -All CCV system properties should be maintained by implementing this feature, see: [CCV spec - Consumer Initiated Slashing](https://github.com/cosmos/ibc/blob/main/spec/app/ics-028-cross-chain-validation/system_model_and_properties.md#consumer-initiated-slashing). +All CCV system properties should be maintained by implementing this feature, see [CCV spec - Consumer Initiated Slashing](https://github.com/cosmos/ibc/blob/main/spec/app/ics-028-cross-chain-validation/system_model_and_properties.md#consumer-initiated-slashing). -One implementation-specific property introduced is that if any of the chain-specific packet data queues become larger than `MaxThrottledPackets (param)`, then the provider binary will panic, and the provider chain will halt. Therefore this param should be set carefully. See `SetThrottledPacketDataSize`. This behavior ensures that if the provider binaries are queuing up more packet data than machines can handle, the provider chain halts deterministically between validators. +One implementation-specific property introduced is that if any of the chain-specific packet data queues become larger than `MaxThrottledPackets`, then the provider binary will panic, and the provider chain will halt. +Therefore this param should be set carefully. See `SetThrottledPacketDataSize`. +This behavior ensures that if the provider binaries are queuing up more packet data than machines can handle, the provider chain halts deterministically between validators. ### Main Throttling Property Using on-chain params and the sub protocol defined, slash packet throttling is implemented such that the following property holds under some conditions. -First, we define the following: +First, we introduce the following definitions: * A consumer initiated slash attack "starts" when the first slash packet from such an attack is received by the provider. * The "initial validator set" for the attack is the validator set that existed on the provider when the attack started. -* There is a list of honest validators s.t if they are jailed, `X`% of the initial validator set will be jailed. +* There is a list of honest validators such that if they are jailed, `X`% of the initial validator set will be jailed. -For the following property to hold, these assumptions must be true: +For the Throttling Property to hold, the following assumptions must be true: 1. We assume the total voting power of the chain (as a function of delegations) does not increase over the course of the attack. 2. No validator has more than `SlashMeterReplenishFraction` of total voting power on the provider. -3. `SlashMeterReplenishFraction` is large enough that `SlashMeterReplenishFraction` * `currentTotalVotingPower` > 1. Ie. the replenish fraction is set high enough that we can ignore the effects of rounding. +3. `SlashMeterReplenishFraction` is large enough that `SlashMeterReplenishFraction` * `currentTotalVotingPower` > 1, + i.e., the replenish fraction is set high enough that we can ignore the effects of rounding. 4. `SlashMeterReplenishPeriod` is sufficiently longer than the time it takes to produce a block. _Note if these assumptions do not hold, throttling will still slow down the described attack in most cases, just not in a way that can be succinctly described. It's possible that more complex properties can be defined._ -Property: - -> The time it takes to jail/tombstone `X`% of the initial validator set will be greater than or equal to `(X * SlashMeterReplenishPeriod / SlashMeterReplenishFraction) - 2 * SlashMeterReplenishPeriod` - -Intuition: - -Let's use the following notation: - -* $C$: Number of replenishment cycles -* $P$: $\text{SlashMeterReplenishPeriod}$ -* $F$: $\text{SlashMeterReplenishFraction}$ -* $V_{\mathit{max}}$: Max power of a validator as a fraction of total voting power - -In $C$ number of replenishment cycles, the fraction of total voting power that can be removed, $a$, is $a \leq F \cdot C + V_{\mathit{max}}$ (where $V_{\mathit{max}}$ is there to account for the power fraction of the last validator removed, one which pushes the meter to the negative value). - -So, we need at least $C \geq \frac{a - V_{\mathit{max}}}{F}$ cycles to remove $a$ fraction of the total voting power. - -Since we defined the start of the attack to be the moment when the first slash request arrives, then $F$ fraction of the initial validator set can be jailed immediately. For the remaining $X - F$ fraction of the initial validator set to be jailed, it takes at least $C \geq \frac{(X - F) - V_{\mathit{max}}}{F}$ cycles. Using the assumption that $V_{\mathit{max}} \leq F$ (assumption 2), we get $C \geq \frac{X - 2F}{F}$ cycles. - -In order to execute $C$ cycles, we need $C \cdot P$ time. - -Thus, jailing the remaining $X - F$ fraction of the initial validator set corresponds to $\frac{P \cdot (X - 2F)}{F}$ time. - -In other words, the attack must take at least $\frac{P \cdot X}{F} - 2P$ time (in the units of replenish period $P$). - -This property is useful because it allows us to reason about the time it takes to jail a certain percentage of the initial provider validator set from consumer initiated slash requests. For example, if `SlashMeterReplenishFraction` is set to 0.06, then it takes no less than 4 replenishment periods to jail 33% of the initial provider validator set on the Cosmos Hub. Note that as of writing this on 11/29/22, the Cosmos Hub does not have a validator with more than 6% of total voting power. +**Throttling Property**: The time it takes to jail/tombstone `X`% of the initial validator set will be greater than or equal to +$\mathit{SlashMeterReplenishPeriod} \cdot \frac{X}{\mathit{SlashMeterReplenishFraction}} - 2 \cdot \mathit{SlashMeterReplenishPeriod}$. + +> **Intuition** +> +> Let's use the following notation: +> +> * $C$: Number of replenishment cycles +> * $P$: $\mathit{SlashMeterReplenishPeriod}$ +> * $F$: $\mathit{SlashMeterReplenishFraction}$ +> * $V_{\mathit{max}}$: Max power of a validator as a fraction of total voting power +> +> In $C$ number of replenishment cycles, the fraction of total voting power that can be removed, $a$, is $a \leq F \cdot C + V_{\mathit{max}}$ (where $V_{\mathit{max}}$ is there to account for the power fraction of the last validator removed, one which pushes the meter to the negative value). +> +> So, we need at least $C \geq \frac{a - V_{\mathit{max}}}{F}$ cycles to remove $a$ fraction of the total voting power. +> +> Since we defined the start of the attack to be the moment when the first slash request arrives, then $F$ fraction of the initial validator set can be jailed immediately. For the remaining $X - F$ fraction of the initial validator set to be jailed, it takes at least $C \geq \frac{(X - F) - V_{\mathit{max}}}{F}$ cycles. Using the assumption that $V_{\mathit{max}} \leq F$ (assumption 2), we get $C \geq \frac{X - 2F}{F}$ cycles. +> +> In order to execute $C$ cycles, we need $C \cdot P$ time. +> +> Thus, jailing the remaining $X - F$ fraction of the initial validator set corresponds to $\frac{P \cdot (X - 2F)}{F}$ time. +> +> In other words, the attack must take at least $\frac{P \cdot X}{F} - 2P$ time (in the units of replenish period $P$). + +This property is useful because it allows us to reason about the time it takes to jail a certain percentage of the initial provider validator set from consumer initiated slash requests. +For example, if `SlashMeterReplenishFraction` is set to `0.06`, then it takes no less than 4 replenishment periods to jail 33% of the initial provider validator set on the Cosmos Hub. +Note that as of writing this on 11/29/22, the Cosmos Hub does not have a validator with more than 6% of total voting power. Note also that 4 replenishment period is a worst case scenario that depends on well crafted attack timings. @@ -160,7 +204,9 @@ In summary, the throttling mechanism as designed has desirable properties whethe ### Negative -* Throttling introduces a vector for a malicious consumer chain to halt the provider, see issue below. However, this is sacrificing liveness in a edge case scenario for the sake of security. As an improvement, [using retries](https://github.com/cosmos/interchain-security/issues/713) would fully prevent this attack vector. +* Throttling introduces a vector for a malicious consumer chain to halt the provider, see issue below. + However, this is sacrificing liveness in a edge case scenario for the sake of security. + As an improvement, [using retries](https://github.com/cosmos/interchain-security/issues/713) would fully prevent this attack vector. ### Neutral diff --git a/docs/docs/adrs/adr-008-throttle-retries.md b/docs/docs/adrs/adr-008-throttle-retries.md index 1faf7bd7ee..7c5015d512 100644 --- a/docs/docs/adrs/adr-008-throttle-retries.md +++ b/docs/docs/adrs/adr-008-throttle-retries.md @@ -21,44 +21,66 @@ For context on why the throttling mechanism exists, see [ADR 002](./adr-002-thro Note the terms slash throttling and jail throttling are synonymous, since in replicated security a `SlashPacket` simply jails a validator for downtime infractions. -Currently the throttling mechanism is designed so that provider logic (slash meter, etc.) dictates how many slash packets can be handled over time. Throttled slash packets are persisted on the provider, leading to multiple possible issues. Namely: +Currently the throttling mechanism is designed so that provider logic (slash meter, etc.) dictates how many `SlashPackets` can be handled over time. +Throttled `SlashPackets` are persisted on the provider, leading to multiple possible issues. Namely: -* If slash or vsc matured packets are actually throttled/queued on the provider, state can grow and potentially lead to a DoS attack. We have short term solutions around this, but overall they come with their own weaknesses. See [#594](https://github.com/cosmos/interchain-security/issues/594). -* If a jailing attack described in [ADR 002](adr-002-throttle.md) were actually to be carried out with the current throttling design, we'd likely have to halt the provider, and perform an emergency upgrade and/or migration to clear the queues of slash packets that were deemed to be malicious. Alternatively, validators would just have to _tough it out_ and wait for the queues to clear, during which all/most validators would be jailed. Right after being jailed, vals would have to unjail themselves promptly to ensure safety. The synchronous coordination required to maintain safety in such a scenario is not ideal. +* If `SlashPackets` or `VSCMaturedPackets` are actually throttled/queued on the provider, state can grow and potentially lead to a DoS attack. + We have short term solutions around this, but overall they come with their own weaknesses. + See [#594](https://github.com/cosmos/interchain-security/issues/594). +* If a jailing attack described in [ADR 002](adr-002-throttle.md) were actually to be carried out with the current throttling design, we'd likely have to halt the provider, and perform an emergency upgrade and/or migration to clear the queues of `SlashPackets` that were deemed to be malicious. + Alternatively, validators would just have to _tough it out_ and wait for the queues to clear, during which all/most validators would be jailed. + Right after being jailed, validators would have to unjail themselves promptly to ensure safety. + The coordination required to maintain safety in such a scenario is not ideal. -So what's the solution? We can improve the throttling mechanism to instead queue/persist relevant data on each consumer, and have consumers retry slash requests as needed. +As as solution, we can improve the throttling mechanism to instead queue/persist relevant data on each consumer, and have consumers retry slash requests as needed. ## Decision ### Consumer changes -Note the consumer already queues up both slash and vsc matured packets via `AppendPendingPacket`. Those packets are dequeued every endblock in `SendPackets` and sent to the provider. +Note the consumer already queues up both `SlashPackets` and `VSCMaturedPackets` via `AppendPendingPacket`. +Those packets are dequeued in every `EndBlock` in `SendPackets` and sent to the provider. -Instead, we will now introduce the following logic on endblock: +Instead, we will now introduce the following logic on `EndBlock`: -* Slash packets will always be sent to the provider once they're at the head of the queue. However, once sent, the consumer will not send any trailing vsc matured packets from the queue until the provider responds with an ack that the slash packet has been handled (ie. val was jailed). That is, slash packets block the sending of trailing vsc matured packets in the consumer queue. -* If two slash packets are at the head of the queue, the consumer will send the first slash packet, and then wait for a success ack from the provider before sending the second slash packet. This seems like it'd simplify implementation. -* VSC matured packets at the head of the queue (ie. NOT trailing a slash packet) can be sent immediately, and do not block any other packets in the queue, since the provider always handles them immediately. +* Slash packets will always be sent to the provider once they're at the head of the queue. + However, once sent, the consumer will not send any subsequent `VSCMaturedPackets` from the queue until the provider responds with an acknowledgement that the sent `SlashPacket` has been handled, i.e., validator was jailed. + That is, `SlashPackets` block the sending of subsequent `VSCMaturedPackets` in the consumer queue. +* If two `SlashPackets` are at the head of the queue, the consumer will send the first `SlashPacket`, and then wait for a success acknowledgement from the provider before sending the second `SlashPacket`. + This seems like it'd simplify implementation. +* `VSCMaturedPackets` at the head of the queue (i.e., NOT following a `SlashPacket`) can be sent immediately, and do not block any other packets in the queue, since the provider always handles them immediately. -To prevent the provider from having to keep track of what slash packets have been rejected, the consumer will have to retry the sending of slash packets over some period of time. This can be achieved with an on-chain consumer param. The suggested param value would probably be 1/2 of the provider's `SlashMeterReplenishmentPeriod`, although it doesn't matter too much as long as the param value is sane. +To prevent the provider from having to keep track of what `SlashPackets` have been rejected, the consumer will have to retry the sending of `SlashPackets` over some period of time. +This can be achieved with an on-chain consumer param, i.e., `RetryDelayPeriod`. +To reduce the amount of redundant re-sends, we recommend setting `RetryDelayPeriod ~ SlashMeterReplenishmentPeriod`, i.e., waiting for the provider slash meter to be replenished before resending the rejected `SlashPacket`. -Note to prevent weird edge case behavior, a retry would not be attempted until either a success ack or failure ack has been recv from the provider. +Note to prevent weird edge case behavior, a retry would not be attempted until either a success or failure acknowledgement has been received from the provider. -With the behavior described, we maintain very similar behavior to the current throttling mechanism regarding the timing that slash and vsc matured packets are handled on the provider. Obviously the queueing and blocking logic is moved, and the two chains would have to send more messages between one another (only in the case the throttling mechanism is triggered). +With the behavior described, we maintain very similar behavior to the previous throttling mechanism regarding the timing that `SlashPackets` and `VSCMaturedPackets` are handled on the provider. +Obviously the queueing and blocking logic is moved, and the two chains would have to send more messages between one another (only in the case the throttling mechanism is triggered). -In the normal case, when no or a few slash packets are being sent, the VSCMaturedPackets will not be delayed, and hence unbonding will not be delayed. +In the normal case, when no or a few `SlashPackets` are being sent, the `VSCMaturedPackets` will not be delayed, and hence unbonding will not be delayed. -For implementation of this design, see [throttle_retry.go](../../../x/ccv/consumer/keeper/throttle_retry.go). +For the implementation of this design, see [throttle_retry.go](https://github.com/cosmos/interchain-security/blob/fec3eccad59416cbdb6844e279f59e3f81242888/x/ccv/consumer/keeper/throttle_retry.go). -### Consumer pending packets storage optimization +#### Consumer pending packets storage optimization -In addition to the mentioned consumer changes above. An optimization will need to be made to the consumer's pending packets storage to properly implement the feature from this ADR. +In addition to the mentioned consumer changes, an optimization will need to be made to the consumer's pending packets storage to properly implement the feature from this ADR. -The consumer ccv module previously queued "pending packets" to be sent on each endblocker in [SendPackets](https://github.com/cosmos/interchain-security/blob/3bc4e7135066d848aac60b0787364c07157fd36d/x/ccv/consumer/keeper/relay.go#L178). These packets are queued in state with a protobuf list of `ConsumerPacketData`. For a single append operation, the entire list is deserialized, then a packet is appended to that list, and the list is serialized again. See older version of [AppendPendingPacket](https://github.com/cosmos/interchain-security/blob/05c2dae7c6372b1252b9e97215d07c6aa7618f33/x/ccv/consumer/keeper/keeper.go#L606). That is, a single append operation has O(N) complexity, where N is the size of the list. +The consumer ccv module previously queued "pending packets" to be sent in each `EndBlock` in [SendPackets](https://github.com/cosmos/interchain-security/blob/3bc4e7135066d848aac60b0787364c07157fd36d/x/ccv/consumer/keeper/relay.go#L178). +These packets are queued in state with a protobuf list of `ConsumerPacketData`. +For a single append operation, the entire list is deserialized, then a packet is appended to that list, and the list is serialized again. +See older version of [AppendPendingPacket](https://github.com/cosmos/interchain-security/blob/05c2dae7c6372b1252b9e97215d07c6aa7618f33/x/ccv/consumer/keeper/keeper.go#L606). +That is, a single append operation has O(N) complexity, where N is the size of the list. -This poor append performance isn't a problem when the pending packets list is small. But with this ADR being implemented, the pending packets list could potentially grow to the order of thousands of entries, in the scenario that a slash packet is bouncing. +This poor append performance isn't a problem when the pending packets list is small. +But with this ADR being implemented, the pending packets list could potentially grow to the order of thousands of entries when `SlashPackets` need to be resent. -We can improve the append time for this queue by converting it from a protobuf-esq list, to a queue implemented with sdk-esq code. The idea is to persist an uint64 index that will be incremented each time you queue up a packet. You can think of this as storing the tail of the queue. Then, packet data will be keyed by that index, making the data naturally ordered byte-wise for sdk's iterator. The index will also be stored in the packet data value bytes, so that the index can later be used to delete certain packets from the queue. +We can improve the append time for this queue by converting it from a protobuf-esq list, to a queue implemented with sdk-esq code. +The idea is to persist an uint64 index that will be incremented each time you queue up a packet. +You can think of this as storing the tail of the queue. +Then, packet data will be keyed by that index, making the data naturally ordered byte-wise for sdk's iterator. +The index will also be stored in the packet data value bytes, so that the index can later be used to delete certain packets from the queue. Two things are achieved with this approach: @@ -67,33 +89,44 @@ Two things are achieved with this approach: ### Provider changes -The main change needed for the provider is the removal of queuing logic for slash and vsc matured packets upon being received. +The main change needed for the provider is the removal of queuing logic for `SlashPackets` and `VSCMaturedPackets` upon being received. -Instead, the provider will consult the slash meter to determine if a slash packet can be handled immediately. If not, the provider will return an ack message to the consumer communicating that the slash packet could not be handled, and needs to be sent again in the future (retried). +Instead, the provider will consult the slash meter to determine if a `SlashPacket` can be handled immediately. +If not, the provider will return an acknowledgement message to the consumer communicating that the `SlashPacket` could not be handled, and needs to be sent again in the future (retried). -VSCMatured packets will always be handled immediately upon being received by the provider. +`VSCMaturedPackets` will always be handled immediately upon being received by the provider. Note [spec](https://github.com/cosmos/ibc/blob/main/spec/app/ics-028-cross-chain-validation/system_model_and_properties.md#consumer-initiated-slashing). Specifically the section on _VSC Maturity and Slashing Order_. Previously the onus was on the provider to maintain this property via queuing packets and handling them FIFO. -Now this property will be maintained by the consumer sending packets in the correct order, and blocking the sending of VSCMatured packets as needed. Then, the ordered IBC channel will ensure that Slash/VSCMatured packets are received in the correct order on the provider. +Now this property will be maintained by the consumer sending packets in the correct order, and blocking the sending of `VSCMaturedPackets` as needed. Then, the ordered IBC channel will ensure that `SlashPackets` and `VSCMaturedPackets` are received in the correct order on the provider. -The provider's main responsibility regarding throttling will now be to determine if a recv slash packet can be handled via slash meter etc., and appropriately ack to the sending consumer. +The provider's main responsibility regarding throttling will now be to determine if a received `SlashPacket` can be handled via slash meter etc., and appropriately acknowledge to the sending consumer. -### Why the provider can handle VSCMatured packets immediately +#### Handling `VSCMaturedPackets` immediately -First we answer, what does a VSCMatured packet communicate to the provider? A VSCMatured packet communicates that a VSC has been applied to a consumer long enough that infractions committed on the consumer could have been submitted. +#### Why the provider can handle VSCMatured packets immediately -If the consumer is following the queuing/blocking protocol described. No bad behavior occurs, `VSC Maturity and Slashing Order` property is maintained. +A `VSCMaturedPacket` communicates to the provider that sufficient time passed on the consumer since the corresponding `VSCPacket` has been applied (on the consumer) such that infractions committed on the consumer could have been submitted. -If a consumer sends VSCMatured packets too leniently: The consumer is malicious and sending duplicate vsc matured packets, or sending the packets sooner than the ccv protocol specifies. In this scenario, the provider needs to handle vsc matured packets immediately to prevent DOS, state bloat, or other issues. The only possible negative outcome is that the malicious consumer may not be able to jail a validator who should have been jailed. The malicious behavior only creates a negative outcome for the chain that is being malicious. +If the consumer is following the queuing/blocking protocol described, then no bad behavior occurs and the _VSC Maturity and Slashing Order_ property is maintained. -If a consumer blocks the sending of VSCMatured packets: The consumer is malicious and blocking vsc matured packets that should have been sent. This will block unbonding only up until the VSC timeout period has elapsed. At that time, the consumer is removed. Again the malicious behavior only creates a negative outcome for the chain that is being malicious. +If a consumer sends `VSCMaturedPackets` too leniently -- the consumer is malicious and sends duplicate `VSCMaturedPackets`, or sends the packets sooner than the CCV protocol specifies -- then the provider needs to handle `VSCMaturedPackets` immediately to prevent DOS, state bloat, or other issues. +The only possible negative outcome is that the malicious consumer may not be able to jail a validator who should have been jailed. +The malicious behavior only creates a negative outcome for the consumer chain that is being malicious. + +If a consumer blocks the sending of `VSCMaturedPackets`, then unbonding operations on the provider will be delayed, but only until the VSC timeout period has elapsed. +At that time, the consumer is removed. +Again the malicious behavior only creates a negative outcome for the consumer chain that is being malicious. ### Splitting of PRs and Upgrade Order -This feature will implement consumer changes in [#1024](https://github.com/cosmos/interchain-security/pull/1024). Note these changes should be deployed to prod for all consumers before the provider changes are deployed to prod. That is the consumer changes in #1024 are compatible with the current ("v1") provider implementation of throttling that's running on the Cosmos Hub as of July 2023. +This feature will implement consumer changes in [#1024](https://github.com/cosmos/interchain-security/pull/1024). + +❗***These changes should be deployed to production for all consumers before the provider changes are deployed to production.*** + +In other words, the consumer changes in [#1024](https://github.com/cosmos/interchain-security/pull/1024) are compatible with the current ("v1") provider implementation of throttling that's running on the Cosmos Hub as of July 2023. -Once all consumers have deployed the changes in #1024, the provider changes from (TBD) can be deployed to prod, fully enabling v2 throttling. +Once all consumers have deployed the changes in #1024, the provider changes from [#1321](https://github.com/cosmos/interchain-security/pull/1321) can be deployed to production, fully enabling v2 throttling. ## Consequences @@ -101,12 +134,13 @@ Once all consumers have deployed the changes in #1024, the provider changes from * Consumers still aren't trustless, but the provider is now less susceptible to mismanaged or malicious consumers. * Recovering from the "jailing attack" is more elegant. * Some issues like [#1001](https://github.com/cosmos/interchain-security/issues/1001) will now be handled implicitly by the improved throttling mechanism. -* Slash and vsc matured packets can be handled immediately once recv by the provider if the slash meter allows. -* In general, we reduce the amount of computation that happens in the provider end-blocker. +* `SlashPackets` and `VSCMaturedPackets` can be handled immediately once received by the provider if the slash meter allows. +* In general, we reduce the amount of computation that happens in the provider `EndBlock`. ### Positive -* We no longer have to reason about a "global queue" and a "chain specific queue", and keeping those all in-sync. Now slash and vsc matured packet queuing is handled on each consumer individually. +* We no longer have to reason about a "global queue" and a "chain specific queue", and keeping those all in-sync. + Now `SlashPackets` and `VSCMaturedPackets` queuing is handled on each consumer individually. * Due to the above, the throttling protocol becomes less complex overall. * We no longer have to worry about throttle related DoS attack on the provider, since no queuing exists on the provider. @@ -117,7 +151,7 @@ Once all consumers have deployed the changes in #1024, the provider changes from ### Neutral -* Core throttling logic on the provider remains unchanged, ie. slash meter, replenishment cycles, etc. +* Core throttling logic on the provider remains unchanged, i.e., slash meter, replenishment cycles, etc. ## References diff --git a/docs/docs/introduction/params.md b/docs/docs/introduction/params.md index 9f68cc706c..9b89a1d038 100644 --- a/docs/docs/introduction/params.md +++ b/docs/docs/introduction/params.md @@ -14,10 +14,10 @@ The parameters necessary for Interchain Security (ICS) are defined in ICS relies on the following time-based parameters. ### ProviderUnbondingPeriod -is the unbonding period on the provider chain as configured during chain genesis. This parameter can later be changed via governance. +`ProviderUnbondingPeriod` is the unbonding period on the provider chain as configured during chain genesis. This parameter can later be changed via governance. ### ConsumerUnbondingPeriod -is the unbonding period on the consumer chain. +`ConsumerUnbondingPeriod` is the unbonding period on the consumer chain. :::info `ConsumerUnbondingPeriod` is set via the `ConsumerAdditionProposal` governance proposal to add a new consumer chain. @@ -34,7 +34,7 @@ Unbonding operations (such as undelegations) are completed on the provider only ### TrustingPeriodFraction -is used to calculate the `TrustingPeriod` of created IBC clients on both provider and consumer chains. +`TrustingPeriodFraction` is used to calculate the `TrustingPeriod` of created IBC clients on both provider and consumer chains. Setting `TrustingPeriodFraction` to `0.5` would result in the following: @@ -49,7 +49,7 @@ Note that a light clients must be updated within the `TrustingPeriod` in order t For more details, see the [IBC specification of Tendermint clients](https://github.com/cosmos/ibc/blob/main/spec/client/ics-007-tendermint-client/README.md). ### CCVTimeoutPeriod -is the period used to compute the timeout timestamp when sending IBC packets. +`CCVTimeoutPeriod` is the period used to compute the timeout timestamp when sending IBC packets. For more details, see the [IBC specification of Channel & Packet Semantics](https://github.com/cosmos/ibc/blob/main/spec/core/ics-004-channel-and-packet-semantics/README.md#sending-packets). @@ -62,14 +62,14 @@ CCVTimeoutPeriod may have different values on the provider and consumer chains. - `CCVTimeoutPeriod` on the consumer is initial set via the `ConsumerAdditionProposal` ### InitTimeoutPeriod -is the maximum allowed duration for CCV channel initialization to execute. +`InitTimeoutPeriod` is the maximum allowed duration for CCV channel initialization to execute. For any consumer chain, if the CCV channel is not established within `InitTimeoutPeriod` then the consumer chain will be removed and therefore will not be secured by the provider chain. The countdown starts when the `spawn_time` specified in the `ConsumerAdditionProposal` is reached. -### `VscTimeoutPeriod` -is the provider-side param that enables the provider to timeout VSC packets even when a consumer chain is not live. +### VscTimeoutPeriod +`VscTimeoutPeriod` is the provider-side param that enables the provider to timeout VSC packets even when a consumer chain is not live. If the `VscTimeoutPeriod` is ever reached for a consumer chain that chain will be considered not live and removed from interchain security. :::tip @@ -77,10 +77,10 @@ If the `VscTimeoutPeriod` is ever reached for a consumer chain that chain will b ::: ### BlocksPerDistributionTransmission -is the number of blocks between rewards transfers from the consumer to the provider. +`BlocksPerDistributionTransmission` is the number of blocks between rewards transfers from the consumer to the provider. ### TransferPeriodTimeout -is the period used to compute the timeout timestamp when sending IBC transfer packets from a consumer to the provider. +`TransferPeriodTimeout` is the period used to compute the timeout timestamp when sending IBC transfer packets from a consumer to the provider. If this timeout expires, then the transfer is attempted again after `BlocksPerDistributionTransmission` blocks. - `TransferPeriodTimeout` on the consumer is initial set via the `ConsumerAdditionProposal` gov proposal to add the consumer @@ -90,16 +90,26 @@ If this timeout expires, then the transfer is attempted again after `BlocksPerDi ## Slash Throttle Parameters ### SlashMeterReplenishPeriod -exists on the provider such that once the slash meter becomes not-full, the slash meter is replenished after this period has elapsed. +`SlashMeterReplenishPeriod` exists on the provider such that once the slash meter becomes not-full, the slash meter is replenished after this period has elapsed. The meter is replenished to an amount equal to the slash meter allowance for that block, or `SlashMeterReplenishFraction * CurrentTotalVotingPower`. ### SlashMeterReplenishFraction -exists on the provider as the portion (in range [0, 1]) of total voting power that is replenished to the slash meter when a replenishment occurs. +`SlashMeterReplenishFraction` exists on the provider as the portion (in range [0, 1]) of total voting power that is replenished to the slash meter when a replenishment occurs. This param also serves as a maximum fraction of total voting power that the slash meter can hold. The param is set/persisted as a string, and converted to a `sdk.Dec` when used. ### MaxThrottledPackets -exists on the provider as the maximum amount of throttled slash or vsc matured packets that can be queued from a single consumer before the provider chain halts, it should be set to a large value. + +`MaxThrottledPackets` exists on the provider as the maximum amount of throttled slash or vsc matured packets that can be queued from a single consumer before the provider chain halts, it should be set to a large value. This param would allow provider binaries to panic deterministically in the event that packet throttling results in a large amount of state-bloat. In such a scenario, packet throttling could prevent a violation of safety caused by a malicious consumer, at the cost of provider liveness. + +:::info +`MaxThrottledPackets` was deprecated in ICS versions >= v3.2.0 due to the implementation of [ADR-008](../adrs/adr-008-throttle-retries.md). +::: + +### RetryDelayPeriod + +`RetryDelayPeriod` exists on the consumer for **ICS versions >= v3.2.0** (introduced by the implementation of [ADR-008](../adrs/adr-008-throttle-retries.md)) and is the period at which the consumer retries to send a `SlashPacket` that was rejected by the provider. + diff --git a/tests/integration/throttle.go b/tests/integration/throttle.go index 71ee3f606b..f186077a07 100644 --- a/tests/integration/throttle.go +++ b/tests/integration/throttle.go @@ -30,10 +30,34 @@ func (s *CCVTestSuite) TestBasicSlashPacketThrottling() { expectedAllowanceAfterFirstSlash int64 expectedReplenishesTillPositive int }{ - {"0.2", 800, -200, 600, 1}, - {"0.1", 400, -600, 300, 3}, // 600/300 = 2, so 3 replenishes to reach positive - {"0.05", 200, -800, 150, 6}, - {"0.01", 40, -960, 30, 33}, // 960/30 = 32, so 33 replenishes to reach positive + { + "0.2", + 800, // replenishFraction * totalPower: 0.2 * 4000 + -200, // expectedMeterBeforeFirstSlash - power(V0): 800 - 1000 + 600, // replenishFraction * newTotalPower: 0.2 * 3000 + 1, // ceil((200+1)/600) + }, + { + "0.1", + 400, // replenishFraction * totalPower: 0.1 * 4000 + -600, // expectedMeterBeforeFirstSlash - power(V0): 400 - 1000 + 300, // replenishFraction * newTotalPower: 0.1 * 3000 + 3, // ceil((600+1)/300) + }, + { + "0.05", + 200, // replenishFraction * totalPower: 0.05 * 4000 + -800, // expectedMeterBeforeFirstSlash - power(V0): 200 - 1000 + 150, // replenishFraction * newTotalPower: 0.05 * 3000 + 6, // ceil((800+1)/150) + }, + { + "0.01", + 40, // replenishFraction * totalPower: 0.01 * 4000 + -960, // expectedMeterBeforeFirstSlash - power(V0): 40 - 1000 + 30, // replenishFraction * newTotalPower: 0.01 * 3000 + 33, // ceil((960+1)/30) + }, } for _, tc := range testCases { @@ -209,10 +233,10 @@ func (s *CCVTestSuite) TestMultiConsumerSlashPacketThrottling() { s.confirmValidatorJailed(valsToSlash[0], true) // Packets were bounced for the second and third consumers. - s.confirmValidatorNotJailed(valsToSlash[1], 1000) + s.confirmValidatorNotJailed(valsToSlash[1], 1000) // each validator has 1000 power from the setup s.confirmValidatorNotJailed(valsToSlash[2], 1000) - // Total power is now 3000 + // Total power is now 3000 (as one validator was jailed) s.Require().Equal(int64(3000), providerStakingKeeper.GetLastTotalPower(s.providerCtx()).Int64()) From fe918ae8a977eee3e82f10adae8359a2ea849def Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Thu, 30 Nov 2023 22:05:34 +0100 Subject: [PATCH 18/19] doc: update ADR-005 (#1389) (#1392) update ADR Co-authored-by: Marius Poke --- .../adrs/adr-005-cryptographic-equivocation-verification.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/docs/adrs/adr-005-cryptographic-equivocation-verification.md b/docs/docs/adrs/adr-005-cryptographic-equivocation-verification.md index 8bd68dfcaf..d4f9bd2bf8 100644 --- a/docs/docs/adrs/adr-005-cryptographic-equivocation-verification.md +++ b/docs/docs/adrs/adr-005-cryptographic-equivocation-verification.md @@ -8,6 +8,7 @@ title: Cryptographic verification of equivocation evidence * 5/1/2023: First draft * 7/23/2023: Add light client attacks handling * 9/6/2023: Add double signing attacks handling +* 11/3/2023: Update limitations to clarify amnesia attacks are ignored ## Status @@ -137,7 +138,7 @@ either using its infraction height or its unsigned timestamp. Note that changes The underlying reason is that a malicious validator could take advantage of getting tombstoned to avoid being slashed on the provider ([see comment](https://github.com/cosmos/interchain-security/pull/1232#issuecomment-1693127641)). -- Currently, the endpoint can only handle "equivocation" light client attacks. This is because the "lunatic" attacks require the endpoint to possess the ability to dissociate which header is conflicted or trusted upon receiving a misbehavior message. Without this information, it's not possible to define the Byzantine validators from the conflicting headers (see [comment](https://github.com/cosmos/interchain-security/pull/826#discussion_r1268668684)). +- Currently, the endpoint can only handle _equivocation_ light client attacks. This is because the _lunatic_ attacks require the endpoint to possess the ability to dissociate which header is conflicted or trusted upon receiving a misbehavior message. Without this information, it's not possible to extract the Byzantine validators from the conflicting headers (see [comment](https://github.com/cosmos/interchain-security/pull/826#discussion_r1268668684)). In addition, "amnesia" attacks are ignored, similar to CometBFT (see [ADR-056](https://github.com/cometbft/cometbft/blob/main/docs/architecture/tendermint-core/adr-056-light-client-amnesia-attacks.md#decision)). ## Consequences From d5310b8940ae91fff05360619244525dc13581e5 Mon Sep 17 00:00:00 2001 From: Marius Poke Date: Fri, 1 Dec 2023 12:26:39 +0100 Subject: [PATCH 19/19] fix!: improve message validation (#1460) * validate ValidatorSetChangePacketData * update ValidateBasic for ValidatorSetChangePacketData * update ConsumerPacketData validation * fix TestConsumerPacketSendExpiredClient * update TestOnRecvSlashPacketErrors * fix TestQueueAndSendSlashPacket * remove TODO * nit: validate MsgAssignConsumerKey * add changelog entries * fix linter * fix gosec * rename ValidateBasic to Validate (IBC packets) * Update x/ccv/types/wire.go Co-authored-by: Simon Noetzlin * revert SlashPacketData address validation --------- Co-authored-by: Simon Noetzlin --- .../bug-fixes/1460-msg-validation.md | 3 + .../state-breaking/1460-msg-validation.md | 3 + tests/integration/expired_client.go | 2 +- tests/integration/slashing.go | 162 ++++++++---------- tests/integration/throttle.go | 9 +- tests/integration/valset_update.go | 15 +- x/ccv/consumer/ibc_module.go | 44 +++-- x/ccv/consumer/keeper/relay.go | 11 +- x/ccv/consumer/keeper/relay_test.go | 55 ++++-- x/ccv/provider/ibc_module.go | 68 ++++++-- x/ccv/provider/keeper/relay.go | 34 ++-- x/ccv/provider/keeper/relay_test.go | 43 ++--- x/ccv/provider/types/errors.go | 2 +- x/ccv/provider/types/msg.go | 5 +- x/ccv/types/wire.go | 50 ++++-- x/ccv/types/wire_test.go | 68 +++++++- 16 files changed, 362 insertions(+), 212 deletions(-) create mode 100644 .changelog/unreleased/bug-fixes/1460-msg-validation.md create mode 100644 .changelog/unreleased/state-breaking/1460-msg-validation.md diff --git a/.changelog/unreleased/bug-fixes/1460-msg-validation.md b/.changelog/unreleased/bug-fixes/1460-msg-validation.md new file mode 100644 index 0000000000..46d18bd4c9 --- /dev/null +++ b/.changelog/unreleased/bug-fixes/1460-msg-validation.md @@ -0,0 +1,3 @@ +- Improve validation of IBC packet data and provider messages. Also, + enable the provider to validate consumer packets before handling them. + ([\#1460](https://github.com/cosmos/interchain-security/pull/1460)) \ No newline at end of file diff --git a/.changelog/unreleased/state-breaking/1460-msg-validation.md b/.changelog/unreleased/state-breaking/1460-msg-validation.md new file mode 100644 index 0000000000..46d18bd4c9 --- /dev/null +++ b/.changelog/unreleased/state-breaking/1460-msg-validation.md @@ -0,0 +1,3 @@ +- Improve validation of IBC packet data and provider messages. Also, + enable the provider to validate consumer packets before handling them. + ([\#1460](https://github.com/cosmos/interchain-security/pull/1460)) \ No newline at end of file diff --git a/tests/integration/expired_client.go b/tests/integration/expired_client.go index f196f16ce1..81654895eb 100644 --- a/tests/integration/expired_client.go +++ b/tests/integration/expired_client.go @@ -129,7 +129,7 @@ func (s *CCVTestSuite) TestConsumerPacketSendExpiredClient() { // try to send slash packet for downtime infraction addr := ed25519.GenPrivKey().PubKey().Address() - val := abci.Validator{Address: addr} + val := abci.Validator{Address: addr, Power: 1} consumerKeeper.QueueSlashPacket(s.consumerCtx(), val, 2, stakingtypes.Infraction_INFRACTION_DOWNTIME) // try to send slash packet for the same downtime infraction consumerKeeper.QueueSlashPacket(s.consumerCtx(), val, 3, stakingtypes.Infraction_INFRACTION_DOWNTIME) diff --git a/tests/integration/slashing.go b/tests/integration/slashing.go index 4058494974..6e0269e268 100644 --- a/tests/integration/slashing.go +++ b/tests/integration/slashing.go @@ -9,6 +9,7 @@ import ( cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" sdk "github.com/cosmos/cosmos-sdk/types" + sdkaddress "github.com/cosmos/cosmos-sdk/types/address" evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" @@ -270,12 +271,14 @@ func (s *CCVTestSuite) TestSlashPacketAcknowledgement() { // Map infraction height on provider so validation passes and provider returns valid ack result providerKeeper.SetValsetUpdateBlockHeight(s.providerCtx(), spd.ValsetUpdateId, 47923) - exportedAck := providerKeeper.OnRecvSlashPacket(s.providerCtx(), packet, spd) - s.Require().NotNil(exportedAck) + ackResult, err := providerKeeper.OnRecvSlashPacket(s.providerCtx(), packet, spd) + s.Require().NotNil(ackResult) + s.Require().NoError(err) + exportedAck := channeltypes.NewResultAcknowledgement(ackResult) // Unmarshal ack to struct that's compatible with consumer. IBC does this automatically ack := channeltypes.Acknowledgement{} - err := channeltypes.SubModuleCdc.UnmarshalJSON(exportedAck.Acknowledgement(), &ack) + err = channeltypes.SubModuleCdc.UnmarshalJSON(exportedAck.Acknowledgement(), &ack) s.Require().NoError(err) err = consumerKeeper.OnAcknowledgementPacket(s.consumerCtx(), packet, ack) @@ -329,9 +332,7 @@ func (suite *CCVTestSuite) TestHandleSlashPacketDowntime() { // TestOnRecvSlashPacketErrors tests errors for the OnRecvSlashPacket method in an integration testing setting func (suite *CCVTestSuite) TestOnRecvSlashPacketErrors() { providerKeeper := suite.providerApp.GetProviderKeeper() - providerSlashingKeeper := suite.providerApp.GetTestSlashingKeeper() firstBundle := suite.getFirstBundle() - consumerChainID := firstBundle.Chain.ChainID suite.SetupAllCCVChannels() @@ -340,100 +341,80 @@ func (suite *CCVTestSuite) TestOnRecvSlashPacketErrors() { // Expect panic if ccv channel is not established via dest channel of packet suite.Panics(func() { - providerKeeper.OnRecvSlashPacket(ctx, channeltypes.Packet{}, ccv.SlashPacketData{}) + _, _ = providerKeeper.OnRecvSlashPacket(ctx, channeltypes.Packet{}, ccv.SlashPacketData{}) }) // Add correct channelID to packet. Now we will not panic anymore. packet := channeltypes.Packet{DestinationChannel: firstBundle.Path.EndpointB.ChannelID} + suite.NotPanics(func() { + _, _ = providerKeeper.OnRecvSlashPacket(ctx, packet, ccv.SlashPacketData{}) + }) - // Init chain height is set by established CCV channel - // Delete init chain height and confirm expected error - initChainHeight, found := providerKeeper.GetInitChainHeight(ctx, consumerChainID) - suite.Require().True(found) - providerKeeper.DeleteInitChainHeight(ctx, consumerChainID) - - packetData := ccv.SlashPacketData{ValsetUpdateId: 0} - errAck := providerKeeper.OnRecvSlashPacket(ctx, packet, packetData) - suite.Require().False(errAck.Success()) - errAckCast := errAck.(channeltypes.Acknowledgement) - // Error strings in err acks are now thrown out by IBC core to prevent app hash. - // Hence a generic error string is expected. - suite.Require().Equal("ABCI code: 1: error handling packet: see events for details", errAckCast.GetError()) - - // Restore init chain height - providerKeeper.SetInitChainHeight(ctx, consumerChainID, initChainHeight) - - // now the method will fail at infraction height check. - packetData.Infraction = stakingtypes.Infraction_INFRACTION_UNSPECIFIED - errAck = providerKeeper.OnRecvSlashPacket(ctx, packet, packetData) - suite.Require().False(errAck.Success()) - errAckCast = errAck.(channeltypes.Acknowledgement) - suite.Require().Equal("ABCI code: 1: error handling packet: see events for details", errAckCast.GetError()) - - // save current VSC ID - vscID := providerKeeper.GetValidatorSetUpdateId(ctx) - - // remove block height value mapped to current VSC ID - providerKeeper.DeleteValsetUpdateBlockHeight(ctx, vscID) - - // Instantiate packet data with current VSC ID - packetData = ccv.SlashPacketData{ValsetUpdateId: vscID} - - // expect an error if mapped block height is not found - errAck = providerKeeper.OnRecvSlashPacket(ctx, packet, packetData) - suite.Require().False(errAck.Success()) - errAckCast = errAck.(channeltypes.Acknowledgement) - suite.Require().Equal("ABCI code: 1: error handling packet: see events for details", errAckCast.GetError()) - - // construct slashing packet with non existing validator - slashingPkt := ccv.NewSlashPacketData( + // Check Validate for SlashPacket data + validAddress := ed25519.GenPrivKey().PubKey().Address() + slashPacketData := ccv.NewSlashPacketData( abci.Validator{ - Address: ed25519.GenPrivKey().PubKey().Address(), - Power: int64(0), + Address: validAddress, + Power: int64(1), }, uint64(0), stakingtypes.Infraction_INFRACTION_DOWNTIME, ) - // Set initial block height for consumer chain - providerKeeper.SetInitChainHeight(ctx, consumerChainID, uint64(ctx.BlockHeight())) - - // Expect no error ack if validator does not exist - // TODO: this behavior should be changed to return an error ack, - // see: https://github.com/cosmos/interchain-security/issues/546 - ack := providerKeeper.OnRecvSlashPacket(ctx, packet, *slashingPkt) - suite.Require().True(ack.Success()) - - val := suite.providerChain.Vals.Validators[0] - - // commit block to set VSC ID - suite.coordinator.CommitBlock(suite.providerChain) - // Update suite.ctx bc CommitBlock updates only providerChain's current header block height - ctx = suite.providerChain.GetContext() - suite.Require().NotZero(providerKeeper.GetValsetUpdateBlockHeight(ctx, vscID)) - - // create validator signing info - valInfo := slashingtypes.NewValidatorSigningInfo(sdk.ConsAddress(val.Address), ctx.BlockHeight(), - ctx.BlockHeight()-1, time.Time{}.UTC(), false, int64(0)) - providerSlashingKeeper.SetValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address), valInfo) - - // update validator address and VSC ID - slashingPkt.Validator.Address = val.Address - slashingPkt.ValsetUpdateId = vscID - - // expect error ack when infraction type in unspecified - tmAddr := suite.providerChain.Vals.Validators[1].Address - slashingPkt.Validator.Address = tmAddr - slashingPkt.Infraction = stakingtypes.Infraction_INFRACTION_UNSPECIFIED - - valInfo.Address = sdk.ConsAddress(tmAddr).String() - providerSlashingKeeper.SetValidatorSigningInfo(ctx, sdk.ConsAddress(tmAddr), valInfo) - - errAck = providerKeeper.OnRecvSlashPacket(ctx, packet, *slashingPkt) - suite.Require().False(errAck.Success()) - - // expect to queue entries for the slash request - slashingPkt.Infraction = stakingtypes.Infraction_INFRACTION_DOWNTIME - ack = providerKeeper.OnRecvSlashPacket(ctx, packet, *slashingPkt) - suite.Require().True(ack.Success()) + // Expect an error if validator address is too long + slashPacketData.Validator.Address = make([]byte, sdkaddress.MaxAddrLen+1) + _, err := providerKeeper.OnRecvSlashPacket(ctx, packet, *slashPacketData) + suite.Require().Error(err, "validating SlashPacket data should fail - invalid validator address") + + // Expect an error if validator power is zero + slashPacketData.Validator.Address = validAddress + slashPacketData.Validator.Power = 0 + _, err = providerKeeper.OnRecvSlashPacket(ctx, packet, *slashPacketData) + suite.Require().Error(err, "validating SlashPacket data should fail - invalid validator power") + + // Expect an error if the infraction type is unspecified + slashPacketData.Validator.Power = 1 + slashPacketData.Infraction = stakingtypes.Infraction_INFRACTION_UNSPECIFIED + _, err = providerKeeper.OnRecvSlashPacket(ctx, packet, *slashPacketData) + suite.Require().Error(err, "validating SlashPacket data should fail - invalid infraction type") + + // Restore slashPacketData to be valid + slashPacketData.Infraction = stakingtypes.Infraction_INFRACTION_DOWNTIME + + // Check ValidateSlashPacket + // Expect an error if a mapping of the infraction height cannot be found; + // just set the vscID of the slash packet to the latest mapped vscID +1 + valsetUpdateBlockHeights := providerKeeper.GetAllValsetUpdateBlockHeights(ctx) + latestMappedValsetUpdateId := valsetUpdateBlockHeights[len(valsetUpdateBlockHeights)-1].ValsetUpdateId + slashPacketData.ValsetUpdateId = latestMappedValsetUpdateId + 1 + _, err = providerKeeper.OnRecvSlashPacket(ctx, packet, *slashPacketData) + suite.Require().Error(err, "ValidateSlashPacket should fail - no infraction height mapping") + + // Restore slashPacketData to be valid + slashPacketData.ValsetUpdateId = latestMappedValsetUpdateId + + // Expect no error if validator does not exist + _, err = providerKeeper.OnRecvSlashPacket(ctx, packet, *slashPacketData) + suite.Require().NoError(err, "no error expected") + + // Check expected behavior for handling SlashPackets for double signing infractions + slashPacketData.Infraction = stakingtypes.Infraction_INFRACTION_DOUBLE_SIGN + ackResult, err := providerKeeper.OnRecvSlashPacket(ctx, packet, *slashPacketData) + suite.Require().NoError(err, "no error expected") + suite.Require().Equal(ccv.V1Result, ackResult, "expected successful ack") + + // Check expected behavior for handling SlashPackets for downtime infractions + slashPacketData.Infraction = stakingtypes.Infraction_INFRACTION_DOWNTIME + + // Expect the packet to bounce if the slash meter is negative + providerKeeper.SetSlashMeter(ctx, sdk.NewInt(-1)) + ackResult, err = providerKeeper.OnRecvSlashPacket(ctx, packet, *slashPacketData) + suite.Require().NoError(err, "no error expected") + suite.Require().Equal(ccv.SlashPacketBouncedResult, ackResult, "expected successful ack") + + // Expect the packet to be handled if the slash meter is positive + providerKeeper.SetSlashMeter(ctx, sdk.NewInt(0)) + ackResult, err = providerKeeper.OnRecvSlashPacket(ctx, packet, *slashPacketData) + suite.Require().NoError(err, "no error expected") + suite.Require().Equal(ccv.SlashPacketHandledResult, ackResult, "expected successful ack") } // TestValidatorDowntime tests if a slash packet is sent @@ -649,6 +630,7 @@ func (suite *CCVTestSuite) TestQueueAndSendSlashPacket() { addr := ed25519.GenPrivKey().PubKey().Address() val := abci.Validator{ Address: addr, + Power: int64(1), } consumerKeeper.QueueSlashPacket(ctx, val, 0, infraction) slashedVals = append(slashedVals, slashedVal{validator: val, infraction: infraction}) diff --git a/tests/integration/throttle.go b/tests/integration/throttle.go index f186077a07..eca27a71bc 100644 --- a/tests/integration/throttle.go +++ b/tests/integration/throttle.go @@ -354,7 +354,8 @@ func (s *CCVTestSuite) TestPacketSpam() { consumerPacketData, err := provider.UnmarshalConsumerPacketData(data) // Same func used by provider's OnRecvPacket s.Require().NoError(err) packet := s.newPacketFromConsumer(data, uint64(sequence), firstBundle.Path, timeoutHeight, timeoutTimestamp) - providerKeeper.OnRecvSlashPacket(s.providerCtx(), packet, *consumerPacketData.GetSlashPacketData()) + _, err = providerKeeper.OnRecvSlashPacket(s.providerCtx(), packet, *consumerPacketData.GetSlashPacketData()) + s.Require().NoError(err) } // Execute block @@ -411,7 +412,8 @@ func (s *CCVTestSuite) TestDoubleSignDoesNotAffectThrottling() { consumerPacketData, err := provider.UnmarshalConsumerPacketData(data) // Same func used by provider's OnRecvPacket s.Require().NoError(err) packet := s.newPacketFromConsumer(data, uint64(sequence), firstBundle.Path, timeoutHeight, timeoutTimestamp) - providerKeeper.OnRecvSlashPacket(s.providerCtx(), packet, *consumerPacketData.GetSlashPacketData()) + _, err = providerKeeper.OnRecvSlashPacket(s.providerCtx(), packet, *consumerPacketData.GetSlashPacketData()) + s.Require().NoError(err) } // Execute block to handle packets in endblock @@ -581,7 +583,8 @@ func (s CCVTestSuite) TestSlashAllValidators() { //nolint:govet // this is a tes consumerPacketData, err := provider.UnmarshalConsumerPacketData(data) // Same func used by provider's OnRecvPacket s.Require().NoError(err) packet := s.newPacketFromConsumer(data, ibcSeqNum, s.getFirstBundle().Path, timeoutHeight, timeoutTimestamp) - providerKeeper.OnRecvSlashPacket(s.providerCtx(), packet, *consumerPacketData.GetSlashPacketData()) + _, err = providerKeeper.OnRecvSlashPacket(s.providerCtx(), packet, *consumerPacketData.GetSlashPacketData()) + s.Require().NoError(err) } // Check that all validators are jailed. diff --git a/tests/integration/valset_update.go b/tests/integration/valset_update.go index b12066afa8..89236dbd0c 100644 --- a/tests/integration/valset_update.go +++ b/tests/integration/valset_update.go @@ -71,9 +71,8 @@ func (suite *CCVTestSuite) TestQueueAndSendVSCMaturedPackets() { // send first packet packet := suite.newPacketFromProvider(pd.GetBytes(), 1, suite.path, clienttypes.NewHeight(1, 0), 0) - ack := consumerKeeper.OnRecvVSCPacket(suite.consumerChain.GetContext(), packet, pd) - suite.Require().NotNil(ack, "OnRecvVSCPacket did not return ack") - suite.Require().True(ack.Success(), "OnRecvVSCPacket did not return a Success Acknowledgment") + err = consumerKeeper.OnRecvVSCPacket(suite.consumerChain.GetContext(), packet, pd) + suite.Require().Nil(err, "OnRecvVSCPacket did return non-nil error") // increase time incrementTime(suite, time.Hour) @@ -83,9 +82,8 @@ func (suite *CCVTestSuite) TestQueueAndSendVSCMaturedPackets() { pd.ValsetUpdateId = 2 packet.Data = pd.GetBytes() packet.Sequence = 2 - ack = consumerKeeper.OnRecvVSCPacket(suite.consumerChain.GetContext(), packet, pd) - suite.Require().NotNil(ack, "OnRecvVSCPacket did not return ack") - suite.Require().True(ack.Success(), "OnRecvVSCPacket did not return a Success Acknowledgment") + err = consumerKeeper.OnRecvVSCPacket(suite.consumerChain.GetContext(), packet, pd) + suite.Require().Nil(err, "OnRecvVSCPacket did return non-nil error") // increase time incrementTime(suite, 24*time.Hour) @@ -95,9 +93,8 @@ func (suite *CCVTestSuite) TestQueueAndSendVSCMaturedPackets() { pd.ValsetUpdateId = 3 packet.Data = pd.GetBytes() packet.Sequence = 3 - ack = consumerKeeper.OnRecvVSCPacket(suite.consumerChain.GetContext(), packet, pd) - suite.Require().NotNil(ack, "OnRecvVSCPacket did not return ack") - suite.Require().True(ack.Success(), "OnRecvVSCPacket did not return a Success Acknowledgment") + err = consumerKeeper.OnRecvVSCPacket(suite.consumerChain.GetContext(), packet, pd) + suite.Require().Nil(err, "OnRecvVSCPacket did return non-nil error") packetMaturities := consumerKeeper.GetAllPacketMaturityTimes(suite.consumerChain.GetContext()) diff --git a/x/ccv/consumer/ibc_module.go b/x/ccv/consumer/ibc_module.go index 99f0141e64..d8f25b2173 100644 --- a/x/ccv/consumer/ibc_module.go +++ b/x/ccv/consumer/ibc_module.go @@ -2,6 +2,7 @@ package consumer import ( "fmt" + "strconv" "strings" transfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" @@ -225,25 +226,48 @@ func (am AppModule) OnRecvPacket( packet channeltypes.Packet, _ sdk.AccAddress, ) ibcexported.Acknowledgement { - var ( - ack ibcexported.Acknowledgement - data types.ValidatorSetChangePacketData - ) + logger := am.keeper.Logger(ctx) + ack := channeltypes.NewResultAcknowledgement([]byte{byte(1)}) + + var data types.ValidatorSetChangePacketData + var ackErr error if err := types.ModuleCdc.UnmarshalJSON(packet.GetData(), &data); err != nil { - errAck := types.NewErrorAcknowledgementWithLog(ctx, fmt.Errorf("cannot unmarshal CCV packet data")) - ack = &errAck - } else { - ack = am.keeper.OnRecvVSCPacket(ctx, packet, data) + ackErr = errorsmod.Wrapf(sdkerrors.ErrInvalidType, "cannot unmarshal VSCPacket data") + logger.Error(fmt.Sprintf("%s sequence %d", ackErr.Error(), packet.Sequence)) + ack = channeltypes.NewErrorAcknowledgement(ackErr) + } + + // only attempt the application logic if the packet data + // was successfully decoded + if ack.Success() { + err := am.keeper.OnRecvVSCPacket(ctx, packet, data) + if err != nil { + ack = channeltypes.NewErrorAcknowledgement(err) + ackErr = err + logger.Error(fmt.Sprintf("%s sequence %d", ackErr.Error(), packet.Sequence)) + } else { + logger.Info("successfully handled VSCPacket sequence: %d", packet.Sequence) + } + } + + eventAttributes := []sdk.Attribute{ + sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), + sdk.NewAttribute(types.AttributeValSetUpdateID, strconv.Itoa(int(data.ValsetUpdateId))), + sdk.NewAttribute(types.AttributeKeyAckSuccess, fmt.Sprintf("%t", ack.Success())), + } + + if ackErr != nil { + eventAttributes = append(eventAttributes, sdk.NewAttribute(types.AttributeKeyAckError, ackErr.Error())) } ctx.EventManager().EmitEvent( sdk.NewEvent( types.EventTypePacket, - sdk.NewAttribute(sdk.AttributeKeyModule, consumertypes.ModuleName), - sdk.NewAttribute(types.AttributeKeyAckSuccess, fmt.Sprintf("%t", ack != nil)), + eventAttributes..., ), ) + // NOTE: acknowledgement will be written synchronously during IBC handler execution. return ack } diff --git a/x/ccv/consumer/keeper/relay.go b/x/ccv/consumer/keeper/relay.go index 3afeb33188..68c911f255 100644 --- a/x/ccv/consumer/keeper/relay.go +++ b/x/ccv/consumer/keeper/relay.go @@ -6,7 +6,6 @@ import ( clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" - "github.com/cosmos/ibc-go/v7/modules/core/exported" errorsmod "cosmossdk.io/errors" @@ -25,7 +24,12 @@ import ( // // Note: CCV uses an ordered IBC channel, meaning VSC packet changes will be accumulated (and later // processed by ApplyCCValidatorChanges) s.t. more recent val power changes overwrite older ones. -func (k Keeper) OnRecvVSCPacket(ctx sdk.Context, packet channeltypes.Packet, newChanges ccv.ValidatorSetChangePacketData) exported.Acknowledgement { +func (k Keeper) OnRecvVSCPacket(ctx sdk.Context, packet channeltypes.Packet, newChanges ccv.ValidatorSetChangePacketData) error { + // validate packet data upon receiving + if err := newChanges.Validate(); err != nil { + return errorsmod.Wrapf(err, "error validating VSCPacket data") + } + // get the provider channel providerChannel, found := k.GetProviderChannel(ctx) if found && providerChannel != packet.DestinationChannel { @@ -87,8 +91,7 @@ func (k Keeper) OnRecvVSCPacket(ctx sdk.Context, packet channeltypes.Packet, new "len updates", len(newChanges.ValidatorUpdates), "len slash acks", len(newChanges.SlashAcks), ) - ack := channeltypes.NewResultAcknowledgement([]byte{byte(1)}) - return ack + return nil } // QueueVSCMaturedPackets appends matured VSCs to an internal queue. diff --git a/x/ccv/consumer/keeper/relay_test.go b/x/ccv/consumer/keeper/relay_test.go index d681a2fdc4..f9d03c68f0 100644 --- a/x/ccv/consumer/keeper/relay_test.go +++ b/x/ccv/consumer/keeper/relay_test.go @@ -74,31 +74,59 @@ func TestOnRecvVSCPacket(t *testing.T) { nil, ) + pd3 := types.NewValidatorSetChangePacketData( + []abci.ValidatorUpdate{}, + 3, + []string{ + "invalid_slash_ack", + }, + ) + testCases := []struct { name string + expError bool packet channeltypes.Packet - newChanges types.ValidatorSetChangePacketData expectedPendingChanges types.ValidatorSetChangePacketData }{ { "success on first packet", + false, channeltypes.NewPacket(pd.GetBytes(), 1, types.ProviderPortID, providerCCVChannelID, types.ConsumerPortID, consumerCCVChannelID, clienttypes.NewHeight(1, 0), 0), types.ValidatorSetChangePacketData{ValidatorUpdates: changes1}, - types.ValidatorSetChangePacketData{ValidatorUpdates: changes1}, }, { "success on subsequent packet", + false, channeltypes.NewPacket(pd.GetBytes(), 2, types.ProviderPortID, providerCCVChannelID, types.ConsumerPortID, consumerCCVChannelID, clienttypes.NewHeight(1, 0), 0), types.ValidatorSetChangePacketData{ValidatorUpdates: changes1}, - types.ValidatorSetChangePacketData{ValidatorUpdates: changes1}, }, { "success on packet with more changes", + false, channeltypes.NewPacket(pd2.GetBytes(), 3, types.ProviderPortID, providerCCVChannelID, types.ConsumerPortID, consumerCCVChannelID, clienttypes.NewHeight(1, 0), 0), - types.ValidatorSetChangePacketData{ValidatorUpdates: changes2}, + types.ValidatorSetChangePacketData{ValidatorUpdates: []abci.ValidatorUpdate{ + { + PubKey: pk1, + Power: 30, + }, + { + PubKey: pk2, + Power: 40, + }, + { + PubKey: pk3, + Power: 10, + }, + }}, + }, + { + "failure on packet with invalid slash acks", + true, + channeltypes.NewPacket(pd3.GetBytes(), 4, types.ProviderPortID, providerCCVChannelID, types.ConsumerPortID, consumerCCVChannelID, + clienttypes.NewHeight(1, 0), 0), types.ValidatorSetChangePacketData{ValidatorUpdates: []abci.ValidatorUpdate{ { PubKey: pk1, @@ -128,10 +156,16 @@ func TestOnRecvVSCPacket(t *testing.T) { consumerKeeper.SetParams(ctx, moduleParams) for _, tc := range testCases { - ack := consumerKeeper.OnRecvVSCPacket(ctx, tc.packet, tc.newChanges) + var newChanges types.ValidatorSetChangePacketData + err := types.ModuleCdc.UnmarshalJSON(tc.packet.GetData(), &newChanges) + require.Nil(t, err, "invalid test case: %s - cannot unmarshal VSCPacket data", tc.name) + err = consumerKeeper.OnRecvVSCPacket(ctx, tc.packet, newChanges) + if tc.expError { + require.Error(t, err, "%s - invalid but OnRecvVSCPacket did not return error", tc.name) + continue + } + require.NoError(t, err, "%s - valid but OnRecvVSCPacket returned error: %w", tc.name, err) - require.NotNil(t, ack, "invalid test case: %s did not return ack", tc.name) - require.True(t, ack.Success(), "invalid test case: %s did not return a Success Acknowledgment", tc.name) providerChannel, ok := consumerKeeper.GetProviderChannel(ctx) require.True(t, ok) require.Equal(t, tc.packet.DestinationChannel, providerChannel, @@ -152,7 +186,7 @@ func TestOnRecvVSCPacket(t *testing.T) { expectedTime := ctx.BlockTime().Add(consumerKeeper.GetUnbondingPeriod(ctx)) require.True( t, - consumerKeeper.PacketMaturityTimeExists(ctx, tc.newChanges.ValsetUpdateId, expectedTime), + consumerKeeper.PacketMaturityTimeExists(ctx, newChanges.ValsetUpdateId, expectedTime), "no packet maturity time for case: %s", tc.name, ) } @@ -199,9 +233,8 @@ func TestOnRecvVSCPacketDuplicateUpdates(t *testing.T) { require.False(t, ok) // Execute OnRecvVSCPacket - ack := consumerKeeper.OnRecvVSCPacket(ctx, packet, vscData) - require.NotNil(t, ack) - require.True(t, ack.Success()) + err := consumerKeeper.OnRecvVSCPacket(ctx, packet, vscData) + require.Nil(t, err) // Confirm pending changes are queued by OnRecvVSCPacket gotPendingChanges, ok := consumerKeeper.GetPendingChanges(ctx) diff --git a/x/ccv/provider/ibc_module.go b/x/ccv/provider/ibc_module.go index 75da9588d6..8d342b0c6f 100644 --- a/x/ccv/provider/ibc_module.go +++ b/x/ccv/provider/ibc_module.go @@ -2,6 +2,7 @@ package provider import ( "fmt" + "strconv" channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" porttypes "github.com/cosmos/ibc-go/v7/modules/core/05-port/types" @@ -175,36 +176,67 @@ func (am AppModule) OnRecvPacket( packet channeltypes.Packet, _ sdk.AccAddress, ) ibcexported.Acknowledgement { + logger := am.keeper.Logger(ctx) + ack := channeltypes.NewResultAcknowledgement([]byte{byte(1)}) + + var ackErr error consumerPacket, err := UnmarshalConsumerPacket(packet) if err != nil { - errAck := ccv.NewErrorAcknowledgementWithLog(ctx, err) - return &errAck + ackErr = errorsmod.Wrapf(sdkerrors.ErrInvalidType, "cannot unmarshal ConsumerPacket data") + logger.Error(fmt.Sprintf("%s sequence %d", ackErr.Error(), packet.Sequence)) + ack = channeltypes.NewErrorAcknowledgement(ackErr) + } + + eventAttributes := []sdk.Attribute{ + sdk.NewAttribute(sdk.AttributeKeyModule, providertypes.ModuleName), + } + + // only attempt the application logic if the packet data + // was successfully decoded + if ack.Success() { + var err error + switch consumerPacket.Type { + case ccv.VscMaturedPacket: + // handle VSCMaturedPacket + data := *consumerPacket.GetVscMaturedPacketData() + err = am.keeper.OnRecvVSCMaturedPacket(ctx, packet, data) + if err == nil { + logger.Info("successfully handled VSCMaturedPacket sequence: %d", packet.Sequence) + eventAttributes = append(eventAttributes, sdk.NewAttribute(ccv.AttributeValSetUpdateID, strconv.Itoa(int(data.ValsetUpdateId)))) + } + case ccv.SlashPacket: + // handle SlashPacket + var ackResult ccv.PacketAckResult + data := *consumerPacket.GetSlashPacketData() + ackResult, err = am.keeper.OnRecvSlashPacket(ctx, packet, data) + if err == nil { + ack = channeltypes.NewResultAcknowledgement(ackResult) + logger.Info("successfully handled SlashPacket sequence: %d", packet.Sequence) + eventAttributes = append(eventAttributes, sdk.NewAttribute(ccv.AttributeValSetUpdateID, strconv.Itoa(int(data.ValsetUpdateId)))) + } + default: + err = fmt.Errorf("invalid consumer packet type: %q", consumerPacket.Type) + } + if err != nil { + ack = channeltypes.NewErrorAcknowledgement(err) + ackErr = err + logger.Error(fmt.Sprintf("%s sequence %d", ackErr.Error(), packet.Sequence)) + } } - // TODO: call ValidateBasic method on consumer packet data - // See: https://github.com/cosmos/interchain-security/issues/634 - - var ack ibcexported.Acknowledgement - switch consumerPacket.Type { - case ccv.VscMaturedPacket: - // handle VSCMaturedPacket - ack = am.keeper.OnRecvVSCMaturedPacket(ctx, packet, *consumerPacket.GetVscMaturedPacketData()) - case ccv.SlashPacket: - // handle SlashPacket - ack = am.keeper.OnRecvSlashPacket(ctx, packet, *consumerPacket.GetSlashPacketData()) - default: - errAck := ccv.NewErrorAcknowledgementWithLog(ctx, fmt.Errorf("invalid consumer packet type: %q", consumerPacket.Type)) - ack = &errAck + eventAttributes = append(eventAttributes, sdk.NewAttribute(ccv.AttributeKeyAckSuccess, fmt.Sprintf("%t", ack.Success()))) + if ackErr != nil { + eventAttributes = append(eventAttributes, sdk.NewAttribute(ccv.AttributeKeyAckError, ackErr.Error())) } ctx.EventManager().EmitEvent( sdk.NewEvent( ccv.EventTypePacket, - sdk.NewAttribute(sdk.AttributeKeyModule, providertypes.ModuleName), - sdk.NewAttribute(ccv.AttributeKeyAckSuccess, fmt.Sprintf("%t", ack != nil)), + eventAttributes..., ), ) + // NOTE: acknowledgement will be written synchronously during IBC handler execution. return ack } diff --git a/x/ccv/provider/keeper/relay.go b/x/ccv/provider/keeper/relay.go index 0cbce718a7..b07ba3b8ff 100644 --- a/x/ccv/provider/keeper/relay.go +++ b/x/ccv/provider/keeper/relay.go @@ -6,7 +6,6 @@ import ( clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" - "github.com/cosmos/ibc-go/v7/modules/core/exported" errorsmod "cosmossdk.io/errors" @@ -22,7 +21,7 @@ func (k Keeper) OnRecvVSCMaturedPacket( ctx sdk.Context, packet channeltypes.Packet, data ccv.VSCMaturedPacketData, -) exported.Acknowledgement { +) error { // check that the channel is established, panic if not chainID, found := k.GetChannelToChain(ctx, packet.DestinationChannel) if !found { @@ -34,6 +33,11 @@ func (k Keeper) OnRecvVSCMaturedPacket( panic(fmt.Errorf("VSCMaturedPacket received on unknown channel %s", packet.DestinationChannel)) } + // validate packet data upon receiving + if err := data.Validate(); err != nil { + return errorsmod.Wrapf(err, "error validating VSCMaturedPacket data") + } + k.HandleVSCMaturedPacket(ctx, chainID, data) k.Logger(ctx).Info("VSCMaturedPacket handled", @@ -41,8 +45,7 @@ func (k Keeper) OnRecvVSCMaturedPacket( "vscID", data.ValsetUpdateId, ) - ack := channeltypes.NewResultAcknowledgement(ccv.V1Result) - return ack + return nil } // HandleVSCMaturedPacket handles a VSCMatured packet. @@ -265,7 +268,11 @@ func (k Keeper) EndBlockCIS(ctx sdk.Context) { // OnRecvSlashPacket delivers a received slash packet, validates it and // then queues the slash packet as pending if valid. -func (k Keeper) OnRecvSlashPacket(ctx sdk.Context, packet channeltypes.Packet, data ccv.SlashPacketData) exported.Acknowledgement { +func (k Keeper) OnRecvSlashPacket( + ctx sdk.Context, + packet channeltypes.Packet, + data ccv.SlashPacketData, +) (ccv.PacketAckResult, error) { // check that the channel is established, panic if not chainID, found := k.GetChannelToChain(ctx, packet.DestinationChannel) if !found { @@ -277,6 +284,11 @@ func (k Keeper) OnRecvSlashPacket(ctx sdk.Context, packet channeltypes.Packet, d panic(fmt.Errorf("SlashPacket received on unknown channel %s", packet.DestinationChannel)) } + // validate packet data upon receiving + if err := data.Validate(); err != nil { + return nil, errorsmod.Wrapf(err, "error validating SlashPacket data") + } + if err := k.ValidateSlashPacket(ctx, chainID, packet, data); err != nil { k.Logger(ctx).Error("invalid slash packet", "error", err.Error(), @@ -285,7 +297,7 @@ func (k Keeper) OnRecvSlashPacket(ctx sdk.Context, packet channeltypes.Packet, d "vscID", data.ValsetUpdateId, "infractionType", data.Infraction, ) - return ccv.NewErrorAcknowledgementWithLog(ctx, err) + return nil, err } // The slash packet validator address may be known only on the consumer chain, @@ -308,7 +320,7 @@ func (k Keeper) OnRecvSlashPacket(ctx sdk.Context, packet channeltypes.Packet, d // return successful ack, as an error would result // in the consumer closing the CCV channel - return channeltypes.NewResultAcknowledgement(ccv.V1Result) + return ccv.V1Result, nil } meter := k.GetSlashMeter(ctx) @@ -321,7 +333,7 @@ func (k Keeper) OnRecvSlashPacket(ctx sdk.Context, packet channeltypes.Packet, d "vscID", data.ValsetUpdateId, "infractionType", data.Infraction, ) - return channeltypes.NewResultAcknowledgement(ccv.SlashPacketBouncedResult) + return ccv.SlashPacketBouncedResult, nil } // Subtract voting power that will be jailed/tombstoned from the slash meter, @@ -340,7 +352,7 @@ func (k Keeper) OnRecvSlashPacket(ctx sdk.Context, packet channeltypes.Packet, d ) // Return result ack that the packet was handled successfully - return channeltypes.NewResultAcknowledgement(ccv.SlashPacketHandledResult) + return ccv.SlashPacketHandledResult, nil } // ValidateSlashPacket validates a recv slash packet before it is @@ -356,10 +368,6 @@ func (k Keeper) ValidateSlashPacket(ctx sdk.Context, chainID string, "the validator update id %d for chain %s", data.ValsetUpdateId, chainID) } - if data.Infraction != stakingtypes.Infraction_INFRACTION_DOUBLE_SIGN && data.Infraction != stakingtypes.Infraction_INFRACTION_DOWNTIME { - return fmt.Errorf("invalid infraction type: %s", data.Infraction) - } - return nil } diff --git a/x/ccv/provider/keeper/relay_test.go b/x/ccv/provider/keeper/relay_test.go index bc482d30a2..fdb9c7cc2f 100644 --- a/x/ccv/provider/keeper/relay_test.go +++ b/x/ccv/provider/keeper/relay_test.go @@ -6,7 +6,6 @@ import ( clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" - exported "github.com/cosmos/ibc-go/v7/modules/core/exported" ibctesting "github.com/cosmos/ibc-go/v7/testing" "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" @@ -106,12 +105,12 @@ func TestOnRecvVSCMaturedPacket(t *testing.T) { providerKeeper.SetChannelToChain(ctx, "channel-2", "chain-2") // Execute on recv for chain-1, confirm v1 result ack is returned - ack := executeOnRecvVSCMaturedPacket(t, &providerKeeper, ctx, "channel-1", 1) - require.Equal(t, channeltypes.NewResultAcknowledgement([]byte{byte(1)}), ack) + err := executeOnRecvVSCMaturedPacket(t, &providerKeeper, ctx, "channel-1", 1) + require.NoError(t, err) // Now queue a slash packet data instance for chain-2, confirm v1 result ack is returned - ack = executeOnRecvVSCMaturedPacket(t, &providerKeeper, ctx, "channel-2", 2) - require.Equal(t, channeltypes.NewResultAcknowledgement([]byte{byte(1)}), ack) + err = executeOnRecvVSCMaturedPacket(t, &providerKeeper, ctx, "channel-2", 2) + require.NoError(t, err) } // TestOnRecvDowntimeSlashPacket tests the OnRecvSlashPacket method specifically for downtime slash packets. @@ -133,12 +132,14 @@ func TestOnRecvDowntimeSlashPacket(t *testing.T) { // Set slash meter to negative value and assert a bounce ack is returned providerKeeper.SetSlashMeter(ctx, math.NewInt(-5)) - ack := executeOnRecvSlashPacket(t, &providerKeeper, ctx, "channel-1", 1, packetData) - require.Equal(t, channeltypes.NewResultAcknowledgement(ccv.SlashPacketBouncedResult), ack) + ackResult, err := executeOnRecvSlashPacket(t, &providerKeeper, ctx, "channel-1", 1, packetData) + require.Equal(t, ccv.SlashPacketBouncedResult, ackResult) + require.NoError(t, err) // Also bounced for chain-2 - ack = executeOnRecvSlashPacket(t, &providerKeeper, ctx, "channel-2", 2, packetData) - require.Equal(t, channeltypes.NewResultAcknowledgement(ccv.SlashPacketBouncedResult), ack) + ackResult, err = executeOnRecvSlashPacket(t, &providerKeeper, ctx, "channel-2", 2, packetData) + require.Equal(t, ccv.SlashPacketBouncedResult, ackResult) + require.NoError(t, err) // Now set slash meter to positive value and assert slash packet handled result is returned providerKeeper.SetSlashMeter(ctx, math.NewInt(5)) @@ -160,8 +161,9 @@ func TestOnRecvDowntimeSlashPacket(t *testing.T) { gomock.InOrder(calls...) // Execute on recv and confirm slash packet handled result is returned - ack = executeOnRecvSlashPacket(t, &providerKeeper, ctx, "channel-1", 1, packetData) - require.Equal(t, channeltypes.NewResultAcknowledgement(ccv.SlashPacketHandledResult), ack) + ackResult, err = executeOnRecvSlashPacket(t, &providerKeeper, ctx, "channel-1", 1, packetData) + require.Equal(t, ccv.SlashPacketHandledResult, ackResult) + require.NoError(t, err) // Require slash meter was decremented appropriately, 5-2=3 require.Equal(t, int64(3), providerKeeper.GetSlashMeter(ctx).Int64()) @@ -185,8 +187,9 @@ func TestOnRecvDoubleSignSlashPacket(t *testing.T) { providerKeeper.SetValsetUpdateBlockHeight(ctx, packetData.ValsetUpdateId, uint64(15)) // Receive the double-sign slash packet for chain-1 and confirm the expected acknowledgement - ack := executeOnRecvSlashPacket(t, &providerKeeper, ctx, "channel-1", 1, packetData) - require.Equal(t, channeltypes.NewResultAcknowledgement(ccv.V1Result), ack) + ackResult, err := executeOnRecvSlashPacket(t, &providerKeeper, ctx, "channel-1", 1, packetData) + require.Equal(t, ccv.V1Result, ackResult) + require.NoError(t, err) require.True(t, providerKeeper.GetSlashLog(ctx, providertypes.NewProviderConsAddress(packetData.Validator.Address))) @@ -198,7 +201,7 @@ func TestOnRecvDoubleSignSlashPacket(t *testing.T) { func executeOnRecvVSCMaturedPacket(t *testing.T, providerKeeper *keeper.Keeper, ctx sdk.Context, channelID string, ibcSeqNum uint64, -) exported.Acknowledgement { +) error { t.Helper() // Instantiate vsc matured packet data and bytes data := testkeeper.GetNewVSCMaturedPacketData() @@ -214,7 +217,7 @@ func executeOnRecvVSCMaturedPacket(t *testing.T, providerKeeper *keeper.Keeper, func executeOnRecvSlashPacket(t *testing.T, providerKeeper *keeper.Keeper, ctx sdk.Context, channelID string, ibcSeqNum uint64, packetData ccv.SlashPacketData, -) exported.Acknowledgement { +) (ccv.PacketAckResult, error) { t.Helper() // Instantiate slash packet data and bytes dataBz, err := packetData.Marshal() @@ -241,16 +244,6 @@ func TestValidateSlashPacket(t *testing.T) { ccv.SlashPacketData{ValsetUpdateId: 61}, true, }, - { - "non-set infraction type", - ccv.SlashPacketData{ValsetUpdateId: validVscID}, - true, - }, - { - "invalid infraction type", - ccv.SlashPacketData{ValsetUpdateId: validVscID, Infraction: stakingtypes.MaxMonikerLength}, - true, - }, { "valid double sign packet with non-zero vscID", ccv.SlashPacketData{ValsetUpdateId: validVscID, Infraction: stakingtypes.Infraction_INFRACTION_DOUBLE_SIGN}, diff --git a/x/ccv/provider/types/errors.go b/x/ccv/provider/types/errors.go index 66d7a9a3b8..6c19a7b396 100644 --- a/x/ccv/provider/types/errors.go +++ b/x/ccv/provider/types/errors.go @@ -11,7 +11,7 @@ var ( ErrUnknownConsumerChainId = errorsmod.Register(ModuleName, 3, "no consumer chain with this chain id") ErrUnknownConsumerChannelId = errorsmod.Register(ModuleName, 4, "no consumer chain with this channel id") ErrInvalidConsumerConsensusPubKey = errorsmod.Register(ModuleName, 5, "empty consumer consensus public key") - ErrBlankConsumerChainID = errorsmod.Register(ModuleName, 6, "consumer chain id must not be blank") + ErrInvalidConsumerChainID = errorsmod.Register(ModuleName, 6, "invalid consumer chain id") ErrConsumerKeyNotFound = errorsmod.Register(ModuleName, 7, "consumer key not found") ErrNoValidatorConsumerAddress = errorsmod.Register(ModuleName, 8, "error getting validator consumer address") ErrNoValidatorProviderAddress = errorsmod.Register(ModuleName, 9, "error getting validator provider address") diff --git a/x/ccv/provider/types/msg.go b/x/ccv/provider/types/msg.go index f7ee11325c..73fb7f5b74 100644 --- a/x/ccv/provider/types/msg.go +++ b/x/ccv/provider/types/msg.go @@ -72,14 +72,13 @@ func (msg MsgAssignConsumerKey) GetSignBytes() []byte { // ValidateBasic implements the sdk.Msg interface. func (msg MsgAssignConsumerKey) ValidateBasic() error { if strings.TrimSpace(msg.ChainId) == "" { - return ErrBlankConsumerChainID + return errorsmod.Wrapf(ErrInvalidConsumerChainID, "chainId cannot be blank") } // It is possible to assign keys for consumer chains that are not yet approved. // This can only be done by a signing validator, but it is still sensible // to limit the chainID size to prevent abuse. - if 128 < len(msg.ChainId) { - return ErrBlankConsumerChainID + return errorsmod.Wrapf(ErrInvalidConsumerChainID, "chainId cannot exceed 128 length") } _, err := sdk.ValAddressFromBech32(msg.ProviderAddr) if err != nil { diff --git a/x/ccv/types/wire.go b/x/ccv/types/wire.go index 5b4e57994f..c7cbe9e126 100644 --- a/x/ccv/types/wire.go +++ b/x/ccv/types/wire.go @@ -5,6 +5,7 @@ import ( errorsmod "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" abci "github.com/cometbft/cometbft/abci/types" @@ -18,14 +19,24 @@ func NewValidatorSetChangePacketData(valUpdates []abci.ValidatorUpdate, valUpdat } } -// ValidateBasic is used for validating the CCV packet data. -func (vsc ValidatorSetChangePacketData) ValidateBasic() error { - if len(vsc.ValidatorUpdates) == 0 { - return errorsmod.Wrap(ErrInvalidPacketData, "validator updates cannot be empty") +// Validate is used for validating the CCV packet data. +func (vsc ValidatorSetChangePacketData) Validate() error { + // Note that vsc.ValidatorUpdates can be empty in the case of unbonding + // operations w/o changes in the voting power of the validators in the validator set + if vsc.ValidatorUpdates == nil { + return errorsmod.Wrap(ErrInvalidPacketData, "validator updates cannot be nil") } + // ValsetUpdateId is strictly positive if vsc.ValsetUpdateId == 0 { return errorsmod.Wrap(ErrInvalidPacketData, "valset update id cannot be equal to zero") } + // Validate the slash acks - must be consensus addresses + for _, ack := range vsc.SlashAcks { + _, err := sdk.ConsAddressFromBech32(ack) + if err != nil { + return err + } + } return nil } @@ -42,8 +53,9 @@ func NewVSCMaturedPacketData(valUpdateID uint64) *VSCMaturedPacketData { } } -// ValidateBasic is used for validating the VSCMatured packet data. -func (mat VSCMaturedPacketData) ValidateBasic() error { +// Validate is used for validating the VSCMatured packet data. +func (mat VSCMaturedPacketData) Validate() error { + // ValsetUpdateId is strictly positive if mat.ValsetUpdateId == 0 { return errorsmod.Wrap(ErrInvalidPacketData, "vscId cannot be equal to zero") } @@ -75,13 +87,19 @@ func NewSlashPacketDataV1(validator abci.Validator, valUpdateId uint64, infracti } } -func (vdt SlashPacketData) ValidateBasic() error { - if len(vdt.Validator.Address) == 0 || vdt.Validator.Power == 0 { - return errorsmod.Wrap(ErrInvalidPacketData, "validator fields cannot be empty") +func (vdt SlashPacketData) Validate() error { + // vdt.Validator.Address must be a consensus address + if err := sdk.VerifyAddressFormat(vdt.Validator.Address); err != nil { + return errorsmod.Wrap(ErrInvalidPacketData, fmt.Sprintf("invalid validator: %s", err.Error())) + } + // vdt.Validator.Power must be positive + if vdt.Validator.Power == 0 { + return errorsmod.Wrap(ErrInvalidPacketData, "validator power cannot be zero") } + // Note that ValsetUpdateId can be zero due to the vscID mapping - if vdt.Infraction == stakingtypes.Infraction_INFRACTION_UNSPECIFIED { - return errorsmod.Wrap(ErrInvalidPacketData, "invalid infraction type") + if vdt.Infraction != stakingtypes.Infraction_INFRACTION_DOUBLE_SIGN && vdt.Infraction != stakingtypes.Infraction_INFRACTION_DOWNTIME { + return errorsmod.Wrap(ErrInvalidPacketData, fmt.Sprintf("invalid infraction type: %s", vdt.Infraction.String())) } return nil @@ -91,22 +109,22 @@ func (vdt SlashPacketData) ToV1() *SlashPacketDataV1 { return NewSlashPacketDataV1(vdt.Validator, vdt.ValsetUpdateId, vdt.Infraction) } -func (cp ConsumerPacketData) ValidateBasic() (err error) { +func (cp ConsumerPacketData) Validate() (err error) { switch cp.Type { case VscMaturedPacket: // validate VSCMaturedPacket - vscPacket := cp.GetVscMaturedPacketData() - if vscPacket == nil { + vscMaturedPacket := cp.GetVscMaturedPacketData() + if vscMaturedPacket == nil { return fmt.Errorf("invalid consumer packet data: VscMaturePacketData data cannot be empty") } - err = vscPacket.ValidateBasic() + err = vscMaturedPacket.Validate() case SlashPacket: // validate SlashPacket slashPacket := cp.GetSlashPacketData() if slashPacket == nil { return fmt.Errorf("invalid consumer packet data: SlashPacketData data cannot be empty") } - err = slashPacket.ValidateBasic() + err = slashPacket.Validate() default: err = fmt.Errorf("invalid consumer packet type: %q", cp.Type) } diff --git a/x/ccv/types/wire_test.go b/x/ccv/types/wire_test.go index 50164fcf73..f97d1af1d2 100644 --- a/x/ccv/types/wire_test.go +++ b/x/ccv/types/wire_test.go @@ -22,23 +22,75 @@ func TestPacketDataValidateBasic(t *testing.T) { pk2, err := cryptocodec.ToTmProtoPublicKey(ed25519.GenPrivKey().PubKey()) require.NoError(t, err) + cId := crypto.NewCryptoIdentityFromIntSeed(4732894342) + validSlashAck := cId.SDKValConsAddress().String() + tooLongSlashAck := string(make([]byte, 1024)) + cases := []struct { name string expError bool packetData types.ValidatorSetChangePacketData }{ { - "nil packet data", + "invalid: nil packet data", true, types.NewValidatorSetChangePacketData(nil, 1, nil), }, { - "empty packet data", - true, + "valid: empty packet data", + false, types.NewValidatorSetChangePacketData([]abci.ValidatorUpdate{}, 2, nil), }, { - "valid packet data", + "invalid: slash ack not consensus address", + true, + types.NewValidatorSetChangePacketData( + []abci.ValidatorUpdate{ + { + PubKey: pk1, + Power: 30, + }, + }, + 3, + []string{ + "some_string", + }, + ), + }, + { + "valid: packet data with valid slash ack", + false, + types.NewValidatorSetChangePacketData( + []abci.ValidatorUpdate{ + { + PubKey: pk2, + Power: 20, + }, + }, + 4, + []string{ + validSlashAck, + }, + ), + }, + { + "invalid: slash ack is too long", + true, + types.NewValidatorSetChangePacketData( + []abci.ValidatorUpdate{ + { + PubKey: pk2, + Power: 20, + }, + }, + 5, + []string{ + tooLongSlashAck, + }, + ), + }, + { + "valid: packet data with nil slash ack", false, types.NewValidatorSetChangePacketData( []abci.ValidatorUpdate{ @@ -51,18 +103,18 @@ func TestPacketDataValidateBasic(t *testing.T) { Power: 20, }, }, - 3, + 6, nil, ), }, } for _, c := range cases { - err := c.packetData.ValidateBasic() + err := c.packetData.Validate() if c.expError { - require.Error(t, err, "%s invalid but passed ValidateBasic", c.name) + require.Error(t, err, "%s invalid but passed Validate", c.name) } else { - require.NoError(t, err, "%s valid but ValidateBasic returned error: %w", c.name, err) + require.NoError(t, err, "%s valid but Validate returned error: %w", c.name, err) } } }