diff --git a/.changelog/unreleased/bug-fixes/ibc-relayer/4204-fix-errors-logs.md b/.changelog/unreleased/bug-fixes/ibc-relayer/4204-fix-errors-logs.md new file mode 100644 index 0000000000..539ae153ef --- /dev/null +++ b/.changelog/unreleased/bug-fixes/ibc-relayer/4204-fix-errors-logs.md @@ -0,0 +1,3 @@ +- Fix error messages in logs to accurately display the RPC endpoint + that failed. + ([\#4250](https://github.com/informalsystems/hermes/issues/4250)) \ No newline at end of file diff --git a/.changelog/unreleased/bug-fixes/ibc-relayer/4237-improve-tls-configuration.md b/.changelog/unreleased/bug-fixes/ibc-relayer/4237-improve-tls-configuration.md new file mode 100644 index 0000000000..1c246d6aef --- /dev/null +++ b/.changelog/unreleased/bug-fixes/ibc-relayer/4237-improve-tls-configuration.md @@ -0,0 +1,3 @@ +- Fix an issue where Hermes would fail to connect to gRPC servers + with an IPv6 address. + ([\#4237](https://github.com/informalsystems/hermes/issues/4237)) \ No newline at end of file diff --git a/.changelog/unreleased/features/ibc-integration-test/4153-add-ipv6-test.md b/.changelog/unreleased/features/ibc-integration-test/4153-add-ipv6-test.md new file mode 100644 index 0000000000..8c7c3b98a7 --- /dev/null +++ b/.changelog/unreleased/features/ibc-integration-test/4153-add-ipv6-test.md @@ -0,0 +1,2 @@ +- Disable TLS configuration when using IPv6 for the gRPC endpoint. + ([\#4224](https://github.com/informalsystems/hermes/issues/4224)) \ No newline at end of file diff --git a/.changelog/unreleased/improvements/ibc-integration-test/4204-update-gaia-to-v20.md b/.changelog/unreleased/improvements/ibc-integration-test/4204-update-gaia-to-v20.md new file mode 100644 index 0000000000..2fcbe90beb --- /dev/null +++ b/.changelog/unreleased/improvements/ibc-integration-test/4204-update-gaia-to-v20.md @@ -0,0 +1,2 @@ +- Update the version of Gaia running the integration tests in the CI from `v18.1.0` + to `v20.0.0` ([\#4204](https://github.com/informalsystems/hermes/issues/4204)) \ No newline at end of file diff --git a/.changelog/unreleased/improvements/ibc-relayer/4153-update-misbehaviour-to-permissionless.md b/.changelog/unreleased/improvements/ibc-relayer/4153-update-misbehaviour-to-permissionless.md new file mode 100644 index 0000000000..65bbfe1c77 --- /dev/null +++ b/.changelog/unreleased/improvements/ibc-relayer/4153-update-misbehaviour-to-permissionless.md @@ -0,0 +1,2 @@ +- Use CCV consumer ID to submit misbehaviour messages + ([\#4153](https://github.com/informalsystems/hermes/issues/4153)) \ No newline at end of file diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml index 59f90f2caf..fb98841907 100644 --- a/.github/workflows/integration.yaml +++ b/.github/workflows/integration.yaml @@ -45,7 +45,7 @@ jobs: fail-fast: false matrix: chain: - - package: gaia18 + - package: gaia20 command: gaiad account_prefix: cosmos native_token: stake @@ -178,10 +178,10 @@ jobs: fail-fast: false matrix: chain: - - package: .#gaia18 .#stride + - package: .#gaia20 .#stride command: gaiad,strided account_prefix: cosmos,stride - - package: .#gaia18 .#neutron + - package: .#gaia20 .#neutron command: gaiad,neutrond account_prefix: cosmos,neutron steps: @@ -218,12 +218,13 @@ jobs: --features interchain-security,ica interchain_security:: interchain-security-icq: + if: false # Disable CCQ test runs-on: ubuntu-20.04 strategy: fail-fast: false matrix: chain: - - package: .#gaia18 .#stride-no-admin + - package: .#gaia20 .#stride-no-admin command: gaiad,strided account_prefix: cosmos,stride steps: @@ -264,7 +265,7 @@ jobs: fail-fast: false matrix: chain: - - package: .#celestia .#gaia18 + - package: .#celestia .#gaia20 command: celestia-appd,gaiad account_prefix: celestia,cosmos native_token: utia,stake @@ -301,3 +302,46 @@ jobs: nix shell .#python ${{ matrix.chain.package }} -c \ cargo nextest run -p ibc-integration-test --no-fail-fast --failure-output final --test-threads=2 \ --features celestia + + # Run the transfer test using IPv6 for gRPC endpoint + ipv6-grpc-endpoint: + runs-on: ubuntu-20.04 + strategy: + fail-fast: false + matrix: + chain: + - package: .#gaia20 + command: gaiad + account_prefix: cosmos + steps: + - uses: actions/checkout@v4 + - name: Install Nix + uses: DeterminateSystems/nix-installer-action@main + with: + extra-conf: | + substituters = https://cache.nixos.org + trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= + - name: Install Cachix + uses: cachix/cachix-action@v15 + with: + name: cosmos-nix + - uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: stable + - uses: actions-rs/cargo@v1 + with: + command: test + args: -p ibc-integration-test --no-fail-fast --no-run + - name: Install cargo-nextest + run: curl -LsSf https://get.nexte.st/latest/linux | tar zxf - -C ${CARGO_HOME:-~/.cargo}/bin + - env: + RUST_LOG: info + RUST_BACKTRACE: 1 + NO_COLOR_LOG: 1 + CHAIN_COMMAND_PATHS: ${{ matrix.chain.command }} + ACCOUNT_PREFIXES: ${{ matrix.chain.account_prefix }} + IPV6_GRPC: true + run: | + nix shell ${{ matrix.chain.package }} -c \ + cargo nextest run -p ibc-integration-test --no-fail-fast --failure-output final --test-threads=2 \ + tests::transfer::test_ibc_transfer diff --git a/.github/workflows/misbehaviour.yml b/.github/workflows/misbehaviour.yml index 43dfc6635d..135ef7c9a1 100644 --- a/.github/workflows/misbehaviour.yml +++ b/.github/workflows/misbehaviour.yml @@ -43,20 +43,20 @@ jobs: fail-fast: false matrix: chain: - - package: gaia18 + - package: gaia20 command: gaiad account_prefix: cosmos steps: - uses: actions/checkout@v4 - name: Install Nix uses: DeterminateSystems/nix-installer-action@main - with: + with: extra-conf: | substituters = https://cache.nixos.org trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= - name: Install Cachix uses: cachix/cachix-action@v15 - with: + with: name: cosmos-nix - name: Install sconfig uses: jaxxstorm/action-install-gh-release@v1.12.0 @@ -102,13 +102,13 @@ jobs: - uses: actions/checkout@v4 - name: Install Nix uses: DeterminateSystems/nix-installer-action@main - with: + with: extra-conf: | substituters = https://cache.nixos.org trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= - name: Install Cachix uses: cachix/cachix-action@v15 - with: + with: name: cosmos-nix - name: Install sconfig uses: jaxxstorm/action-install-gh-release@v1.12.0 @@ -154,13 +154,13 @@ jobs: - uses: actions/checkout@v4 - name: Install Nix uses: DeterminateSystems/nix-installer-action@main - with: + with: extra-conf: | substituters = https://cache.nixos.org trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= - name: Install Cachix uses: cachix/cachix-action@v15 - with: + with: name: cosmos-nix - name: Install sconfig uses: jaxxstorm/action-install-gh-release@v1.12.0 @@ -193,7 +193,6 @@ jobs: run: | nix shell .#${{ matrix.chain.package }} -c bash light_client_attack_freeze_test.sh - ics-double-sign: runs-on: ubuntu-20.04 timeout-minutes: 20 @@ -207,13 +206,13 @@ jobs: - uses: actions/checkout@v4 - name: Install Nix uses: DeterminateSystems/nix-installer-action@main - with: + with: extra-conf: | substituters = https://cache.nixos.org trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= - name: Install Cachix uses: cachix/cachix-action@v15 - with: + with: name: cosmos-nix - name: Install sconfig uses: jaxxstorm/action-install-gh-release@v1.12.0 @@ -245,4 +244,3 @@ jobs: working-directory: ci/misbehaviour-ics run: | nix shell .#${{ matrix.chain.package }} -c bash double_sign_test.sh - diff --git a/.github/workflows/multi-chains.yaml b/.github/workflows/multi-chains.yaml index 43846c1347..9aeb907848 100644 --- a/.github/workflows/multi-chains.yaml +++ b/.github/workflows/multi-chains.yaml @@ -58,7 +58,7 @@ jobs: fail-fast: false matrix: first-package: - - package: gaia18 + - package: gaia20 command: gaiad account_prefix: cosmos - package: ibc-go-v7-simapp diff --git a/Cargo.lock b/Cargo.lock index e2ce18f2fc..d971a972a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -65,6 +65,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anstream" version = "0.6.15" @@ -116,9 +131,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.90" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37bf3594c4c988a53154954629820791dde498571819ae4ca50ca811e060cc95" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "arc-swap" @@ -157,7 +172,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.87", ] [[package]] @@ -168,7 +183,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.87", ] [[package]] @@ -478,9 +493,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.7.2" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" dependencies = [ "serde", ] @@ -506,6 +521,20 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets 0.52.6", +] + [[package]] name = "clap" version = "3.2.25" @@ -639,6 +668,19 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "cosmos-sdk-proto" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c1a5856db92cd90dddc955bce308703d3519fb33ae3d0b8f3658e9cfd05c3f" +dependencies = [ + "informalsystems-pbjson", + "prost", + "serde", + "tendermint-proto", + "tonic", +] + [[package]] name = "cpufeatures" version = "0.2.13" @@ -724,7 +766,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.87", ] [[package]] @@ -792,7 +834,7 @@ checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.87", ] [[package]] @@ -1125,7 +1167,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.87", ] [[package]] @@ -1508,6 +1550,29 @@ dependencies = [ "tracing", ] +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "ibc-chain-registry" version = "0.29.3" @@ -1549,12 +1614,13 @@ dependencies = [ [[package]] name = "ibc-proto" -version = "0.47.1" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c852d22b782d2d793f4a646f968de419be635e02bc8798d5d74a6e44eef27733" +checksum = "07b9db9a33cb15d6eb56105cd9b70db016e56bcae74a8adaa1627e5b24d1f1d2" dependencies = [ "base64 0.22.1", "bytes", + "cosmos-sdk-proto", "flex-error", "ics23", "informalsystems-pbjson", @@ -1744,6 +1810,7 @@ dependencies = [ name = "ibc-test-framework" version = "0.29.3" dependencies = [ + "chrono", "color-eyre", "crossbeam-channel", "eyre", @@ -2090,9 +2157,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "oneline-eyre" @@ -2278,7 +2345,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.87", ] [[package]] @@ -2379,9 +2446,9 @@ dependencies = [ [[package]] name = "prost" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2ecbe40f08db5c006b5764a2645f7f3f141ce756412ac9e1dd6087e6d32995" +checksum = "7b0487d90e047de87f984913713b85c601c05609aad5b0df4b4573fbf69aa13f" dependencies = [ "bytes", "prost-derive", @@ -2389,24 +2456,15 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acf0c195eebb4af52c752bec4f52f645da98b6e92077a04110c7f349477ae5ac" +checksum = "e9552f850d5f0964a4e4d0bf306459ac29323ddfbae05e35a7c0d35cb0803cc5" dependencies = [ "anyhow", "itertools", "proc-macro2", "quote", - "syn 2.0.76", -] - -[[package]] -name = "prost-types" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cee5168b05f49d4b0ca581206eb14a7b22fafd963efe729ac48eb03266e25cc2" -dependencies = [ - "prost", + "syn 2.0.87", ] [[package]] @@ -2500,9 +2558,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", @@ -2723,6 +2781,19 @@ dependencies = [ "security-framework", ] +[[package]] +name = "rustls-native-certs" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcaf18a4f2be7326cd874a5fa579fae794320a0f388d365dca7e480e55f83f8a" +dependencies = [ + "openssl-probe", + "rustls-pemfile 2.1.3", + "rustls-pki-types", + "schannel", + "security-framework", +] + [[package]] name = "rustls-pemfile" version = "1.0.4" @@ -2909,9 +2980,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.210" +version = "1.0.214" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" dependencies = [ "serde_derive", ] @@ -2937,13 +3008,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.210" +version = "1.0.214" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.87", ] [[package]] @@ -2976,7 +3047,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.87", ] [[package]] @@ -3015,9 +3086,9 @@ dependencies = [ [[package]] name = "serial_test" -version = "3.1.1" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b4b487fe2acf240a021cf57c6b2b4903b1e78ca0ecd862a71b71d2a51fed77d" +checksum = "1b258109f244e1d6891bf1053a55d63a5cd4f8f4c30cf9a1280989f80e7a1fa9" dependencies = [ "futures", "log", @@ -3029,13 +3100,13 @@ dependencies = [ [[package]] name = "serial_test_derive" -version = "3.1.1" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82fe9db325bcef1fbcde82e078a5cc4efdf787e96b3b9cf45b50b529f2083d67" +checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.87", ] [[package]] @@ -3211,7 +3282,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.76", + "syn 2.0.87", ] [[package]] @@ -3248,9 +3319,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.76" +version = "2.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578e081a14e0cefc3279b0472138c513f37b41a08d5a3cca9b6e4e8ceb6cd525" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" dependencies = [ "proc-macro2", "quote", @@ -3323,9 +3394,9 @@ dependencies = [ [[package]] name = "tendermint" -version = "0.38.1" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "505d9d6ffeb83b1de47c307c6e0d2dff56c6256989299010ad03cd80a8491e97" +checksum = "37d513ce7f9e41c67ab2dd3d554ef65f36fbcc61745af1e1f93eafdeefa1ce37" dependencies = [ "bytes", "digest 0.10.7", @@ -3337,7 +3408,6 @@ dependencies = [ "num-traits", "once_cell", "prost", - "prost-types", "ripemd", "serde", "serde_bytes", @@ -3354,9 +3424,9 @@ dependencies = [ [[package]] name = "tendermint-config" -version = "0.38.1" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de111ea653b2adaef627ac2452b463c77aa615c256eaaddf279ec5a1cf9775f" +checksum = "4de4e66e78c6bfb768993e69c4fc5333dbc863f6d54ebd7a5d08d91556768087" dependencies = [ "flex-error", "serde", @@ -3368,9 +3438,9 @@ dependencies = [ [[package]] name = "tendermint-light-client" -version = "0.38.1" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91e5abb448c65e8abdfe0e17a3a189e005a71b4169b89f36aaa2053ff239577" +checksum = "3e88c08a112db05101396a79f71c017d7dbf548dc21614f82251f17ecbe5d5e8" dependencies = [ "contracts", "crossbeam-channel", @@ -3393,9 +3463,9 @@ dependencies = [ [[package]] name = "tendermint-light-client-detector" -version = "0.38.1" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb1ac1607eb7a3393313558b339c36eebeba15aa7f2d101d1d47299e65825152" +checksum = "d48a431ea923182c37ca9f3cc8333490ac6746a64520d1c4a3dd18c08b0806ac" dependencies = [ "crossbeam-channel", "derive_more", @@ -3416,9 +3486,9 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" -version = "0.38.1" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2674adbf0dc51aa0c8eaf8462c7d6692ec79502713e50ed5432a442002be90" +checksum = "7affc5fffe9df158185e15bce3e47fc3a0c901e6708f3b7d33f0867d7aef8ce1" dependencies = [ "derive_more", "flex-error", @@ -3429,14 +3499,13 @@ dependencies = [ [[package]] name = "tendermint-proto" -version = "0.38.1" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ed14abe3b0502a3afe21ca74ca5cdd6c7e8d326d982c26f98a394445eb31d6e" +checksum = "c81ba1b023ec00763c3bc4f4376c67c0047f185cccf95c416c7a2f16272c4cbb" dependencies = [ "bytes", "flex-error", "prost", - "prost-types", "serde", "serde_bytes", "subtle-encoding", @@ -3445,9 +3514,9 @@ dependencies = [ [[package]] name = "tendermint-rpc" -version = "0.38.1" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02f96a2b8a0d3d0b59e4024b1a6bdc1589efc6af4709d08a480a20cc4ba90f63" +checksum = "4d3ec9d6a266cb079a44272189b5a033227d058ab28659722557c1f7fed6b83c" dependencies = [ "async-trait", "async-tungstenite", @@ -3479,9 +3548,9 @@ dependencies = [ [[package]] name = "tendermint-testgen" -version = "0.38.1" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae007e2918414ae96e4835426aace7538d23b8ddf96d71e23d241f58f386e877" +checksum = "7d97c36f54bf8754292166604e0c1a16cdbac3c7a2b59cb866f068b25fb0c811" dependencies = [ "ed25519-consensus", "gumdrop", @@ -3521,7 +3590,7 @@ checksum = "5999e24eaa32083191ba4e425deb75cdf25efefabe5aaccb7446dd0d4122a3f5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.87", ] [[package]] @@ -3532,22 +3601,22 @@ checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" [[package]] name = "thiserror" -version = "1.0.64" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.64" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.87", ] [[package]] @@ -3659,7 +3728,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.87", ] [[package]] @@ -3763,9 +3832,9 @@ dependencies = [ [[package]] name = "tonic" -version = "0.12.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6f6ba989e4b2c58ae83d862d3a3e27690b6e3ae630d0deb59f3697f32aa88ad" +checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52" dependencies = [ "async-stream", "async-trait", @@ -3782,7 +3851,7 @@ dependencies = [ "percent-encoding", "pin-project", "prost", - "rustls-native-certs 0.7.3", + "rustls-native-certs 0.8.0", "rustls-pemfile 2.1.3", "socket2", "tokio", @@ -3846,7 +3915,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.87", ] [[package]] @@ -4130,7 +4199,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.87", "wasm-bindgen-shared", ] @@ -4164,7 +4233,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.87", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4216,6 +4285,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -4401,7 +4479,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.87", ] [[package]] @@ -4421,5 +4499,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.87", ] diff --git a/Cargo.toml b/Cargo.toml index 97e40befb7..47f9d39fe2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,14 +29,19 @@ ibc-telemetry = { version = "0.29.3", path = "crates/telemetry" } ibc-test-framework = { version = "0.29.3", path = "tools/test-framework" } ibc-integration-test = { version = "0.29.3", path = "tools/integration-test" } +# IBC dependencies +ibc-proto = "0.51.0" +ics23 = "0.12.0" + # Tendermint dependencies -tendermint = { version = "0.38.1", default-features = false } -tendermint-light-client = { version = "0.38.1", default-features = false } -tendermint-light-client-detector = { version = "0.38.1", default-features = false } -tendermint-light-client-verifier = { version = "0.38.1", default-features = false } -tendermint-proto = { version = "0.38.1" } -tendermint-rpc = { version = "0.38.1" } -tendermint-testgen = { version = "0.38.1" } +tendermint = { version = "0.40.0", default-features = false } +tendermint-light-client = { version = "0.40.0", default-features = false } +tendermint-light-client-detector = { version = "0.40.0", default-features = false } +tendermint-light-client-verifier = { version = "0.40.0", default-features = false } +tendermint-proto = { version = "0.40.0" } +tendermint-rpc = { version = "0.40.0" } +tendermint-testgen = { version = "0.40.0" } + # Other dependencies abscissa_core = "=0.6.0" @@ -48,7 +53,7 @@ bech32 = "0.9.1" bitcoin = "0.31.2" bs58 = "0.5.1" byte-unit = { version = "4.0.19", default-features = false } -bytes = "1.7.2" +bytes = "1.8.0" clap = "3.2" clap_complete = "3.2" color-eyre = "0.6" @@ -72,13 +77,11 @@ hex = "0.4.3" http = "1.0.0" humantime = "2.1.0" humantime-serde = "1.1.1" -ibc-proto = "0.47.1" -ics23 = "0.12.0" itertools = "0.13.0" moka = "0.12.8" num-bigint = "0.4" num-rational = "0.4.1" -once_cell = "1.19.0" +once_cell = "1.20.2" oneline-eyre = "0.1" opentelemetry = "0.19.0" opentelemetry-prometheus = "0.12.0" @@ -86,24 +89,24 @@ primitive-types = { version = "0.12.1", default-features = false } prometheus = "0.13.4" prost = "0.13" rand = "0.8.5" -regex = "1.11.0" +regex = "1.11.1" reqwest = { version = "0.11.27", default-features = false } retry = { version = "2.0.0", default-features = false } ripemd = "0.1.3" secp256k1 = "0.28.2" semver = "1.0.21" -serde = "1.0.210" +serde = "1.0.214" serde_derive = "1.0.104" serde_json = "1.0.132" serde_yaml = "0.9.34" -serial_test = "3.1.1" +serial_test = "3.2.0" sha2 = "0.10.6" signal-hook = "0.3.17" signature = "2.1.0" strum = "0.25" subtle-encoding = "0.5.1" test-log = "0.2.14" -thiserror = "1.0.64" +thiserror = "1.0.69" time = "0.3" tiny-bip39 = "1.0.0" tiny-keccak = { version = "2.0.2", default-features = false } diff --git a/ci/misbehaviour-ics/double_sign_test.sh b/ci/misbehaviour-ics/double_sign_test.sh index 3685868433..4829f59c0f 100644 --- a/ci/misbehaviour-ics/double_sign_test.sh +++ b/ci/misbehaviour-ics/double_sign_test.sh @@ -1,6 +1,10 @@ #!/bin/bash # shellcheck disable=2086,2004 +## Prerequisites: +# * ICS v6.x +# * Hermes v1.10.3+45a29cc00 + set -eu DEBUG=${DEBUG:-false} @@ -9,13 +13,6 @@ if [ "$DEBUG" = true ]; then set -x fi -# User balance of stake tokens -USER_COINS="100000000000stake" -# Amount of stake tokens staked -STAKE="100000000stake" -# Node IP address -NODE_IP="127.0.0.1" - # Home directory HOME_DIR="/tmp/hermes-ics-double-sign" @@ -25,11 +22,19 @@ if [ "$DEBUG" = true ]; then else HERMES_DEBUG="" fi + # Hermes config HERMES_CONFIG="$HOME_DIR/hermes.toml" # Hermes binary HERMES_BIN="cargo run -q --bin hermes -- $HERMES_DEBUG --config $HERMES_CONFIG" +# User balance of stake tokens +USER_COINS="100000000000stake" +# Amount of stake tokens staked +STAKE="100000000stake" +# Node IP address +NODE_IP="127.0.0.1" + # Validator moniker MONIKERS=("coordinator" "alice" "bob") LEAD_VALIDATOR_MONIKER="coordinator" @@ -51,13 +56,8 @@ CLIENT_BASEPORT=29220 # Clean start pkill -f interchain-security-pd &> /dev/null || true -pkill -f interchain-security-cd &> /dev/null || true -pkill -f hermes &> /dev/null || true sleep 1 - -mkdir -p "${HOME_DIR}" -rm -rf "${PROV_NODES_ROOT_DIR}" -rm -rf "${CONS_NODES_ROOT_DIR}" +rm -rf ${PROV_NODES_ROOT_DIR} # Let lead validator create genesis file LEAD_VALIDATOR_PROV_DIR=${PROV_NODES_ROOT_DIR}/provider-${LEAD_VALIDATOR_MONIKER} @@ -70,7 +70,6 @@ do MONIKER=${MONIKERS[$index]} # validator key PROV_KEY=${MONIKER}-key - PROV_KEY2=${MONIKER}-key2 # home directory of this validator on provider PROV_NODE_DIR=${PROV_NODES_ROOT_DIR}/provider-${MONIKER} @@ -80,16 +79,17 @@ do # Build genesis file and node directory structure interchain-security-pd init $MONIKER --chain-id provider --home ${PROV_NODE_DIR} - jq ".app_state.gov.params.voting_period = \"5s\" | .app_state.staking.params.unbonding_time = \"86400s\"" \ + jq ".app_state.gov.params.voting_period = \"10s\" \ + | .app_state.gov.params.expedited_voting_period = \"9s\" \ + | .app_state.staking.params.unbonding_time = \"86400s\" \ + | .app_state.provider.params.blocks_per_epoch = \"5\"" \ ${PROV_NODE_DIR}/config/genesis.json > \ ${PROV_NODE_DIR}/edited_genesis.json && mv ${PROV_NODE_DIR}/edited_genesis.json ${PROV_NODE_DIR}/config/genesis.json - sleep 1 # Create account keypair - interchain-security-pd keys add $PROV_KEY --home ${PROV_NODE_DIR} --keyring-backend test --output json > ${PROV_NODE_DIR}/${PROV_KEY}.json 2>&1 - interchain-security-pd keys add $PROV_KEY2 --home ${PROV_NODE_DIR} --keyring-backend test --output json > ${PROV_NODE_DIR}/${PROV_KEY2}.json 2>&1 + interchain-security-pd keys add $PROV_KEY --home ${PROV_NODE_DIR} --keyring-backend test --output json > ${PROV_NODE_DIR}/${PROV_KEY}.json 2>&1 sleep 1 # copy genesis in, unless this validator is the lead validator @@ -100,9 +100,6 @@ do # Add stake to user PROV_ACCOUNT_ADDR=$(jq -r '.address' ${PROV_NODE_DIR}/${PROV_KEY}.json) interchain-security-pd genesis add-genesis-account $PROV_ACCOUNT_ADDR $USER_COINS --home ${PROV_NODE_DIR} --keyring-backend test - - PROV_ACCOUNT_ADDR2=$(jq -r '.address' ${PROV_NODE_DIR}/${PROV_KEY2}.json) - interchain-security-pd genesis add-genesis-account $PROV_ACCOUNT_ADDR2 $USER_COINS --home ${PROV_NODE_DIR} --keyring-backend test sleep 1 # copy genesis out, unless this validator is the lead validator @@ -141,10 +138,10 @@ do fi # Stake 1/1000 user's coins - interchain-security-pd genesis gentx $PROV_KEY $STAKE --chain-id provider --home ${PROV_NODE_DIR} --keyring-backend test --moniker $MONIKER + interchain-security-pd genesis gentx $PROV_KEY $STAKE --chain-id provider --home ${PROV_NODE_DIR} --keyring-backend test --moniker $MONIKER sleep 1 - # Copy gentxs to the lead validator for possible future collection. + # Copy gentxs to the lead validator for possible future collection. # Obviously we don't need to copy the first validator's gentx to itself if [ $MONIKER != $LEAD_VALIDATOR_MONIKER ]; then cp ${PROV_NODE_DIR}/config/gentx/* ${LEAD_VALIDATOR_PROV_DIR}/config/gentx/ @@ -183,7 +180,6 @@ do # validator key PROV_KEY=${MONIKER}-key - PROV_KEY2=${MONIKER}-key2 # home directory of this validator on provider PROV_NODE_DIR=${PROV_NODES_ROOT_DIR}/provider-${MONIKER} @@ -219,89 +215,217 @@ do done # Build consumer chain proposal file -tee ${LEAD_VALIDATOR_PROV_DIR}/consumer-proposal.json< /dev/null || true +sleep 1 +rm -rf ${CONS_NODES_ROOT_DIR} + for index in "${!MONIKERS[@]}" do MONIKER=${MONIKERS[$index]} # validator key PROV_KEY=${MONIKER}-key - PROV_KEY2=${MONIKER}-key2 PROV_NODE_DIR=${PROV_NODES_ROOT_DIR}/provider-${MONIKER} @@ -314,8 +438,7 @@ do sleep 1 # Create account keypair - interchain-security-cd keys add $PROV_KEY --home ${CONS_NODE_DIR} --keyring-backend test --output json > ${CONS_NODE_DIR}/${PROV_KEY}.json 2>&1 - interchain-security-cd keys add $PROV_KEY2 --home ${CONS_NODE_DIR} --keyring-backend test --output json > ${CONS_NODE_DIR}/${PROV_KEY2}.json 2>&1 + interchain-security-cd keys add $PROV_KEY --home ${CONS_NODE_DIR} --keyring-backend test --output json > ${CONS_NODE_DIR}/${PROV_KEY}.json 2>&1 sleep 1 # copy genesis in, unless this validator is the lead validator @@ -326,15 +449,15 @@ do # Add stake to user CONS_ACCOUNT_ADDR=$(jq -r '.address' ${CONS_NODE_DIR}/${PROV_KEY}.json) interchain-security-cd genesis add-genesis-account $CONS_ACCOUNT_ADDR $USER_COINS --home ${CONS_NODE_DIR} - CONS_ACCOUNT_ADDR2=$(jq -r '.address' ${CONS_NODE_DIR}/${PROV_KEY2}.json) - interchain-security-cd genesis add-genesis-account $CONS_ACCOUNT_ADDR2 $USER_COINS --home ${CONS_NODE_DIR} - sleep 10 ### this probably does not have to be done for each node # Add consumer genesis states to genesis file RPC_LADDR_PORT=$(($RPC_LADDR_BASEPORT + $index)) RPC_LADDR=tcp://${NODE_IP}:${RPC_LADDR_PORT} - interchain-security-pd query provider consumer-genesis consumer --home ${PROV_NODE_DIR} --node ${RPC_LADDR} -o json > consumer_gen.json + interchain-security-pd query provider consumer-genesis $CONSUMER_ID \ + --home ${PROV_NODE_DIR} \ + --node ${RPC_LADDR} -o json > consumer_gen.json + jq -s '.[0].app_state.ccvconsumer = .[1] | .[0]' ${CONS_NODE_DIR}/config/genesis.json consumer_gen.json > ${CONS_NODE_DIR}/edited_genesis.json \ && mv ${CONS_NODE_DIR}/edited_genesis.json ${CONS_NODE_DIR}/config/genesis.json rm consumer_gen.json @@ -376,7 +499,6 @@ done sleep 1 - for index in "${!MONIKERS[@]}" do MONIKER=${MONIKERS[$index]} @@ -437,42 +559,13 @@ do sleep 6 done -## Cause double signing - -# create directory for double signing node -mkdir $CONS_NODES_ROOT_DIR/consumer-bob-sybil/ -cp -r $CONS_NODES_ROOT_DIR/consumer-bob/* $CONS_NODES_ROOT_DIR/consumer-bob-sybil - -# clear state in consumer-bob-sybil -echo '{"height": "0","round": 0,"step": 0,"signature":"","signbytes":""}' > $CONS_NODES_ROOT_DIR/consumer-bob-sybil/data/priv_validator_state.json - -# add new node key to sybil -# key was generated using gaiad init -# if the node key is not unique, double signing cannot be achieved -# and errors such as this can be seen in the terminal -# 5:54PM ERR found conflicting vote from ourselves; did you unsafe_reset a validator? height=1961 module=consensus round=0 type=2 -# 5:54PM ERR failed to process message err="conflicting votes from validator C888306A908A217B9A943D1DAD8790044D0947A4" -echo '{"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"tj55by/yYwruSz4NxsOG9y9k2WrPvKLXKQdz/9jL9Uptmi647OYpcisjwf92TyA+wCUYVDOgW7D53Q+638l9/w=="}}' > $CONS_NODES_ROOT_DIR/consumer-bob-sybil/config/node_key.json - -# does not use persistent peers; will do a lookup in genesis.json to find peers -#ARGS="--address tcp://$CHAIN_PREFIX.252:26655 --rpc.laddr tcp://$CHAIN_PREFIX.252:26658 --grpc.address $CHAIN_PREFIX.252:9091 --log_level trace --p2p.laddr tcp://$CHAIN_PREFIX.252:26656 --grpc-web.enable=false" - -# start double signing node - it should not talk to the node with the same key -#ip netns exec $HOME/nodes/consumer/consumer-bob-sybil $BIN $ARGS --home $HOME/nodes/consumer/consumer-bob-sybil start &> $HOME/nodes/consumer/consumer-bob-sybil/logs & - -# Start gaia -interchain-security-cd start \ - --home $CONS_NODES_ROOT_DIR/consumer-bob-sybil \ - --p2p.persistent_peers ${PERSISTENT_PEERS} \ - --rpc.laddr tcp://${NODE_IP}:29179 \ - --grpc.address ${NODE_IP}:29199 \ - --address tcp://${NODE_IP}:29209 \ - --p2p.laddr tcp://${NODE_IP}:29189 \ - --grpc-web.enable=false &> $CONS_NODES_ROOT_DIR/consumer-bob-sybil/logs & +## Setup Hermes -# Setup Hermes config file +HERMES_PROV_NODE_DIR=${PROV_NODES_ROOT_DIR}/provider-${HERMES_VALIDATOR_MONIKER} +HERMES_KEY=${HERMES_VALIDATOR_MONIKER}-key +HERMES_CONS_NODE_DIR=${CONS_NODES_ROOT_DIR}/consumer-${HERMES_VALIDATOR_MONIKER} -tee $HERMES_CONFIG< $HOME_DIR/hermes-start-logs.txt & +sleep 5 + +## Cause double signing + +CONS_NODE_SYBIL_DIR=${CONS_NODES_ROOT_DIR}/consumer-${MONIKER}-sybil + +# create directory for double signing node +mkdir $CONS_NODE_SYBIL_DIR +cp -r $CONS_NODE_DIR/* $CONS_NODE_SYBIL_DIR + +# clear state in sybil node directory +echo '{"height": "0","round": 0,"step": 0,"signature":"","signbytes":""}' \ + > $CONS_NODE_SYBIL_DIR/data/priv_validator_state.json + +# add new node key to sybil +# key was generated using gaiad init +# if the node key is not unique, double signing cannot be achieved +# and errors such as this can be seen in the terminal +# 5:54PM ERR found conflicting vote from ourselves; did you unsafe_reset a validator? height=1961 module=consensus round=0 type=2 +# 5:54PM ERR failed to process message err="conflicting votes from validator C888306A908A217B9A943D1DAD8790044D0947A4" +echo '{"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"tj55by/yYwruSz4NxsOG9y9k2WrPvKLXKQdz/9jL9Uptmi647OYpcisjwf92TyA+wCUYVDOgW7D53Q+638l9/w=="}}' \ + > $CONS_NODE_SYBIL_DIR/config/node_key.json + +# does not use persistent peers; will do a lookup in genesis.json to find peers +# start double signing node - it should not talk to the node with the same key + +# Start gaia +interchain-security-cd start \ + --home ${CONS_NODE_SYBIL_DIR} \ + --rpc.laddr tcp://${NODE_IP}:$((RPC_LADDR_PORT+1)) \ + --grpc.address ${NODE_IP}:$((GRPC_LADDR_PORT+1)) \ + --address tcp://${NODE_IP}:$((NODE_ADDRESS_PORT+1)) \ + --p2p.laddr tcp://${NODE_IP}:$((P2P_LADDR_PORT+1)) \ + --grpc-web.enable=false &> ${CONS_NODE_SYBIL_DIR}/logs & + +sleep 5 + +## start Hermes in evidence mode +$HERMES_BIN evidence --chain consumer --check-past-blocks 0 &> $HOME_DIR/hermes-evidence-logs.txt & -$HERMES_BIN evidence --chain consumer --key-name evidence &> $HOME_DIR/hermes-evidence-logs.txt & +sleep 1 + +# Wait for Hermes to submit double signing evidence +$HERMES_BIN update client --host-chain consumer --client 07-tendermint-0 for _ in $(seq 1 10) do sleep 5 MSG="successfully submitted double voting evidence to chain" - + if grep -c "$MSG" $HOME_DIR/hermes-evidence-logs.txt; then echo "[SUCCESS] Successfully submitted double voting evidence to provider chain" exit 0 @@ -600,9 +717,6 @@ done echo "[ERROR] Failed to submit double voting evidence to provider chain" echo "" echo "---------------------------------------------------------------" -echo "Hermes start logs:" -cat $HOME_DIR/hermes-start-logs.txt -echo "---------------------------------------------------------------" echo "Hermes evidence logs:" cat $HOME_DIR/hermes-evidence-logs.txt echo "---------------------------------------------------------------" diff --git a/ci/misbehaviour-ics/light_client_attack_freeze_test.sh b/ci/misbehaviour-ics/light_client_attack_freeze_test.sh index 24661ace22..2cd1b50f58 100644 --- a/ci/misbehaviour-ics/light_client_attack_freeze_test.sh +++ b/ci/misbehaviour-ics/light_client_attack_freeze_test.sh @@ -85,7 +85,10 @@ rm -rf "${CONS_FORK_NODE_DIR}" # Build genesis file and node directory structure interchain-security-pd init $MONIKER --chain-id provider --home ${PROV_NODE_DIR} -jq ".app_state.gov.params.voting_period = \"5s\" | .app_state.staking.params.unbonding_time = \"86400s\"" \ +jq ".app_state.gov.params.voting_period = \"5s\" \ +| .app_state.gov.params.expedited_voting_period = \"4s\" \ +| .app_state.staking.params.unbonding_time = \"86400s\" \ +| .app_state.provider.params.blocks_per_epoch = \"5\"" \ ${PROV_NODE_DIR}/config/genesis.json > \ ${PROV_NODE_DIR}/edited_genesis.json && mv ${PROV_NODE_DIR}/edited_genesis.json ${PROV_NODE_DIR}/config/genesis.json @@ -160,31 +163,171 @@ interchain-security-pd start \ waiting 10 "for provider sub-node to start" # Build consumer chain proposal file -tee ${PROV_NODE_DIR}/consumer-proposal.json< consumer_gen.json +interchain-security-pd query provider consumer-genesis $CONSUMER_ID --home ${PROV_NODE_DIR} -o json > consumer_gen.json jq -s '.[0].app_state.ccvconsumer = .[1] | .[0]' ${CONS_NODE_DIR}/config/genesis.json consumer_gen.json > ${CONS_NODE_DIR}/edited_genesis.json \ && mv ${CONS_NODE_DIR}/edited_genesis.json ${CONS_NODE_DIR}/config/genesis.json rm consumer_gen.json @@ -296,7 +439,7 @@ rpc_addr = "http://${NODE_IP}:26648" rpc_timeout = "10s" store_prefix = "ibc" trusting_period = "2days" -event_source = { mode = 'push', url = 'ws://${NODE_IP}:26648/websocket' , batch_delay = '50ms' } +event_source = { mode = 'pull', interval = '500ms', max_retries = 4 } [chains.gas_price] denom = "stake" @@ -319,7 +462,7 @@ rpc_addr = "http://${NODE_IP}:26658" rpc_timeout = "10s" store_prefix = "ibc" trusting_period = "2days" -event_source = { mode = 'push', url = 'ws://${NODE_IP}:26658/websocket' , batch_delay = '50ms' } +event_source = { mode = 'pull', interval = '500ms', max_retries = 4 } [chains.gas_price] denom = "stake" @@ -419,7 +562,7 @@ rpc_addr = "http://${NODE_IP}:26638" rpc_timeout = "10s" store_prefix = "ibc" trusting_period = "2days" -event_source = { mode = 'push', url = 'ws://${NODE_IP}:26638/websocket' , batch_delay = '50ms' } +event_source = { mode = 'pull', interval = '500ms', max_retries = 4 } [chains.gas_price] denom = "stake" @@ -442,7 +585,7 @@ rpc_addr = "http://${NODE_IP}:26658" rpc_timeout = "10s" store_prefix = "ibc" trusting_period = "2days" -event_source = { mode = 'push', url = 'ws://${NODE_IP}:26658/websocket' , batch_delay = '50ms' } +event_source = { mode = 'pull', interval = '500ms', max_retries = 4 } [chains.gas_price] denom = "stake" @@ -506,6 +649,7 @@ if [ "$FROZEN_HEIGHT" != "null" ]; then diag "Client is frozen, as expected." else diag "Client is not frozen, aborting." + ${HOME_DIR}/hermes-evidence-logs.txt exit 1 fi @@ -513,6 +657,7 @@ if grep -q "found light client attack evidence" ${HOME_DIR}/hermes-evidence-logs diag "Evidence found, proceeding." else diag "Evidence not found, aborting." + cat ${HOME_DIR}/hermes-evidence-logs.txt exit 1 fi diff --git a/ci/misbehaviour-ics/light_client_attack_test.sh b/ci/misbehaviour-ics/light_client_attack_test.sh index f42982c353..9d5d93c472 100644 --- a/ci/misbehaviour-ics/light_client_attack_test.sh +++ b/ci/misbehaviour-ics/light_client_attack_test.sh @@ -85,7 +85,10 @@ rm -rf "${CONS_FORK_NODE_DIR}" # Build genesis file and node directory structure interchain-security-pd init $MONIKER --chain-id provider --home ${PROV_NODE_DIR} -jq ".app_state.gov.params.voting_period = \"5s\" | .app_state.staking.params.unbonding_time = \"86400s\"" \ +jq ".app_state.gov.params.voting_period = \"5s\" \ +| .app_state.gov.params.expedited_voting_period = \"4s\" \ +| .app_state.staking.params.unbonding_time = \"86400s\" \ +| .app_state.provider.params.blocks_per_epoch = \"5\"" \ ${PROV_NODE_DIR}/config/genesis.json > \ ${PROV_NODE_DIR}/edited_genesis.json && mv ${PROV_NODE_DIR}/edited_genesis.json ${PROV_NODE_DIR}/config/genesis.json @@ -169,33 +172,173 @@ interchain-security-pd start \ waiting 5 "for provider sub-node to start" # Build consumer chain proposal file -tee ${PROV_NODE_DIR}/consumer-proposal.json< consumer_gen.json +interchain-security-pd query provider consumer-genesis $CONSUMER_ID --home ${PROV_NODE_DIR} -o json > consumer_gen.json jq -s '.[0].app_state.ccvconsumer = .[1] | .[0]' ${CONS_NODE_DIR}/config/genesis.json consumer_gen.json > ${CONS_NODE_DIR}/edited_genesis.json \ && mv ${CONS_NODE_DIR}/edited_genesis.json ${CONS_NODE_DIR}/config/genesis.json rm consumer_gen.json @@ -305,7 +448,7 @@ rpc_addr = "http://${NODE_IP}:26648" rpc_timeout = "10s" store_prefix = "ibc" trusting_period = "2days" -event_source = { mode = 'push', url = 'ws://${NODE_IP}:26648/websocket' , batch_delay = '50ms' } +event_source = { mode = 'pull', interval = '500ms', max_retries = 4 } [chains.gas_price] denom = "stake" @@ -328,7 +471,7 @@ rpc_addr = "http://${NODE_IP}:26658" rpc_timeout = "10s" store_prefix = "ibc" trusting_period = "2days" -event_source = { mode = 'push', url = 'ws://${NODE_IP}:26658/websocket' , batch_delay = '50ms' } +event_source = { mode = 'pull', interval = '500ms', max_retries = 4 } [chains.gas_price] denom = "stake" @@ -445,7 +588,7 @@ rpc_addr = "http://${NODE_IP}:26638" rpc_timeout = "10s" store_prefix = "ibc" trusting_period = "2days" -event_source = { mode = 'push', url = 'ws://${NODE_IP}:26638/websocket' , batch_delay = '50ms' } +event_source = { mode = 'pull', interval = '500ms', max_retries = 4 } [chains.gas_price] denom = "stake" @@ -467,7 +610,7 @@ rpc_addr = "http://${NODE_IP}:26658" rpc_timeout = "10s" store_prefix = "ibc" trusting_period = "2days" -event_source = { mode = 'push', url = 'ws://${NODE_IP}:26658/websocket' , batch_delay = '50ms' } +event_source = { mode = 'pull', interval = '500ms', max_retries = 4 } [chains.gas_price] denom = "stake" @@ -479,6 +622,7 @@ event_source = { mode = 'push', url = 'ws://${NODE_IP}:26658/websocket' , batch_ EOF waiting 10 "for a couple blocks" +sleep 5 read -r height hash < <( curl -s "localhost:26648"/commit \ @@ -512,7 +656,7 @@ waiting 10 "for Hermes relayer to start" diag "Running Hermes relayer evidence command" # Run hermes in evidence mode -$HERMES_BIN evidence --chain consumer &> ${HOME_DIR}/hermes-evidence-logs.txt & +$HERMES_BIN --debug=rpc evidence --chain consumer &> ${HOME_DIR}/hermes-evidence-logs.txt & # If we sleep 5 here and above, we end up on the forked block later waiting 10 "for Hermes evidence monitor to start" @@ -555,6 +699,7 @@ if grep -q "found light client attack evidence" ${HOME_DIR}/hermes-evidence-logs diag "Evidence found, proceeding!" else diag "Evidence not found, aborting." + cat ${HOME_DIR}/hermes-evidence-logs.txt exit 1 fi diff --git a/crates/relayer-cli/src/commands/evidence.rs b/crates/relayer-cli/src/commands/evidence.rs index fa54be707b..c74c1286e3 100644 --- a/crates/relayer-cli/src/commands/evidence.rs +++ b/crates/relayer-cli/src/commands/evidence.rs @@ -18,7 +18,7 @@ use ibc_relayer::chain::endpoint::ChainEndpoint; use ibc_relayer::chain::handle::{BaseChainHandle, ChainHandle}; use ibc_relayer::chain::requests::{IncludeProof, PageRequest, QueryHeight}; use ibc_relayer::chain::tracking::TrackedMsgs; -use ibc_relayer::foreign_client::ForeignClient; +use ibc_relayer::foreign_client::{fetch_ccv_consumer_id, ForeignClient}; use ibc_relayer::spawn::spawn_chain_runtime_with_modified_config; use ibc_relayer_types::applications::ics28_ccv::msgs::ccv_double_voting::MsgSubmitIcsConsumerDoubleVoting; use ibc_relayer_types::applications::ics28_ccv::msgs::ccv_misbehaviour::MsgSubmitIcsConsumerMisbehaviour; @@ -318,10 +318,15 @@ fn submit_duplicate_vote_evidence( let signer = counterparty_chain_handle.get_signer()?; - if !is_counterparty_provider(chain, counterparty_chain_handle, counterparty_client_id) { - debug!("counterparty client `{counterparty_client_id}` on chain `{counterparty_chain_id}` is not a CCV client, skipping..."); - return Ok(ControlFlow::Continue(())); - } + let consumer_id = match fetch_ccv_consumer_id(counterparty_chain_handle, counterparty_client_id) + { + Ok(consumer_id) => consumer_id, + Err(e) => { + info!("Failed to query Consumer ID: {e}. \ + Counterparty client `{counterparty_client_id}` on chain `{counterparty_chain_id}` might not be a CCV client, skipping..."); + return Ok(ControlFlow::Continue(())); + } + }; let infraction_height = evidence.vote_a.height; @@ -359,6 +364,7 @@ fn submit_duplicate_vote_evidence( submitter: signer.clone(), duplicate_vote_evidence: evidence.clone(), infraction_block_header, + consumer_id, } .to_any(); @@ -507,42 +513,14 @@ fn submit_light_client_attack_evidence( counterparty.id(), ); - let counterparty_is_provider = - is_counterparty_provider(chain, counterparty, &counterparty_client_id); - let counterparty_client_is_frozen = counterparty_client.is_frozen(); - if !counterparty_is_provider && counterparty_client_is_frozen { - warn!( - "cannot submit light client attack evidence to client `{}` on counterparty chain `{}`", - counterparty_client_id, - counterparty.id() - ); - warn!("reason: client is frozen and chain is not a CCV provider chain"); - - return Ok(()); - } - let signer = counterparty.get_signer()?; let common_height = Height::from_tm(evidence.common_height, chain.id()); let counterparty_has_common_consensus_state = has_consensus_state(counterparty, &counterparty_client_id, common_height); - if counterparty_is_provider - && counterparty_client_is_frozen - && !counterparty_has_common_consensus_state - { - warn!( - "cannot submit light client attack evidence to client `{}` on provider chain `{}`", - counterparty_client_id, - counterparty.id() - ); - warn!("reason: client is frozen and does not have a consensus state at height {common_height}"); - - return Ok(()); - } - let mut msgs = if counterparty_has_common_consensus_state { info!( "skip building update client message for client `{}` on counterparty chain `{}`", @@ -550,8 +528,8 @@ fn submit_light_client_attack_evidence( counterparty.id() ); info!( - "reason: counterparty chain already has consensus state at common height {common_height}" - ); + "reason: counterparty chain already has consensus state at common height {common_height}" + ); Vec::new() } else { @@ -571,21 +549,31 @@ fn submit_light_client_attack_evidence( } }; - if counterparty_is_provider { + if let Ok(consumer_id) = fetch_ccv_consumer_id(counterparty, &counterparty_client_id) { + if counterparty_client_is_frozen && !counterparty_has_common_consensus_state { + warn!( + "cannot submit light client attack evidence to client `{}` on provider chain `{}`", + counterparty_client_id, + counterparty.id() + ); + warn!("reason: client is frozen and does not have a consensus state at height {common_height}"); + + return Ok(()); + } info!( "will submit consumer light client attack evidence to client `{}` on provider chain `{}`", counterparty_client_id, counterparty.id(), ); - - let msg = MsgSubmitIcsConsumerMisbehaviour { - submitter: signer.clone(), - misbehaviour: misbehaviour.clone(), - } - .to_any(); - - msgs.push(msg); - }; + msgs.push( + MsgSubmitIcsConsumerMisbehaviour { + submitter: signer.clone(), + misbehaviour: misbehaviour.clone(), + consumer_id, + } + .to_any(), + ); + } // We do not need to submit the misbehaviour if the client is already frozen. if !counterparty_client_is_frozen { @@ -662,29 +650,6 @@ fn has_consensus_state( res.is_ok() } -/// If the misbehaving chain is a CCV consumer chain, -/// then try fetch the consumer chains of the counterparty chains. -/// If that fails, then the counterparty chain is not a provider chain. -/// Otherwise, check if the misbehaving chain is a consumer of the counterparty chain, -/// which is then definitely a provider. -fn is_counterparty_provider( - chain: &CosmosSdkChain, - counterparty_chain_handle: &BaseChainHandle, - counterparty_client_id: &ClientId, -) -> bool { - if chain.config().ccv_consumer_chain { - let consumer_chains = counterparty_chain_handle - .query_consumer_chains() - .unwrap_or_default(); // If the query fails, use an empty list of consumers - - consumer_chains.iter().any(|(chain_id, client_id)| { - chain_id == chain.id() && client_id == counterparty_client_id - }) - } else { - false - } -} - /// Fetch all the counterparty clients of the given chain. /// A counterparty client is a client that has a connection with that chain. /// @@ -716,6 +681,11 @@ fn fetch_all_counterparty_clients( connection.connection_id ); + if client_id.as_str() == "09-localhost" { + debug!("skipping localhost client `{client_id}`..."); + continue; + } + debug!( "fetching client state for client `{client_id}` on connection `{}`", connection.connection_id diff --git a/crates/relayer-types/src/applications/ics28_ccv/msgs/ccv_double_voting.rs b/crates/relayer-types/src/applications/ics28_ccv/msgs/ccv_double_voting.rs index a1ce4291d9..9f07f31539 100644 --- a/crates/relayer-types/src/applications/ics28_ccv/msgs/ccv_double_voting.rs +++ b/crates/relayer-types/src/applications/ics28_ccv/msgs/ccv_double_voting.rs @@ -9,6 +9,7 @@ use crate::signer::Signer; use crate::tx_msg::Msg; use super::error::Error; +use super::ConsumerId; pub const ICS_DOUBLE_VOTING_TYPE_URL: &str = "/interchain_security.ccv.provider.v1.MsgSubmitConsumerDoubleVoting"; @@ -18,6 +19,7 @@ pub struct MsgSubmitIcsConsumerDoubleVoting { pub submitter: Signer, pub duplicate_vote_evidence: DuplicateVoteEvidence, pub infraction_block_header: Header, + pub consumer_id: ConsumerId, } impl Msg for MsgSubmitIcsConsumerDoubleVoting { @@ -57,6 +59,7 @@ impl TryFrom for MsgSubmitIcsConsumerDoubleVoting { .map_err(|e| { Error::invalid_raw_double_voting(format!("cannot convert header: {e}")) })?, + consumer_id: ConsumerId::new(raw.consumer_id), }) } } @@ -67,6 +70,7 @@ impl From for RawIcsDoubleVoting { submitter: value.submitter.to_string(), duplicate_vote_evidence: Some(value.duplicate_vote_evidence.into()), infraction_block_header: Some(value.infraction_block_header.into()), + consumer_id: value.consumer_id.to_string(), } } } diff --git a/crates/relayer-types/src/applications/ics28_ccv/msgs/ccv_misbehaviour.rs b/crates/relayer-types/src/applications/ics28_ccv/msgs/ccv_misbehaviour.rs index 8b5c6c2750..67a7d20102 100644 --- a/crates/relayer-types/src/applications/ics28_ccv/msgs/ccv_misbehaviour.rs +++ b/crates/relayer-types/src/applications/ics28_ccv/msgs/ccv_misbehaviour.rs @@ -10,6 +10,7 @@ use crate::signer::Signer; use crate::tx_msg::Msg; use super::error::Error; +use super::ConsumerId; pub const ICS_MISBEHAVIOR_TYPE_URL: &str = "/interchain_security.ccv.provider.v1.MsgSubmitConsumerMisbehaviour"; @@ -18,6 +19,7 @@ pub const ICS_MISBEHAVIOR_TYPE_URL: &str = pub struct MsgSubmitIcsConsumerMisbehaviour { pub submitter: Signer, pub misbehaviour: Misbehaviour, + pub consumer_id: ConsumerId, } impl Msg for MsgSubmitIcsConsumerMisbehaviour { @@ -48,6 +50,7 @@ impl TryFrom for MsgSubmitIcsConsumerMisbehaviour { .map_err(|_e| { Error::invalid_raw_misbehaviour("cannot convert misbehaviour".into()) })?, + consumer_id: ConsumerId::new(raw.consumer_id), }) } } @@ -57,6 +60,7 @@ impl From for RawIcsMisbehaviour { RawIcsMisbehaviour { submitter: value.submitter.to_string(), misbehaviour: Some(value.misbehaviour.into()), + consumer_id: value.consumer_id.to_string(), } } } diff --git a/crates/relayer-types/src/applications/ics28_ccv/msgs/mod.rs b/crates/relayer-types/src/applications/ics28_ccv/msgs/mod.rs index df28e4a73a..6f992ae0f7 100644 --- a/crates/relayer-types/src/applications/ics28_ccv/msgs/mod.rs +++ b/crates/relayer-types/src/applications/ics28_ccv/msgs/mod.rs @@ -1,3 +1,52 @@ pub mod ccv_double_voting; pub mod ccv_misbehaviour; pub mod error; + +use std::convert::Infallible; + +use derive_more::Display; +use ibc_proto::interchain_security::ccv::provider::v1::Chain; +use serde::{Deserialize, Serialize}; + +use crate::core::ics24_host; +use crate::core::ics24_host::identifier::{ChainId, ClientId}; + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Display, Serialize, Deserialize)] +pub struct ConsumerId(String); + +impl ConsumerId { + pub const fn new(id: String) -> Self { + Self(id) + } + + pub fn as_str(&self) -> &str { + &self.0 + } +} + +impl std::str::FromStr for ConsumerId { + type Err = Infallible; + + fn from_str(s: &str) -> Result { + Ok(Self(s.to_string())) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ConsumerChain { + pub chain_id: ChainId, + pub consumer_id: ConsumerId, + pub client_id: ClientId, +} + +impl TryFrom for ConsumerChain { + type Error = ics24_host::error::ValidationError; + + fn try_from(value: Chain) -> Result { + Ok(Self { + chain_id: ChainId::from_string(&value.chain_id), + consumer_id: ConsumerId::new(value.consumer_id), + client_id: value.client_id.parse()?, + }) + } +} diff --git a/crates/relayer-types/src/applications/ics31_icq/response.rs b/crates/relayer-types/src/applications/ics31_icq/response.rs index 98b45acf06..7a5c9bb638 100644 --- a/crates/relayer-types/src/applications/ics31_icq/response.rs +++ b/crates/relayer-types/src/applications/ics31_icq/response.rs @@ -52,11 +52,13 @@ impl CrossChainQueryResponse { pub fn try_to_any(&self, signer: Signer) -> Result { let mut encoded = vec![]; + let proof_ops = into_proof_ops(self.proof.clone()); + let msg_submit_cross_chain_query_result = MsgSubmitQueryResponse { chain_id: self.chain_id.to_string(), query_id: self.query_id.to_string(), result: self.result.clone(), - proof_ops: Some(into_proof_ops(self.proof.clone())), + proof_ops: Some(proof_ops), height: self.height, from_address: signer.as_ref().to_string(), }; diff --git a/crates/relayer-types/src/applications/transfer/error.rs b/crates/relayer-types/src/applications/transfer/error.rs index f0e52d946d..71c8d04b0f 100644 --- a/crates/relayer-types/src/applications/transfer/error.rs +++ b/crates/relayer-types/src/applications/transfer/error.rs @@ -4,7 +4,6 @@ use std::string::FromUtf8Error; use flex_error::{define_error, DisplayOnly, TraceError}; use subtle_encoding::Error as EncodingError; -use tendermint_proto::Error as TendermintProtoError; use uint::FromDecStrErr; use crate::core::ics04_channel::channel::Ordering; @@ -127,7 +126,7 @@ define_error! { | _ | { "no trace associated with specified hash" }, DecodeRawMsg - [ TraceError ] + [ TraceError ] | _ | { "error decoding raw msg" }, UnknownMsgType diff --git a/crates/relayer-types/src/applications/transfer/msgs/send.rs b/crates/relayer-types/src/applications/transfer/msgs/send.rs index 36e71dc66b..fcbb34a6bb 100644 --- a/crates/relayer-types/src/applications/transfer/msgs/send.rs +++ b/crates/relayer-types/src/applications/transfer/msgs/send.rs @@ -43,11 +43,11 @@ where type Error = Error; fn try_from(value: RawMsgSend) -> Result { - let amount: Vec> = value + let amount = value .amount .into_iter() - .map(Coin::try_from) - .collect::>, _>>()?; + .map(Coin::::try_from) + .collect::, _>>()?; Ok(MsgSend { from_address: value.from_address, to_address: value.to_address, diff --git a/crates/relayer-types/src/clients/ics07_tendermint/client_state.rs b/crates/relayer-types/src/clients/ics07_tendermint/client_state.rs index 818cf13347..44888d8afa 100644 --- a/crates/relayer-types/src/clients/ics07_tendermint/client_state.rs +++ b/crates/relayer-types/src/clients/ics07_tendermint/client_state.rs @@ -305,9 +305,9 @@ impl From for RawTmClientState { Self { chain_id: value.chain_id.to_string(), trust_level: Some(value.trust_threshold.into()), - trusting_period: Some(value.trusting_period.into()), - unbonding_period: Some(value.unbonding_period.into()), - max_clock_drift: Some(value.max_clock_drift.into()), + trusting_period: Some(value.trusting_period.try_into().unwrap()), + unbonding_period: Some(value.unbonding_period.try_into().unwrap()), + max_clock_drift: Some(value.max_clock_drift.try_into().unwrap()), frozen_height: Some(value.frozen_height.map(|height| height.into()).unwrap_or( RawHeight { revision_number: 0, diff --git a/crates/relayer-types/src/core/ics02_client/error.rs b/crates/relayer-types/src/core/ics02_client/error.rs index a560a499f1..27316e2747 100644 --- a/crates/relayer-types/src/core/ics02_client/error.rs +++ b/crates/relayer-types/src/core/ics02_client/error.rs @@ -1,5 +1,4 @@ use flex_error::{define_error, TraceError}; -use tendermint_proto::Error as TendermintProtoError; use crate::core::ics02_client::client_type::ClientType; use crate::core::ics02_client::height::HeightError; @@ -109,14 +108,14 @@ define_error! { }, DecodeRawClientState - [ TraceError ] + [ TraceError ] | _ | { "error decoding raw client state" }, MissingRawClientState | _ | { "missing raw client state" }, InvalidRawConsensusState - [ TraceError ] + [ TraceError ] | _ | { "invalid raw client consensus state" }, MissingRawConsensusState @@ -138,7 +137,7 @@ define_error! { | _ | { "invalid client identifier" }, InvalidRawHeader - [ TraceError ] + [ TraceError ] | _ | { "invalid raw header" }, MalformedHeader @@ -148,7 +147,7 @@ define_error! { | _ | { "missing raw header" }, DecodeRawMisbehaviour - [ TraceError ] + [ TraceError ] | _ | { "invalid raw misbehaviour" }, InvalidRawMisbehaviour @@ -254,19 +253,19 @@ define_error! { | e | { format_args!("the local consensus state could not be retrieved for height {}", e.height) }, InvalidConnectionEnd - [ TraceError] + [ TraceError] | _ | { "invalid connection end" }, InvalidChannelEnd - [ TraceError] + [ TraceError] | _ | { "invalid channel end" }, InvalidAnyClientState - [ TraceError] + [ TraceError] | _ | { "invalid any client state" }, InvalidAnyConsensusState - [ TraceError ] + [ TraceError ] | _ | { "invalid any client consensus state" }, Signer diff --git a/crates/relayer-types/src/core/ics02_client/events.rs b/crates/relayer-types/src/core/ics02_client/events.rs index 3abe9b7b8d..da295833ef 100644 --- a/crates/relayer-types/src/core/ics02_client/events.rs +++ b/crates/relayer-types/src/core/ics02_client/events.rs @@ -1,9 +1,10 @@ //! Types for the IBC events emitted from Tendermint Websocket by the client module. -use serde_derive::{Deserialize, Serialize}; use std::fmt::{Display, Error as FmtError, Formatter}; + +use ibc_proto::Protobuf; +use serde_derive::{Deserialize, Serialize}; use tendermint::abci; -use tendermint_proto::Protobuf; use super::header::AnyHeader; use crate::core::ics02_client::client_type::ClientType; diff --git a/crates/relayer-types/src/core/ics26_routing/error.rs b/crates/relayer-types/src/core/ics26_routing/error.rs index f51581b6e0..5a4773e5af 100644 --- a/crates/relayer-types/src/core/ics26_routing/error.rs +++ b/crates/relayer-types/src/core/ics26_routing/error.rs @@ -29,7 +29,7 @@ define_error! { | e | { format_args!("unknown type URL {0}", e.url) }, MalformedMessageBytes - [ TraceError ] + [ TraceError ] | _ | { "the message is malformed and cannot be decoded" }, } } diff --git a/crates/relayer/src/chain/cosmos.rs b/crates/relayer/src/chain/cosmos.rs index e7729c7382..1dc99d6923 100644 --- a/crates/relayer/src/chain/cosmos.rs +++ b/crates/relayer/src/chain/cosmos.rs @@ -4,6 +4,7 @@ use bytes::Bytes; use config::CosmosSdkConfig; use core::{future::Future, str::FromStr, time::Duration}; use futures::future::join_all; +use ibc_proto::interchain_security::ccv::provider::v1::QueryConsumerIdFromClientIdRequest; use itertools::Itertools; use num_bigint::BigInt; use prost::Message; @@ -22,6 +23,7 @@ use ibc_proto::ibc::apps::fee::v1::{ use ibc_proto::ibc::core::channel::v1::{QueryUpgradeErrorRequest, QueryUpgradeRequest}; use ibc_proto::interchain_security::ccv::v1::ConsumerParams as CcvConsumerParams; use ibc_proto::Protobuf; +use ibc_relayer_types::applications::ics28_ccv::msgs::{ConsumerChain, ConsumerId}; use ibc_relayer_types::applications::ics31_icq::response::CrossChainQueryResponse; use ibc_relayer_types::clients::ics07_tendermint::client_state::{ AllowUpdate, ClientState as TmClientState, @@ -367,6 +369,7 @@ impl CosmosSdkChain { } /// Performs a gRPC query to fetch CCV Consumer chain staking parameters. + /// Assumes we are the consumer chain. pub fn query_ccv_consumer_chain_params(&self) -> Result { crate::time!( "query_ccv_consumer_chain_params", @@ -2542,7 +2545,10 @@ impl ChainEndpoint for CosmosSdkChain { Ok(incentivized_response) } - fn query_consumer_chains(&self) -> Result, Error> { + fn query_consumer_chains(&self) -> Result, Error> { + use ibc_proto::interchain_security::ccv::provider::v1::ConsumerPhase; + use ibc_proto::interchain_security::ccv::provider::v1::QueryConsumerChainsRequest; + crate::time!( "query_consumer_chains", { @@ -2556,9 +2562,10 @@ impl ChainEndpoint for CosmosSdkChain { ibc_proto::interchain_security::ccv::provider::v1::query_client::QueryClient::new, ))?; - let request = tonic::Request::new( - ibc_proto::interchain_security::ccv::provider::v1::QueryConsumerChainsRequest {}, - ); + let request = tonic::Request::new(QueryConsumerChainsRequest { + phase: ConsumerPhase::Launched as i32, + pagination: Some(PageRequest::all().into()), + }); let response = self .block_on(client.query_consumer_chains(request)) @@ -2568,8 +2575,8 @@ impl ChainEndpoint for CosmosSdkChain { let result = response .chains .into_iter() - .map(|c| (c.chain_id.parse().unwrap(), c.client_id.parse().unwrap())) - .collect(); + .map(|c| ConsumerChain::try_from(c).map_err(Error::ics24_host_validation_error)) + .collect::, _>>()?; Ok(result) } @@ -2631,6 +2638,40 @@ impl ChainEndpoint for CosmosSdkChain { IncludeProof::No => Ok((error_receipt, None)), } } + + /// Performs a gRPC query to fetch the CCV ConsumerID corresponding + /// to the given ClientID. + /// + /// Assumes we are the provider chain. + fn query_ccv_consumer_id(&self, client_id: ClientId) -> Result { + use ibc_proto::interchain_security::ccv::provider::v1::query_client::QueryClient; + + crate::telemetry!(query, &self.config.id, "query_ccv_consumer_id"); + crate::time!( + "query_ccv_consumer_id", + { + "src_chain": &self.config.id, + } + ); + + let grpc_addr = Uri::from_str(&self.config.grpc_addr.to_string()) + .map_err(|e| Error::invalid_uri(self.config.grpc_addr.to_string(), e))?; + + let mut client = self + .block_on(create_grpc_client(&grpc_addr, QueryClient::new))? + .max_decoding_message_size(self.config.max_grpc_decoding_size.get_bytes() as usize); + + let request = tonic::Request::new(QueryConsumerIdFromClientIdRequest { + client_id: client_id.to_string(), + }); + + let response = self + .block_on(client.query_consumer_id_from_client_id(request)) + .map_err(|e| Error::grpc_status(e, "query_ccv_consumer_id".to_owned()))?; + + let consumer_id = response.into_inner().consumer_id; + Ok(ConsumerId::new(consumer_id)) + } } fn sort_events_by_sequence(events: &mut [IbcEventWithHeight]) { @@ -2814,7 +2855,7 @@ pub async fn fetch_compat_mode( } }?; - Ok(compat_mode.into()) + Ok(compat_mode) } #[cfg(test)] diff --git a/crates/relayer/src/chain/cosmos/encode.rs b/crates/relayer/src/chain/cosmos/encode.rs index 447785f97e..a64ebd51e8 100644 --- a/crates/relayer/src/chain/cosmos/encode.rs +++ b/crates/relayer/src/chain/cosmos/encode.rs @@ -185,6 +185,7 @@ pub fn encode_to_bech32(address: &str, account_prefix: &str) -> Result Result<(AuthInfo, Vec), Error> { + #[allow(deprecated)] let auth_info = AuthInfo { signer_infos: vec![signer_info], fee: Some(fee), diff --git a/crates/relayer/src/chain/cosmos/query/account.rs b/crates/relayer/src/chain/cosmos/query/account.rs index 19d9ac5b34..eb8e1dc8b8 100644 --- a/crates/relayer/src/chain/cosmos/query/account.rs +++ b/crates/relayer/src/chain/cosmos/query/account.rs @@ -1,6 +1,6 @@ use http::uri::Uri; use ibc_proto::cosmos::auth::v1beta1::query_client::QueryClient; -use ibc_proto::cosmos::auth::v1beta1::{BaseAccount, EthAccount, QueryAccountRequest}; +use ibc_proto::cosmos::auth::v1beta1::{BaseAccount, QueryAccountRequest}; use prost::Message; use tracing::info; @@ -9,6 +9,19 @@ use crate::config::default::max_grpc_decoding_size; use crate::error::Error; use crate::util::create_grpc_client; +/// EthAccount defines an Ethermint account. +/// TODO: remove when/if a canonical `EthAccount` +/// lands in the next Cosmos SDK release +/// (note +/// only adds the PubKey type) +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EthAccount { + #[prost(message, optional, tag = "1")] + pub base_account: ::core::option::Option, + #[prost(bytes = "vec", tag = "2")] + pub code_hash: ::prost::alloc::vec::Vec, +} + /// Get a `&mut Account` from an `&mut Option` if it is `Some(Account)`. /// Otherwise query for the account information, update the `Option` to `Some`, /// and return the underlying `&mut` reference. diff --git a/crates/relayer/src/chain/cosmos/query/balance.rs b/crates/relayer/src/chain/cosmos/query/balance.rs index 2b8a93e6de..23053fd807 100644 --- a/crates/relayer/src/chain/cosmos/query/balance.rs +++ b/crates/relayer/src/chain/cosmos/query/balance.rs @@ -53,6 +53,7 @@ pub async fn query_all_balances( let request = tonic::Request::new(QueryAllBalancesRequest { address: account_address.to_string(), pagination: None, + resolve_denom: false, // TODO: Correctly handle resolve_denom argument }); let response = client diff --git a/crates/relayer/src/chain/endpoint.rs b/crates/relayer/src/chain/endpoint.rs index 0a12e73341..387ff0b8d6 100644 --- a/crates/relayer/src/chain/endpoint.rs +++ b/crates/relayer/src/chain/endpoint.rs @@ -1,6 +1,7 @@ use alloc::sync::Arc; use ibc_proto::ibc::core::channel::v1::{QueryUpgradeErrorRequest, QueryUpgradeRequest}; +use ibc_relayer_types::applications::ics28_ccv::msgs::{ConsumerChain, ConsumerId}; use ibc_relayer_types::core::ics02_client::height::Height; use tokio::runtime::Runtime as TokioRuntime; @@ -688,7 +689,7 @@ pub trait ChainEndpoint: Sized { request: QueryIncentivizedPacketRequest, ) -> Result; - fn query_consumer_chains(&self) -> Result, Error>; + fn query_consumer_chains(&self) -> Result, Error>; fn query_upgrade( &self, @@ -703,4 +704,6 @@ pub trait ChainEndpoint: Sized { height: Height, include_proof: IncludeProof, ) -> Result<(ErrorReceipt, Option), Error>; + + fn query_ccv_consumer_id(&self, client_id: ClientId) -> Result; } diff --git a/crates/relayer/src/chain/handle.rs b/crates/relayer/src/chain/handle.rs index 2137821e61..b4aae48916 100644 --- a/crates/relayer/src/chain/handle.rs +++ b/crates/relayer/src/chain/handle.rs @@ -8,6 +8,7 @@ use ibc_proto::ibc::apps::fee::v1::{ QueryIncentivizedPacketRequest, QueryIncentivizedPacketResponse, }; use ibc_proto::ibc::core::channel::v1::{QueryUpgradeErrorRequest, QueryUpgradeRequest}; +use ibc_relayer_types::applications::ics28_ccv::msgs::{ConsumerChain, ConsumerId}; use ibc_relayer_types::{ applications::ics31_icq::response::CrossChainQueryResponse, core::{ @@ -371,7 +372,7 @@ pub enum ChainRequest { }, QueryConsumerChains { - reply_to: ReplyTo>, + reply_to: ReplyTo>, }, QueryUpgrade { @@ -387,6 +388,11 @@ pub enum ChainRequest { include_proof: IncludeProof, reply_to: ReplyTo<(ErrorReceipt, Option)>, }, + + QueryConsumerId { + client_id: ClientId, + reply_to: ReplyTo, + }, } pub trait ChainHandle: Clone + Display + Send + Sync + Debug + 'static { @@ -699,7 +705,7 @@ pub trait ChainHandle: Clone + Display + Send + Sync + Debug + 'static { request: QueryIncentivizedPacketRequest, ) -> Result; - fn query_consumer_chains(&self) -> Result, Error>; + fn query_consumer_chains(&self) -> Result, Error>; fn query_upgrade( &self, @@ -714,4 +720,6 @@ pub trait ChainHandle: Clone + Display + Send + Sync + Debug + 'static { height: Height, include_proof: IncludeProof, ) -> Result<(ErrorReceipt, Option), Error>; + + fn query_ccv_consumer_id(&self, client_id: &ClientId) -> Result; } diff --git a/crates/relayer/src/chain/handle/base.rs b/crates/relayer/src/chain/handle/base.rs index 0898269281..64d7d7d30e 100644 --- a/crates/relayer/src/chain/handle/base.rs +++ b/crates/relayer/src/chain/handle/base.rs @@ -8,20 +8,23 @@ use ibc_proto::ibc::{ core::channel::v1::{QueryUpgradeErrorRequest, QueryUpgradeRequest}, }; use ibc_relayer_types::{ - applications::ics31_icq::response::CrossChainQueryResponse, + applications::{ + ics28_ccv::msgs::{ConsumerChain, ConsumerId}, + ics31_icq::response::CrossChainQueryResponse, + }, core::{ ics02_client::{events::UpdateClient, header::AnyHeader}, - ics03_connection::connection::{ConnectionEnd, IdentifiedConnectionEnd}, - ics03_connection::version::Version, - ics04_channel::channel::{ChannelEnd, IdentifiedChannelEnd}, + ics03_connection::{ + connection::{ConnectionEnd, IdentifiedConnectionEnd}, + version::Version, + }, ics04_channel::{ + channel::{ChannelEnd, IdentifiedChannelEnd}, packet::{PacketMsgType, Sequence}, upgrade::{ErrorReceipt, Upgrade}, }, ics23_commitment::{commitment::CommitmentPrefix, merkle::MerkleProof}, - ics24_host::identifier::ChainId, - ics24_host::identifier::ChannelId, - ics24_host::identifier::{ClientId, ConnectionId, PortId}, + ics24_host::identifier::{ChainId, ChannelId, ClientId, ConnectionId, PortId}, }, proofs::Proofs, signer::Signer, @@ -522,7 +525,7 @@ impl ChainHandle for BaseChainHandle { self.send(|reply_to| ChainRequest::QueryIncentivizedPacket { request, reply_to }) } - fn query_consumer_chains(&self) -> Result, Error> { + fn query_consumer_chains(&self) -> Result, Error> { self.send(|reply_to| ChainRequest::QueryConsumerChains { reply_to }) } @@ -553,4 +556,11 @@ impl ChainHandle for BaseChainHandle { reply_to, }) } + + fn query_ccv_consumer_id(&self, client_id: &ClientId) -> Result { + self.send(|reply_to| ChainRequest::QueryConsumerId { + client_id: client_id.clone(), + reply_to, + }) + } } diff --git a/crates/relayer/src/chain/handle/cache.rs b/crates/relayer/src/chain/handle/cache.rs index 6a1731f903..403fee0bb2 100644 --- a/crates/relayer/src/chain/handle/cache.rs +++ b/crates/relayer/src/chain/handle/cache.rs @@ -1,20 +1,22 @@ use core::fmt::{Display, Error as FmtError, Formatter}; use crossbeam_channel as channel; -use ibc_relayer_types::core::ics02_client::header::AnyHeader; -use ibc_relayer_types::core::ics04_channel::upgrade::ErrorReceipt; +use ibc_relayer_types::applications::ics28_ccv::msgs::ConsumerId; use tracing::Span; use ibc_proto::ibc::apps::fee::v1::QueryIncentivizedPacketRequest; use ibc_proto::ibc::apps::fee::v1::QueryIncentivizedPacketResponse; use ibc_proto::ibc::core::channel::v1::{QueryUpgradeErrorRequest, QueryUpgradeRequest}; +use ibc_relayer_types::applications::ics28_ccv::msgs::ConsumerChain; use ibc_relayer_types::applications::ics31_icq::response::CrossChainQueryResponse; use ibc_relayer_types::core::ics02_client::events::UpdateClient; +use ibc_relayer_types::core::ics02_client::header::AnyHeader; use ibc_relayer_types::core::ics03_connection::connection::ConnectionEnd; use ibc_relayer_types::core::ics03_connection::connection::IdentifiedConnectionEnd; use ibc_relayer_types::core::ics03_connection::version::Version; use ibc_relayer_types::core::ics04_channel::channel::ChannelEnd; use ibc_relayer_types::core::ics04_channel::channel::IdentifiedChannelEnd; use ibc_relayer_types::core::ics04_channel::packet::{PacketMsgType, Sequence}; +use ibc_relayer_types::core::ics04_channel::upgrade::ErrorReceipt; use ibc_relayer_types::core::ics04_channel::upgrade::Upgrade; use ibc_relayer_types::core::ics23_commitment::commitment::CommitmentPrefix; use ibc_relayer_types::core::ics23_commitment::merkle::MerkleProof; @@ -515,7 +517,7 @@ impl ChainHandle for CachingChainHandle { self.inner.query_incentivized_packet(request) } - fn query_consumer_chains(&self) -> Result, Error> { + fn query_consumer_chains(&self) -> Result, Error> { self.inner.query_consumer_chains() } @@ -537,4 +539,8 @@ impl ChainHandle for CachingChainHandle { self.inner .query_upgrade_error(request, height, include_proof) } + + fn query_ccv_consumer_id(&self, client_id: &ClientId) -> Result { + self.inner.query_ccv_consumer_id(client_id) + } } diff --git a/crates/relayer/src/chain/handle/counting.rs b/crates/relayer/src/chain/handle/counting.rs index 1096e1b0bf..4fe7685a9e 100644 --- a/crates/relayer/src/chain/handle/counting.rs +++ b/crates/relayer/src/chain/handle/counting.rs @@ -3,13 +3,13 @@ use std::collections::HashMap; use std::sync::{Arc, RwLock, RwLockReadGuard}; use crossbeam_channel as channel; -use ibc_proto::ibc::core::channel::v1::{QueryUpgradeErrorRequest, QueryUpgradeRequest}; -use ibc_relayer_types::core::ics04_channel::upgrade::{ErrorReceipt, Upgrade}; use tracing::{debug, Span}; use ibc_proto::ibc::apps::fee::v1::{ QueryIncentivizedPacketRequest, QueryIncentivizedPacketResponse, }; +use ibc_proto::ibc::core::channel::v1::{QueryUpgradeErrorRequest, QueryUpgradeRequest}; +use ibc_relayer_types::applications::ics28_ccv::msgs::{ConsumerChain, ConsumerId}; use ibc_relayer_types::applications::ics31_icq::response::CrossChainQueryResponse; use ibc_relayer_types::core::ics02_client::events::UpdateClient; use ibc_relayer_types::core::ics02_client::header::AnyHeader; @@ -19,6 +19,7 @@ use ibc_relayer_types::core::ics03_connection::version::Version; use ibc_relayer_types::core::ics04_channel::channel::ChannelEnd; use ibc_relayer_types::core::ics04_channel::channel::IdentifiedChannelEnd; use ibc_relayer_types::core::ics04_channel::packet::{PacketMsgType, Sequence}; +use ibc_relayer_types::core::ics04_channel::upgrade::{ErrorReceipt, Upgrade}; use ibc_relayer_types::core::ics23_commitment::commitment::CommitmentPrefix; use ibc_relayer_types::core::ics23_commitment::merkle::MerkleProof; use ibc_relayer_types::core::ics24_host::identifier::{ @@ -507,7 +508,7 @@ impl ChainHandle for CountingChainHandle { self.inner.query_incentivized_packet(request) } - fn query_consumer_chains(&self) -> Result, Error> { + fn query_consumer_chains(&self) -> Result, Error> { self.inc_metric("query_consumer_chains"); self.inner.query_consumer_chains() } @@ -532,4 +533,9 @@ impl ChainHandle for CountingChainHandle { self.inner .query_upgrade_error(request, height, include_proof) } + + fn query_ccv_consumer_id(&self, client_id: &ClientId) -> Result { + self.inc_metric("query_ccv_consumer_id"); + self.inner.query_ccv_consumer_id(client_id) + } } diff --git a/crates/relayer/src/chain/runtime.rs b/crates/relayer/src/chain/runtime.rs index 1422f386b3..0aff1cb248 100644 --- a/crates/relayer/src/chain/runtime.rs +++ b/crates/relayer/src/chain/runtime.rs @@ -10,10 +10,12 @@ use ibc_proto::ibc::{ core::channel::v1::{QueryUpgradeErrorRequest, QueryUpgradeRequest}, }; use ibc_relayer_types::{ - applications::ics31_icq::response::CrossChainQueryResponse, + applications::{ + ics28_ccv::msgs::{ConsumerChain, ConsumerId}, + ics31_icq::response::CrossChainQueryResponse, + }, core::{ - ics02_client::events::UpdateClient, - ics02_client::header::AnyHeader, + ics02_client::{events::UpdateClient, header::AnyHeader}, ics03_connection::{ connection::{ConnectionEnd, IdentifiedConnectionEnd}, version::Version, @@ -24,7 +26,7 @@ use ibc_relayer_types::{ upgrade::{ErrorReceipt, Upgrade}, }, ics23_commitment::{commitment::CommitmentPrefix, merkle::MerkleProof}, - ics24_host::identifier::{ChainId, ChannelId, ClientId, ConnectionId, PortId}, + ics24_host::identifier::{ChannelId, ClientId, ConnectionId, PortId}, }, proofs::Proofs, signer::Signer, @@ -363,6 +365,10 @@ where ChainRequest::QueryUpgradeError { request, height, include_proof, reply_to } => { self.query_upgrade_error(request, height, include_proof, reply_to)? }, + + ChainRequest::QueryConsumerId { client_id, reply_to } => { + self.query_ccv_consumer_id(client_id, reply_to)? + }, } }, } @@ -865,10 +871,7 @@ where Ok(()) } - fn query_consumer_chains( - &self, - reply_to: ReplyTo>, - ) -> Result<(), Error> { + fn query_consumer_chains(&self, reply_to: ReplyTo>) -> Result<(), Error> { let result = self.chain.query_consumer_chains(); reply_to.send(result).map_err(Error::send)?; @@ -902,4 +905,15 @@ where Ok(()) } + + fn query_ccv_consumer_id( + &self, + client_id: ClientId, + reply_to: ReplyTo, + ) -> Result<(), Error> { + let result = self.chain.query_ccv_consumer_id(client_id); + reply_to.send(result).map_err(Error::send)?; + + Ok(()) + } } diff --git a/crates/relayer/src/config/compat_mode.rs b/crates/relayer/src/config/compat_mode.rs index cb2a035855..795081ea6f 100644 --- a/crates/relayer/src/config/compat_mode.rs +++ b/crates/relayer/src/config/compat_mode.rs @@ -1,95 +1 @@ -use core::fmt::{Display, Error as FmtError, Formatter}; -use core::str::FromStr; -use serde::Deserialize; -use serde::Deserializer; -use serde::Serialize; -use serde::Serializer; - -use tendermint_rpc::client::CompatMode as TmCompatMode; - -use crate::config::Error; - -/// CometBFT RPC compatibility mode -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum CompatMode { - /// Use version 0.34 of the protocol. - V0_34, - /// Use version 0.37+ of the protocol. - V0_37, -} - -impl Display for CompatMode { - fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { - match self { - Self::V0_34 => write!(f, "v0.34"), - Self::V0_37 => write!(f, "v0.37"), - } - } -} - -impl FromStr for CompatMode { - type Err = Error; - - fn from_str(s: &str) -> Result { - const VALID_COMPAT_MODES: &str = "0.34, 0.37, 0.38"; - - // Trim leading 'v', if present - match s.trim_start_matches('v') { - "0.34" => Ok(CompatMode::V0_34), - "0.37" => Ok(CompatMode::V0_37), - "0.38" => Ok(CompatMode::V0_37), // v0.38 is compatible with v0.37 - _ => Err(Error::invalid_compat_mode( - s.to_string(), - VALID_COMPAT_MODES, - )), - } - } -} - -impl<'de> Deserialize<'de> for CompatMode { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - use serde::de; - - let s = String::deserialize(deserializer)?; - FromStr::from_str(&s).map_err(de::Error::custom) - } -} - -impl Serialize for CompatMode { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - self.to_string().serialize(serializer) - } -} - -impl From for CompatMode { - fn from(value: TmCompatMode) -> Self { - match value { - TmCompatMode::V0_34 => Self::V0_34, - TmCompatMode::V0_37 => Self::V0_37, - } - } -} - -impl From for TmCompatMode { - fn from(value: CompatMode) -> Self { - match value { - CompatMode::V0_34 => Self::V0_34, - CompatMode::V0_37 => Self::V0_37, - } - } -} - -impl CompatMode { - pub fn equal_to_tm_compat_mode(&self, tm_compat_mode: TmCompatMode) -> bool { - match self { - Self::V0_34 => tm_compat_mode == TmCompatMode::V0_34, - Self::V0_37 => tm_compat_mode == TmCompatMode::V0_37, - } - } -} +pub use tendermint_rpc::client::CompatMode; diff --git a/crates/relayer/src/error.rs b/crates/relayer/src/error.rs index 6d46f90fe0..c8ac6d656d 100644 --- a/crates/relayer/src/error.rs +++ b/crates/relayer/src/error.rs @@ -30,6 +30,7 @@ use ibc_relayer_types::clients::ics07_tendermint::error as tendermint_error; use ibc_relayer_types::core::ics02_client::{client_type::ClientType, error as client_error}; use ibc_relayer_types::core::ics03_connection::error as connection_error; use ibc_relayer_types::core::ics23_commitment::error as commitment_error; +use ibc_relayer_types::core::ics24_host::error::ValidationError; use ibc_relayer_types::core::ics24_host::identifier::{ChainId, ChannelId, ConnectionId}; use ibc_relayer_types::proofs::ProofError; @@ -606,6 +607,10 @@ define_error! { [ TraceError ] |_| { "HTTP response body error" }, + InvalidHttpHost + { endpoint: String } + |e| { format!("HTTP host is invalid for the endpoint `{}`", e.endpoint) }, + JsonDeserialize [ TraceError ] |_| { "JSON deserialization error" }, @@ -633,6 +638,10 @@ define_error! { InvalidChannelString { channel: String } |e| { format!("invalid channel string {}", e.channel) }, + + Ics24HostValidationError + [ ValidationError ] + |_| { "ICS24 host validation error" }, } } diff --git a/crates/relayer/src/foreign_client.rs b/crates/relayer/src/foreign_client.rs index c05a5739f4..1120ee2dda 100644 --- a/crates/relayer/src/foreign_client.rs +++ b/crates/relayer/src/foreign_client.rs @@ -9,6 +9,7 @@ use std::thread; use std::time::Instant; use ibc_proto::google::protobuf::Any; +use ibc_relayer_types::applications::ics28_ccv::msgs::ConsumerId; use itertools::Itertools; use tracing::{debug, error, info, instrument, trace, warn}; @@ -365,7 +366,7 @@ pub enum ConsensusStateTrusted { NotTrusted { elapsed: Duration, network_timestamp: Timestamp, - consensus_state_timestmap: Timestamp, + consensus_state_timestamp: Timestamp, }, Trusted { elapsed: Duration, @@ -767,12 +768,12 @@ impl ForeignClient { error!( latest_height = %client_state.latest_height(), - network_timestmap = %network_timestamp, - consensus_state_timestamp = %consensus_state_timestmap, + network_timestamp = %network_timestamp, + consensus_state_timestamp = %consensus_state_timestamp, elapsed = ?elapsed, "client state is not valid: latest height is outside of trusting period!", ); @@ -829,7 +830,7 @@ impl ForeignClient ForeignClient ForeignClient { + msgs.push( + MsgSubmitIcsConsumerMisbehaviour { + submitter: signer.clone(), + misbehaviour: tm_misbehaviour, + consumer_id, + } + .to_any(), + ); } - .to_any(), - ); + Err(e) => { + error!( + "cannot build CCV misbehaviour evidence: failed to fetch CCV consumer id for client {}: {}", + self.id, e + ); + } + } } msgs.push( @@ -1931,3 +1943,21 @@ pub fn extract_client_id(event: &IbcEvent) -> Result<&ClientId, ForeignClientErr )), } } + +pub fn fetch_ccv_consumer_id( + provider: &impl ChainHandle, + client_id: &ClientId, +) -> Result { + let consumer_id = provider.query_ccv_consumer_id(client_id).map_err(|e| { + ForeignClientError::misbehaviour( + format!( + "failed to query CCV consumer id corresponding to client {} from provider {}", + client_id, + provider.id() + ), + e, + ) + })?; + + Ok(consumer_id) +} diff --git a/crates/relayer/src/link/relay_path.rs b/crates/relayer/src/link/relay_path.rs index 8038f03995..ff16acd086 100644 --- a/crates/relayer/src/link/relay_path.rs +++ b/crates/relayer/src/link/relay_path.rs @@ -557,7 +557,7 @@ impl RelayPath { let dst_latest_info = self .dst_chain() .query_application_status() - .map_err(|e| LinkError::query(self.src_chain().id(), e))?; + .map_err(|e| LinkError::query(self.dst_chain().id(), e))?; let dst_latest_height = dst_latest_info.height; @@ -1672,7 +1672,7 @@ impl RelayPath { let dst_status = self .dst_chain() .query_application_status() - .map_err(|e| LinkError::query(self.src_chain().id(), e))?; + .map_err(|e| LinkError::query(self.dst_chain().id(), e))?; let dst_current_height = dst_status.height; diff --git a/crates/relayer/src/upgrade_chain.rs b/crates/relayer/src/upgrade_chain.rs index 2889d7ab62..318f35130b 100644 --- a/crates/relayer/src/upgrade_chain.rs +++ b/crates/relayer/src/upgrade_chain.rs @@ -296,6 +296,7 @@ fn build_upgrade_proposal( metadata: "".to_string(), title: "proposal 0".to_string(), summary: "upgrade the chain software and unbonding period".to_string(), + expedited: false, }; let mut buf_msg = Vec::new(); diff --git a/crates/relayer/src/util.rs b/crates/relayer/src/util.rs index 01304856d2..9799e6cc4e 100644 --- a/crates/relayer/src/util.rs +++ b/crates/relayer/src/util.rs @@ -21,10 +21,25 @@ pub async fn create_grpc_client( grpc_addr: &tonic::transport::Uri, client_constructor: impl FnOnce(tonic::transport::Channel) -> T, ) -> Result { - let tls_config = tonic::transport::ClientTlsConfig::new().with_native_roots(); - let channel = tonic::transport::Channel::builder(grpc_addr.clone()) - .tls_config(tls_config) - .map_err(crate::error::Error::grpc_transport)? + let builder = tonic::transport::Channel::builder(grpc_addr.clone()); + + // Don't configures TLS for the endpoint if using IPv6 + let builder = if grpc_addr.scheme() == Some(&http::uri::Scheme::HTTPS) { + let domain = grpc_addr + .host() + .map(|d| d.replace(['[', ']'], "")) + .ok_or_else(|| crate::error::Error::invalid_http_host(grpc_addr.to_string()))?; + let tls_config = tonic::transport::ClientTlsConfig::new() + .with_native_roots() + .domain_name(domain); + builder + .tls_config(tls_config) + .map_err(crate::error::Error::grpc_transport)? + } else { + builder + }; + + let channel = builder .connect() .await .map_err(crate::error::Error::grpc_transport)?; diff --git a/crates/relayer/src/util/compat_mode.rs b/crates/relayer/src/util/compat_mode.rs index 716534bb95..c637d5a247 100644 --- a/crates/relayer/src/util/compat_mode.rs +++ b/crates/relayer/src/util/compat_mode.rs @@ -1,7 +1,6 @@ use tracing::warn; use tendermint::Version; -use tendermint_rpc::client::CompatMode as TmCompatMode; use crate::chain::cosmos::version::ConsensusVersion; use crate::config::compat_mode::CompatMode; @@ -11,21 +10,21 @@ pub fn compat_mode_from_node_version( configured_version: &Option, version: Version, ) -> Result { - let queried_version = TmCompatMode::from_version(version); + let queried_version = CompatMode::from_version(version); // This will prioritize the use of the CompatMode specified in Hermes configuration file match (configured_version, queried_version) { - (Some(configured), Ok(queried)) if !configured.equal_to_tm_compat_mode(queried) => { + (Some(configured), Ok(queried)) if configured != &queried => { warn!( "potential `compat_mode` misconfiguration! Configured version '{configured}' does not match chain version '{queried}'. \ Hermes will use the configured `compat_mode` version '{configured}'. \ If this configuration is done on purpose this message can be ignored.", ); - Ok(configured.clone()) + Ok(*configured) } - (Some(configured), _) => Ok(configured.clone()), - (_, Ok(queried)) => Ok(queried.into()), + (Some(configured), _) => Ok(*configured), + (_, Ok(queried)) => Ok(queried), (_, Err(e)) => Err(Error::invalid_compat_mode(e)), } } @@ -50,7 +49,7 @@ pub fn compat_mode_from_version_specs( If this configuration is done on purpose this message can be ignored." ); - Ok(configured.clone()) + Ok(*configured) } (Some(configured), None) => { warn!( @@ -58,7 +57,7 @@ pub fn compat_mode_from_version_specs( and will use the configured `compat_mode` version `{configured}`." ); - Ok(configured.clone()) + Ok(*configured) } (None, Some(queried)) => Ok(queried), (None, None) => { @@ -77,7 +76,7 @@ fn compat_mode_from_semver(v: semver::Version) -> Option { match (v.major, v.minor) { (0, 34) => Some(CompatMode::V0_34), (0, 37) => Some(CompatMode::V0_37), - (0, 38) => Some(CompatMode::V0_37), + (0, 38) => Some(CompatMode::V0_38), _ => None, } } diff --git a/crates/relayer/src/worker/cross_chain_query.rs b/crates/relayer/src/worker/cross_chain_query.rs index 811bb14b36..29f1f69db6 100644 --- a/crates/relayer/src/worker/cross_chain_query.rs +++ b/crates/relayer/src/worker/cross_chain_query.rs @@ -8,6 +8,7 @@ use crate::error::Error; use crate::event::IbcEventWithHeight; use crate::foreign_client::ForeignClient; use crate::object::CrossChainQuery; +use crate::telemetry; use crate::util::task::{spawn_background_task, Next, TaskError, TaskHandle}; use crate::worker::WorkerCmd; @@ -74,6 +75,12 @@ fn handle_cross_chain_query( // Handle of queried chain has to query data from it's RPC info!("request: {}", cross_chain_query.short_name()); + telemetry!( + cross_chain_queries, + &cross_chain_query.src_chain_id, + &cross_chain_query.dst_chain_id, + queries.len() + ); let response = chain_b_handle.cross_chain_query(queries); if let Ok(cross_chain_query_responses) = response { // Run only when cross chain query response is not empty @@ -87,7 +94,7 @@ fn handle_cross_chain_query( }, IncludeProof::No, ) - .map_err(|_| TaskError::Fatal(RunError::query()))? + .map_err(|e| TaskError::Fatal(RunError::relayer(e)))? .0; // Retrieve client based on client id @@ -96,19 +103,21 @@ fn handle_cross_chain_query( chain_a_handle.clone(), connection_end.client_id(), ) - .map_err(|_| TaskError::Fatal(RunError::query()))?; + .map_err(|e| TaskError::Fatal(RunError::foreign_client(e)))?; let target_height = Height::new( chain_b_handle.id().version(), cross_chain_query_responses.first().unwrap().height as u64, ) - .map_err(|_| TaskError::Fatal(RunError::query()))? + .map_err(|e| TaskError::Fatal(RunError::ics02(e)))? .increment(); // Push update client msg let mut chain_a_msgs = client_a .wait_and_build_update_client(target_height) - .map_err(|_| TaskError::Fatal(RunError::query()))?; + .map_err(|e| TaskError::Fatal(RunError::foreign_client(e)))?; + + let num_cross_chain_query_responses = cross_chain_query_responses.len(); for response in cross_chain_query_responses { info!("response arrived: query_id: {}", response.query_id); @@ -118,18 +127,40 @@ fn handle_cross_chain_query( .try_to_any( chain_a_handle .get_signer() - .map_err(|_| TaskError::Fatal(RunError::query()))?, + .map_err(|e| TaskError::Fatal(RunError::relayer(e)))?, ) - .map_err(|_| TaskError::Fatal(RunError::query()))?, + .map_err(|e| TaskError::Fatal(RunError::ics31(e)))?, ); } - chain_a_handle + let ccq_responses = chain_a_handle .send_messages_and_wait_check_tx(TrackedMsgs::new_uuid( chain_a_msgs, Uuid::new_v4(), )) - .map_err(|_| TaskError::Ignore(RunError::query()))?; + .map_err(|e| { + // Since all the CCQs failed, generate a failure code for the telemetry + let failed_codes = + vec![tendermint::abci::Code::from(1); num_cross_chain_query_responses]; + telemetry!( + cross_chain_query_responses, + &cross_chain_query.dst_chain_id, + &cross_chain_query.src_chain_id, + failed_codes + ); + + TaskError::Ignore(RunError::relayer(e)) + })?; + + telemetry!( + cross_chain_query_responses, + &cross_chain_query.dst_chain_id, + &cross_chain_query.src_chain_id, + ccq_responses + .iter() + .map(|ccq_response| ccq_response.code) + .collect() + ); } } } diff --git a/crates/relayer/src/worker/error.rs b/crates/relayer/src/worker/error.rs index a694678539..e95b2290ed 100644 --- a/crates/relayer/src/worker/error.rs +++ b/crates/relayer/src/worker/error.rs @@ -1,9 +1,12 @@ use crossbeam_channel::RecvError; use flex_error::{define_error, DisplayOnly}; +use ibc_relayer_types::applications::ics31_icq::error::Error as Ics31Error; use ibc_relayer_types::core::ics02_client::error::Error as Ics02Error; use crate::channel::ChannelError; use crate::connection::ConnectionError; +use crate::error::Error as RelayerError; +use crate::foreign_client::ForeignClientError; use crate::link::error::LinkError; define_error! { @@ -12,6 +15,10 @@ define_error! { [ Ics02Error ] | _ | { "client error" }, + Ics31 + [ Ics31Error ] + | _ | { "cross chain query error" }, + Connection [ ConnectionError ] | _ | { "connection error" }, @@ -20,10 +27,18 @@ define_error! { [ ChannelError ] | _ | { "channel error" }, + ForeignClient + [ ForeignClientError ] + | _ | { "foreign client error" }, + Link [ LinkError ] | _ | { "link error" }, + Relayer + [ RelayerError ] + | _ | { "relayer error" }, + Retry { retries: retry::Error } | e | { format_args!("worker failed after {} retries", e.retries) }, @@ -31,8 +46,5 @@ define_error! { Recv [ DisplayOnly ] | _ | { "error receiving from channel: sender end has been closed" }, - - Query - | _ | { "error occurred during querying" } } } diff --git a/crates/telemetry/src/state.rs b/crates/telemetry/src/state.rs index e5a9c9cf96..136b414077 100644 --- a/crates/telemetry/src/state.rs +++ b/crates/telemetry/src/state.rs @@ -215,6 +215,15 @@ pub struct TelemetryState { /// Number of ICS-20 packets filtered because the memo and/or the receiver fields were exceeding the configured limits filtered_packets: Counter, + + /// Observed ICS31 CrossChainQueries + cross_chain_queries: Counter, + + /// Observed ICS31 CrossChainQuery successful Responses + cross_chain_query_responses: Counter, + + /// Observed ICS31 CrossChainQuery error Responses + cross_chain_query_error_responses: Counter, } impl TelemetryState { @@ -423,6 +432,21 @@ impl TelemetryState { .u64_counter("filtered_packets") .with_description("Number of ICS-20 packets filtered because the memo and/or the receiver fields were exceeding the configured limits") .init(), + + cross_chain_queries: meter + .u64_counter("cross_chain_queries") + .with_description("Number of ICS-31 queries received") + .init(), + + cross_chain_query_responses: meter + .u64_counter("cross_chain_query_responses") + .with_description("Number of ICS-31 successful query responses") + .init(), + + cross_chain_query_error_responses: meter + .u64_counter("cross_chain_query_error_responses") + .with_description("Number of ICS-31 error query responses") + .init(), } } @@ -1236,6 +1260,41 @@ impl TelemetryState { self.filtered_packets.add(&cx, count, labels); } } + + pub fn cross_chain_queries(&self, src_chain: &ChainId, dst_chain: &ChainId, count: usize) { + let cx = Context::current(); + + if count > 0 { + let labels = &[ + KeyValue::new("src_chain", src_chain.to_string()), + KeyValue::new("dst_chain", dst_chain.to_string()), + ]; + + self.cross_chain_queries.add(&cx, count as u64, labels); + } + } + + pub fn cross_chain_query_responses( + &self, + src_chain: &ChainId, + dst_chain: &ChainId, + ccq_responses_codes: Vec, + ) { + let cx = Context::current(); + + let labels = &[ + KeyValue::new("src_chain", src_chain.to_string()), + KeyValue::new("dst_chain", dst_chain.to_string()), + ]; + + for code in ccq_responses_codes.iter() { + if code.is_ok() { + self.cross_chain_query_responses.add(&cx, 1, labels); + } else { + self.cross_chain_query_error_responses.add(&cx, 1, labels); + } + } + } } use std::sync::Arc; diff --git a/flake.lock b/flake.lock index e8cd15d075..5081f49548 100644 --- a/flake.lock +++ b/flake.lock @@ -101,15 +101,16 @@ "cometbft-src": { "flake": false, "locked": { - "narHash": "sha256-G5gchJMn/BFzwYx8/ikPDL5fS/TuFIBF4DKJbkalp/M=", + "lastModified": 1723450629, + "narHash": "sha256-2QO4KeEUX4HHT1AKhEdPplJHjBhalfM11Dn3/urIVig=", "owner": "cometbft", "repo": "cometbft", - "rev": "66a5a9da9f7a3306f382eb9142ccb9c9f7997d3f", + "rev": "e1b4453baf0af6487ad187c7f17dc50517126673", "type": "github" }, "original": { "owner": "cometbft", - "ref": "v0.38.0", + "ref": "v0.38.11", "repo": "cometbft", "type": "github" } @@ -158,6 +159,7 @@ "gaia17-src": "gaia17-src", "gaia18-src": "gaia18-src", "gaia19-src": "gaia19-src", + "gaia20-src": "gaia20-src", "gaia5-src": "gaia5-src", "gaia6-ordered-src": "gaia6-ordered-src", "gaia6-src": "gaia6-src", @@ -226,11 +228,11 @@ "wasmvm_2_1_2-src": "wasmvm_2_1_2-src" }, "locked": { - "lastModified": 1728982270, - "narHash": "sha256-iAzKmzrUm6BD6Vwb6rtbkTy1e/nFTsgo+Y5WNrrY2vc=", + "lastModified": 1729681912, + "narHash": "sha256-Ym4WfC/Iogqft2KOoHEv2FlWlgsxOAA5CZCbGXPf65o=", "owner": "informalsystems", "repo": "cosmos.nix", - "rev": "8b7f6e8d2c2e6455fcc9d86ef098e7f182827637", + "rev": "04a2efd0e01206b5df601b205d7662db79a765f0", "type": "github" }, "original": { @@ -498,11 +500,11 @@ "systems": "systems_6" }, "locked": { - "lastModified": 1726560853, - "narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=", + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", "owner": "numtide", "repo": "flake-utils", - "rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", "type": "github" }, "original": { @@ -674,6 +676,23 @@ "type": "github" } }, + "gaia20-src": { + "flake": false, + "locked": { + "lastModified": 1726853009, + "narHash": "sha256-N7x3k56AtPbIbbJjqKmlEJIytKElALJwj14lZ2pewZg=", + "owner": "cosmos", + "repo": "gaia", + "rev": "2dba9d471ef73b0a99e844bf55a44ddae700ea06", + "type": "github" + }, + "original": { + "owner": "cosmos", + "ref": "v20.0.0", + "repo": "gaia", + "type": "github" + } + }, "gaia5-src": { "flake": false, "locked": { @@ -1132,15 +1151,16 @@ "interchain-security-src": { "flake": false, "locked": { - "narHash": "sha256-adBzn51PKoRsCL9gIzC5Tcqmu7u3GjxTcDj2jpZ/da8=", + "lastModified": 1726849313, + "narHash": "sha256-1WEvV3LoXfGvZC9fXOb8mBLKVGCVBiXZcwUewSPit+8=", "owner": "cosmos", "repo": "interchain-security", - "rev": "03aada4af3243dbf739a12adfacc7b37232df694", + "rev": "1e60637f9d8f3505208282416abfbb87fabc4795", "type": "github" }, "original": { "owner": "cosmos", - "ref": "feat/ics-misbehaviour-handling", + "ref": "v6.1.0", "repo": "interchain-security", "type": "github" } @@ -1445,11 +1465,11 @@ }, "nixpkgs_5": { "locked": { - "lastModified": 1728538411, - "narHash": "sha256-f0SBJz1eZ2yOuKUr5CA9BHULGXVSn6miBuUWdTyhUhU=", + "lastModified": 1731531548, + "narHash": "sha256-sz8/v17enkYmfpgeeuyzniGJU0QQBfmAjlemAUYhfy8=", "owner": "nixos", "repo": "nixpkgs", - "rev": "b69de56fac8c2b6f8fd27f2eca01dcda8e0a4221", + "rev": "24f0d4acd634792badd6470134c387a3b039dace", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 1a9ae4229a..ce0e9b787d 100644 --- a/flake.nix +++ b/flake.nix @@ -2,9 +2,9 @@ description = "Nix development dependencies for ibc-rs"; inputs = { - nixpkgs.url = github:nixos/nixpkgs/nixpkgs-unstable; - flake-utils.url = github:numtide/flake-utils; - cosmos-nix.url = github:informalsystems/cosmos.nix; + nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + cosmos-nix.url = "github:informalsystems/cosmos.nix"; }; outputs = inputs: let @@ -33,6 +33,7 @@ evmos gaia6-ordered gaia18 + gaia20 ibc-go-v2-simapp ibc-go-v3-simapp ibc-go-v4-simapp diff --git a/tools/test-framework/Cargo.toml b/tools/test-framework/Cargo.toml index 34f5a08182..5c71770833 100644 --- a/tools/test-framework/Cargo.toml +++ b/tools/test-framework/Cargo.toml @@ -42,3 +42,5 @@ toml = { workspace = true } tonic = { workspace = true, features = ["tls", "tls-roots"] } tracing = { workspace = true } tracing-subscriber = { workspace = true } + +chrono = "0.4.38" diff --git a/tools/test-framework/src/bootstrap/consumer.rs b/tools/test-framework/src/bootstrap/consumer.rs index 6c85595331..c473ab6c28 100644 --- a/tools/test-framework/src/bootstrap/consumer.rs +++ b/tools/test-framework/src/bootstrap/consumer.rs @@ -8,6 +8,7 @@ use std::time::Duration; use tracing::info; use crate::chain::builder::ChainBuilder; +use crate::chain::cli::provider::validator_opt_in; use crate::chain::config; use crate::chain::ext::bootstrap::ChainBootstrapMethodsExt; use crate::error::Error; @@ -22,6 +23,7 @@ pub fn bootstrap_consumer_node( genesis_modifier: impl FnOnce(&mut serde_json::Value) -> Result<(), Error>, chain_number: usize, provider_chain_driver: &ChainDriver, + provider_fee: &String, ) -> Result { let stake_denom = Denom::base("stake"); @@ -38,7 +40,7 @@ pub fn bootstrap_consumer_node( )))?; let initial_coin = Token::new(denom.clone(), initial_amount); - let chain_driver = builder.new_chain(prefix, false, chain_number)?; + let chain_driver = builder.new_chain("consumer", false, chain_number)?; chain_driver.initialize()?; @@ -53,11 +55,23 @@ pub fn bootstrap_consumer_node( chain_driver.add_genesis_account(&user2.address, &[&initial_stake, &initial_coin])?; // Wait for the consumer chain to be initialized before querying the genesis + thread::sleep(Duration::from_secs(5)); + + validator_opt_in( + provider_chain_driver.chain_id.as_str(), + &provider_chain_driver.command_path, + &provider_chain_driver.home_path, + &provider_chain_driver.rpc_listen_address(), + provider_fee, + prefix, + )?; + + // Wait enough time so that the spawn_time passed thread::sleep(Duration::from_secs(30)); node_a .chain_driver - .query_consumer_genesis(&chain_driver, chain_driver.chain_id.as_str())?; + .query_consumer_genesis(&chain_driver, prefix)?; chain_driver.replace_genesis_state()?; diff --git a/tools/test-framework/src/bootstrap/init.rs b/tools/test-framework/src/bootstrap/init.rs index 795f19787c..07e653771f 100644 --- a/tools/test-framework/src/bootstrap/init.rs +++ b/tools/test-framework/src/bootstrap/init.rs @@ -50,6 +50,11 @@ pub fn init_test() -> Result { let compat_modes = env::var("COMPAT_MODES").ok().map(parse_chain_command_paths); + let ipv6_grpc = env::var("IPV6_GRPC") + .ok() + .map(|val| val == "true") + .unwrap_or(false); + let account_prefixes = parse_chain_command_paths(account_prefix); let native_tokens = parse_chain_command_paths(native_token); @@ -72,6 +77,7 @@ pub fn init_test() -> Result { hang_on_fail, bootstrap_with_random_ids: false, native_tokens, + ipv6_grpc, compat_modes, }) } diff --git a/tools/test-framework/src/bootstrap/single.rs b/tools/test-framework/src/bootstrap/single.rs index 508d0b28ba..df87589b35 100644 --- a/tools/test-framework/src/bootstrap/single.rs +++ b/tools/test-framework/src/bootstrap/single.rs @@ -100,6 +100,7 @@ pub fn bootstrap_single_node( config::set_rpc_port(config, chain_driver.rpc_port)?; config::set_p2p_port(config, chain_driver.p2p_port)?; config::set_pprof_port(config, chain_driver.pprof_port)?; + config::set_block_sync(config, true)?; config::set_timeout_commit(config, Duration::from_secs(1))?; config::set_timeout_propose(config, Duration::from_secs(1))?; config::set_mode(config, "validator")?; @@ -112,7 +113,11 @@ pub fn bootstrap_single_node( let minimum_gas = format!("0{}", native_token); chain_driver.update_chain_config("app.toml", |config| { - config::set_grpc_port(config, chain_driver.grpc_port)?; + if builder.ipv6_grpc { + config::set_grpc_port_ipv6(config, chain_driver.grpc_port)?; + } else { + config::set_grpc_port(config, chain_driver.grpc_port)?; + } config::enable_grpc(config)?; config::disable_grpc_web(config)?; config::disable_api(config)?; diff --git a/tools/test-framework/src/chain/builder.rs b/tools/test-framework/src/chain/builder.rs index ff23be521f..89895dd274 100644 --- a/tools/test-framework/src/chain/builder.rs +++ b/tools/test-framework/src/chain/builder.rs @@ -45,6 +45,8 @@ pub struct ChainBuilder { pub compat_modes: Option>, + pub ipv6_grpc: bool, + pub runtime: Arc, } @@ -58,6 +60,7 @@ impl ChainBuilder { account_prefixes: Vec, native_tokens: Vec, compat_modes: Option>, + ipv6_grpc: bool, runtime: Arc, ) -> Self { Self { @@ -66,6 +69,7 @@ impl ChainBuilder { account_prefixes, native_tokens, compat_modes, + ipv6_grpc, runtime, } } @@ -80,6 +84,7 @@ impl ChainBuilder { config.account_prefixes.clone(), config.native_tokens.clone(), config.compat_modes.clone(), + config.ipv6_grpc, runtime, ) } @@ -146,6 +151,7 @@ impl ChainBuilder { self.runtime.clone(), self.native_tokens[native_token_number].clone(), compat_mode, + self.ipv6_grpc, )?; Ok(driver) diff --git a/tools/test-framework/src/chain/cli/provider.rs b/tools/test-framework/src/chain/cli/provider.rs index 2c5dc6f8cd..76310a2a6d 100644 --- a/tools/test-framework/src/chain/cli/provider.rs +++ b/tools/test-framework/src/chain/cli/provider.rs @@ -1,6 +1,6 @@ use eyre::eyre; use std::collections::HashMap; -use std::str; +use std::{str, thread}; use crate::chain::exec::{simple_exec, ExecOutput}; use crate::error::{handle_generic_error, Error}; @@ -12,7 +12,8 @@ pub fn submit_consumer_chain_proposal( rpc_listen_address: &str, fees: &str, ) -> Result<(), Error> { - let proposal_file = format!("{}/consumer_proposal.json", home_path); + let proposal_file = format!("{}/consumer_proposal_topn.json", home_path); + thread::sleep(std::time::Duration::from_secs(3)); // The submission might fail silently if there is not enough gas let raw_output = simple_exec( @@ -21,8 +22,234 @@ pub fn submit_consumer_chain_proposal( &[ "tx", "gov", - "submit-legacy-proposal", - "consumer-addition", + "submit-proposal", + &proposal_file, + "--chain-id", + chain_id, + "--from", + "validator", + "--home", + home_path, + "--node", + rpc_listen_address, + "--keyring-backend", + "test", + "--gas", + "2000000", + "--fees", + fees, + "--output", + "json", + "--yes", + ], + )?; + + let output: serde_json::Value = + serde_json::from_str(&raw_output.stdout).map_err(handle_generic_error)?; + + let output_code = output.get("code").and_then(|code| code.as_u64()).ok_or_else(|| Error::generic(eyre!("failed to extract 'code' from 'tx gov submit-legacy-proposal consumer-addition' command")))?; + + // Proposal submission might fail due to account sequence error. + // Wait a bit and resubmit if the first submission fails + if output_code != 0 { + thread::sleep(std::time::Duration::from_secs(3)); + simple_exec( + chain_id, + command_path, + &[ + "tx", + "gov", + "submit-proposal", + &proposal_file, + "--chain-id", + chain_id, + "--from", + "validator", + "--home", + home_path, + "--node", + rpc_listen_address, + "--keyring-backend", + "test", + "--gas", + "2000000", + "--fees", + fees, + "--output", + "json", + "--yes", + ], + )?; + } + + Ok(()) +} + +pub fn create_consumer( + chain_id: &str, + command_path: &str, + home_path: &str, + rpc_listen_address: &str, + fees: &str, +) -> Result { + let proposal_file = format!("{}/consumer_proposal.json", home_path); + + // The submission might fail silently if there is not enough gas + let raw_output = simple_exec( + chain_id, + command_path, + &[ + "tx", + "provider", + "create-consumer", + &proposal_file, + "--chain-id", + chain_id, + "--from", + "validator", + "--home", + home_path, + "--node", + rpc_listen_address, + "--keyring-backend", + "test", + "--gas", + "2000000", + "--fees", + fees, + "--output", + "json", + "--yes", + ], + )?; + + let output: serde_json::Value = + serde_json::from_str(&raw_output.stdout).map_err(handle_generic_error)?; + + let hash = output.get("txhash").and_then(|code| code.as_str()).unwrap(); + + thread::sleep(std::time::Duration::from_secs(3)); + + let hash_output = simple_exec( + chain_id, + command_path, + &[ + "query", + "tx", + hash, + "--chain-id", + chain_id, + "--home", + home_path, + "--node", + rpc_listen_address, + "--output", + "json", + ], + )?; + + let hash_output_json: serde_json::Value = + serde_json::from_str(&hash_output.stdout).map_err(handle_generic_error)?; + + let output_code = output.get("code").and_then(|code| code.as_u64()).ok_or_else(|| Error::generic(eyre!("failed to extract 'code' from 'tx gov submit-legacy-proposal consumer-addition' command")))?; + + if output_code != 0 { + let output_logs = output.get("raw_log").and_then(|code| code.as_str()).ok_or_else(|| Error::generic(eyre!("failed to extract 'raw_logs' from 'tx gov submit-legacy-proposal consumer-addition' command")))?; + return Err(Error::generic(eyre!("output code for commande 'tx provider create-consumer' should be 0, but is instead '{output_code}'. Detail: {output_logs}", ))); + } + + let events = hash_output_json + .get("events") + .and_then(|code| code.as_array()) + .ok_or_else(|| { + Error::generic(eyre!( + "failed to extract 'events' from the output of `create-consumer` CLI" + )) + })?; + + let create_consumer_event = events + .iter() + .find(|v| v.get("type").and_then(|v| v.as_str()) == Some("create_consumer")) + .ok_or_else(|| Error::generic(eyre!("failed to extract create_consumer event")))?; + + let attributes = create_consumer_event + .get("attributes") + .and_then(|v| v.as_array()) + .ok_or_else(|| { + Error::generic(eyre!( + "failed to extract attributes from create_consumer event" + )) + })?; + + let consumer_id_attribute = attributes + .iter() + .find(|v| v.get("key").and_then(|v| v.as_str()) == Some("consumer_id")) + .ok_or_else(|| Error::generic(eyre!("failed to extract consumer_id attribute")))?; + + let consumer_id = consumer_id_attribute + .get("value") + .and_then(|v| v.as_str()) + .ok_or_else(|| Error::generic(eyre!("failed to extract consumer_id")))?; + + Ok(consumer_id.to_owned()) +} + +pub fn validator_opt_in( + chain_id: &str, + command_path: &str, + home_path: &str, + rpc_listen_address: &str, + fees: &str, + consumer_id: &str, +) -> Result<(), Error> { + simple_exec( + chain_id, + command_path, + &[ + "tx", + "provider", + "opt-in", + consumer_id, + "--chain-id", + chain_id, + "--from", + "validator", + "--home", + home_path, + "--node", + rpc_listen_address, + "--keyring-backend", + "test", + "--gas", + "2000000", + "--fees", + fees, + "--output", + "json", + "--yes", + ], + )?; + + Ok(()) +} + +pub fn update_consumer( + chain_id: &str, + command_path: &str, + home_path: &str, + rpc_listen_address: &str, + fees: &str, +) -> Result<(), Error> { + let proposal_file = format!("{}/consumer_update_proposal.json", home_path); + + // The submission might fail silently if there is not enough gas + let raw_output = simple_exec( + chain_id, + command_path, + &[ + "tx", + "provider", + "update-consumer", &proposal_file, "--chain-id", chain_id, diff --git a/tools/test-framework/src/chain/config.rs b/tools/test-framework/src/chain/config.rs index 9d98f9702d..4d76a1cf43 100644 --- a/tools/test-framework/src/chain/config.rs +++ b/tools/test-framework/src/chain/config.rs @@ -45,6 +45,17 @@ pub fn set_grpc_port(config: &mut Value, port: u16) -> Result<(), Error> { Ok(()) } +pub fn set_grpc_port_ipv6(config: &mut Value, port: u16) -> Result<(), Error> { + config + .get_mut("grpc") + .ok_or_else(|| eyre!("expect grpc section"))? + .as_table_mut() + .ok_or_else(|| eyre!("expect object"))? + .insert("address".to_string(), format!("[::]:{port}").into()); + + Ok(()) +} + pub fn disable_grpc_web(config: &mut Value) -> Result<(), Error> { if let Some(field) = config.get_mut("grpc-web") { field @@ -92,6 +103,16 @@ pub fn set_pprof_port(config: &mut Value, port: u16) -> Result<(), Error> { Ok(()) } +/// Set the `pprof_laddr` field in the full node config. +pub fn set_block_sync(config: &mut Value, value: bool) -> Result<(), Error> { + config + .as_table_mut() + .ok_or_else(|| eyre!("expect object"))? + .insert("block_sync".to_string(), value.into()); + + Ok(()) +} + pub fn set_mempool_version(config: &mut Value, version: &str) -> Result<(), Error> { config .get_mut("mempool") diff --git a/tools/test-framework/src/chain/driver.rs b/tools/test-framework/src/chain/driver.rs index 85830caf5e..bd4aba6543 100644 --- a/tools/test-framework/src/chain/driver.rs +++ b/tools/test-framework/src/chain/driver.rs @@ -5,11 +5,11 @@ use alloc::sync::Arc; use core::time::Duration; use eyre::eyre; -use ibc_relayer::config::compat_mode::CompatMode; use std::cmp::max; use tokio::runtime::Runtime; use ibc_relayer::chain::cosmos::types::config::TxConfig; +use ibc_relayer::config::compat_mode::CompatMode; use ibc_relayer_types::applications::transfer::amount::Amount; use ibc_relayer_types::core::ics24_host::identifier::ChainId; @@ -98,6 +98,8 @@ pub struct ChainDriver { pub runtime: Arc, pub compat_mode: Option, + + pub ipv6_grpc: bool, } impl ExportEnv for ChainDriver { @@ -125,12 +127,18 @@ impl ChainDriver { runtime: Arc, native_token: String, compat_mode: Option, + ipv6_grpc: bool, ) -> Result { + let grpc_address = if ipv6_grpc { + format!("http://[::1]:{grpc_port}") + } else { + format!("http://localhost:{grpc_port}") + }; let tx_config = new_tx_config_for_test( chain_id.clone(), chain_type.clone(), format!("http://localhost:{rpc_port}"), - format!("http://localhost:{grpc_port}"), + grpc_address, chain_type.address_type(), native_token, )?; @@ -149,6 +157,7 @@ impl ChainDriver { tx_config, runtime, compat_mode, + ipv6_grpc, }) } @@ -164,7 +173,11 @@ impl ChainDriver { /// Returns the full URL for the GRPC address. pub fn grpc_address(&self) -> String { - format!("http://localhost:{}", self.grpc_port) + if self.ipv6_grpc { + format!("http://[::1]:{}", self.grpc_port) + } else { + format!("http://127.0.0.1:{}", self.grpc_port) + } } /** @@ -186,7 +199,11 @@ impl ChainDriver { as it requires no scheme to be specified. */ pub fn grpc_listen_address(&self) -> String { - format!("localhost:{}", self.grpc_port) + if self.ipv6_grpc { + format!("[::1]:{}", self.grpc_port) + } else { + format!("127.0.0.1:{}", self.grpc_port) + } } /** diff --git a/tools/test-framework/src/chain/ext/bootstrap.rs b/tools/test-framework/src/chain/ext/bootstrap.rs index 4fef50083d..07103fcd05 100644 --- a/tools/test-framework/src/chain/ext/bootstrap.rs +++ b/tools/test-framework/src/chain/ext/bootstrap.rs @@ -1,3 +1,6 @@ +use chrono::DateTime; +use chrono::Duration as ChronoDuration; +use chrono::Utc; use core::str::FromStr; use eyre::eyre; use hdpath::StandardHDPath; @@ -14,10 +17,12 @@ use crate::chain::cli::bootstrap::{ add_genesis_account, add_genesis_validator, add_wallet, collect_gen_txs, initialize, start_chain, }; +use crate::chain::cli::provider::submit_consumer_chain_proposal; use crate::chain::cli::provider::{ - copy_validator_key_pair, query_consumer_genesis, query_gov_proposal, replace_genesis_state, - submit_consumer_chain_proposal, + copy_validator_key_pair, create_consumer, query_consumer_genesis, query_gov_proposal, + replace_genesis_state, update_consumer, validator_opt_in, }; +use crate::chain::cli::query::query_auth_module; use crate::chain::driver::ChainDriver; use crate::chain::exec::simple_exec; use crate::error::{handle_generic_error, Error}; @@ -107,7 +112,21 @@ pub trait ChainBootstrapMethodsExt { &self, consumer_chain_id: &str, fees: &str, - spawn_time: &str, + ) -> Result<(), Error>; + + fn create_permisionless_consumer( + &self, + consumer_chain_id: &str, + fees: &str, + ) -> Result; + + fn validator_opt_in(&self, consumer_chain_id: &str, fees: &str) -> Result<(), Error>; + + fn update_consumer( + &self, + consumer_chain_id: &str, + fees: &str, + validator: &str, ) -> Result<(), Error>; /** @@ -278,8 +297,132 @@ impl ChainBootstrapMethodsExt for ChainDriver { &self, consumer_chain_id: &str, fees: &str, - _spawn_time: &str, ) -> Result<(), Error> { + let raw_proposal = r#" + { + "chain_id": "{consumer_chain_id}", + "initialization_parameters": { + "initial_height": { + "revision_number": 0, + "revision_height": 1 + }, + "genesis_hash": "Z2VuX2hhc2g=", + "binary_hash": "YmluX2hhc2g=", + "spawn_time": "{spawn_time}", + "unbonding_period": 100000000000, + "ccv_timeout_period": 100000000000, + "transfer_timeout_period": 100000000000, + "consumer_redistribution_fraction": "0.75", + "blocks_per_distribution_transmission": 10, + "historical_entries": 10000, + "distribution_transmission_channel": "" + }, + "power_shaping_parameters": { + "top_N": 100, + "validators_power_cap": 0, + "validator_set_cap": 0, + "allowlist": [], + "denylist": [], + "min_stake": 0, + "allow_inactive_vals": false + }, + "metadata": "ipfs://CID", + "deposit": "10000000stake", + "title": "\"update consumer 0 to top N\"", + "summary": "\"update consumer 0 to top N\"", + "expedited": false + }"#; + + let current_time: DateTime = Utc::now(); + let future_time = current_time + ChronoDuration::seconds(30); + let spawn_time = future_time.to_rfc3339(); + + let proposal = raw_proposal.replace("{consumer_chain_id}", consumer_chain_id); + let proposal = proposal.replace("{spawn_time}", &spawn_time); + + self.write_file("consumer_proposal_topn.json", &proposal)?; + + submit_consumer_chain_proposal( + self.chain_id.as_str(), + &self.command_path, + &self.home_path, + &self.rpc_listen_address(), + fees, + ) + } + + fn create_permisionless_consumer( + &self, + consumer_chain_id: &str, + fees: &str, + ) -> Result { + let raw_proposal = r#" + { + "chain_id": "{consumer_chain_id}", + "metadata": { + "name": "consumer-1-metadata-name", + "description":"consumer-1-metadata-description", + "metadata": "consumer-1-metadata-metadata" + }, + "initialization_parameters": { + "initial_height": { + "revision_number": 0, + "revision_height": 1 + }, + "genesis_hash": "Z2VuX2hhc2g=", + "binary_hash": "YmluX2hhc2g=", + "spawn_time": "{spawn_time}", + "unbonding_period": 100000000000, + "ccv_timeout_period": 100000000000, + "transfer_timeout_period": 100000000000, + "consumer_redistribution_fraction": "0.75", + "blocks_per_distribution_transmission": 10, + "historical_entries": 10000, + "distribution_transmission_channel": "" + } + }"#; + let current_time: DateTime = Utc::now(); + let future_time = current_time + ChronoDuration::seconds(30); + let spawn_time = future_time.to_rfc3339(); + + let proposal = raw_proposal.replace("{consumer_chain_id}", consumer_chain_id); + let proposal = proposal.replace("{spawn_time}", &spawn_time); + + self.write_file("consumer_proposal.json", &proposal)?; + + create_consumer( + self.chain_id.as_str(), + &self.command_path, + &self.home_path, + &self.rpc_listen_address(), + fees, + ) + } + + fn validator_opt_in(&self, consumer_chain_id: &str, fees: &str) -> Result<(), Error> { + validator_opt_in( + self.chain_id.as_str(), + &self.command_path, + &self.home_path, + &self.rpc_listen_address(), + fees, + consumer_chain_id, + ) + } + + fn update_consumer( + &self, + consumer_chain_id: &str, + fees: &str, + validator: &str, + ) -> Result<(), Error> { + let gov_address = query_auth_module( + self.chain_id.as_str(), + &self.command_path, + &self.home_path, + &self.rpc_listen_address(), + "gov", + )?; let res = simple_exec( self.chain_id.as_str(), "jq", @@ -294,37 +437,23 @@ impl ChainBootstrapMethodsExt for ChainDriver { spawn_time.pop(); let raw_proposal = r#" { - "title": "Create consumer chain", - "summary": "First consumer chain", - "chain_id": "{consumer_chain_id}", - "initial_height": { - "revision_number": 1, - "revision_height": 1 - }, - "genesis_hash": "Z2VuX2hhc2g=", - "binary_hash": "YmluX2hhc2g=", - "spawn_time": "{spawn_time}", - "blocks_per_distribution_transmission": 10, - "consumer_redistribution_fraction": "0.75", - "distribution_transmission_channel": "", - "historical_entries": 10000, - "transfer_timeout_period": 100000000000, - "ccv_timeout_period": 100000000000, - "unbonding_period": 100000000000, - "deposit": "10000001stake", - "top_N": 95, - "validators_power_cap": 0, - "validator_set_cap": 0, - "allowlist": [], - "denylist": [] + "consumer_id": "{consumer_chain_id}", + "owner_address": "{validator}", + "new_owner_address": "{gov_address}", + "metadata": { + "name": "consumer-1-metadata-name", + "description":"consumer-1-metadata-description", + "metadata": "consumer-1-metadata-metadata" + } }"#; let proposal = raw_proposal.replace("{consumer_chain_id}", consumer_chain_id); - let proposal = proposal.replace("{spawn_time}", &spawn_time); + let proposal = proposal.replace("{gov_address}", &gov_address); + let proposal = proposal.replace("{validator}", validator); - self.write_file("consumer_proposal.json", &proposal)?; + self.write_file("consumer_update_proposal.json", &proposal)?; - submit_consumer_chain_proposal( + update_consumer( self.chain_id.as_str(), &self.command_path, &self.home_path, diff --git a/tools/test-framework/src/chain/tagged.rs b/tools/test-framework/src/chain/tagged.rs index b574c6eef1..af7a73c06a 100644 --- a/tools/test-framework/src/chain/tagged.rs +++ b/tools/test-framework/src/chain/tagged.rs @@ -132,7 +132,7 @@ impl<'a, Chain: Send> TaggedChainDriverExt for MonoTagged Result, Error> { + fn query_consumer_chains(&self) -> Result, Error> { self.value().query_consumer_chains() } @@ -456,4 +457,8 @@ where self.value() .query_upgrade_error(request, height, include_proof) } + + fn query_ccv_consumer_id(&self, client_id: &ClientId) -> Result { + self.value().query_ccv_consumer_id(client_id) + } } diff --git a/tools/test-framework/src/types/config.rs b/tools/test-framework/src/types/config.rs index a586c46e84..ff0889465b 100644 --- a/tools/test-framework/src/types/config.rs +++ b/tools/test-framework/src/types/config.rs @@ -34,6 +34,8 @@ pub struct TestConfig { pub compat_modes: Option>, + pub ipv6_grpc: bool, + /** The directory path for storing the chain and relayer files. Defaults to `"data"`. This can be overridden with the `$CHAIN_STORE_DIR`