diff --git a/.changelog/unreleased/bug-fixes/ibc-relayer/3770-non-utf8-packet-data.md b/.changelog/unreleased/bug-fixes/ibc-relayer/3770-non-utf8-packet-data.md new file mode 100644 index 0000000000..ab7a573fcb --- /dev/null +++ b/.changelog/unreleased/bug-fixes/ibc-relayer/3770-non-utf8-packet-data.md @@ -0,0 +1,6 @@ +- Allow relaying ICS-04 packets with non-UTF-8 payloads ([\#3770](https://github.com/informalsystems/hermes/issues/3770)) + Hermes does not assume anymore that an ICS-04 packet data is valid UTF-8, + by using the `packet_data_hex` attribute when assembling a packet from events, instead of the deprecated `packet_data` attribute. + Relying on the `packet_data` attribute enforces a UTF-8 encoded payload (eg. JSON), disallowing eg. Protobuf-encoded payloads. + The `packet_data` atttribute [has been deprecated][0] in favor of `packet_data_hex` since IBC-Go v1.0.0. + [0]: https://github.com/cosmos/ibc-go/blob/fadf8f2b0ab184798d021d220d877e00c7634e26/CHANGELOG.md?plain=1#L1417 diff --git a/.changelog/unreleased/bug-fixes/ibc-relayer/3831-better-compat-check.md b/.changelog/unreleased/bug-fixes/ibc-relayer/3831-better-compat-check.md new file mode 100644 index 0000000000..b533e98fe6 --- /dev/null +++ b/.changelog/unreleased/bug-fixes/ibc-relayer/3831-better-compat-check.md @@ -0,0 +1,2 @@ +- Improve reliability of compatibility check and fix parsing of expected modules + versions ([\#3831](https://github.com/informalsystems/hermes/issues/3831)) \ No newline at end of file diff --git a/.changelog/unreleased/features/ibc-relayer/3811-relayer-memo-overwrite.md b/.changelog/unreleased/features/ibc-relayer/3811-relayer-memo-overwrite.md new file mode 100644 index 0000000000..fcd1a840d3 --- /dev/null +++ b/.changelog/unreleased/features/ibc-relayer/3811-relayer-memo-overwrite.md @@ -0,0 +1,3 @@ +- Add a per-chain configuration `memo_overwrite` allowing users + to overwite the relayer memo used for each transaction + ([\#3811](https://github.com/informalsystems/hermes/issues/3811)) \ No newline at end of file diff --git a/.changelog/unreleased/features/ibc-telemetry/3845-add-simulate-errors-metric.md b/.changelog/unreleased/features/ibc-telemetry/3845-add-simulate-errors-metric.md new file mode 100644 index 0000000000..7e83a36b32 --- /dev/null +++ b/.changelog/unreleased/features/ibc-telemetry/3845-add-simulate-errors-metric.md @@ -0,0 +1,11 @@ +- Added a new Prometheus metric `simulate_errors` for tracking when a transaction simulation fails, with the following labels: + * `recoverable` (can the execution continue if this happened?) + * `account` (account from which the tx was sent) + * `error_description` (description of the error) + ([\#3845](https://github.com/informalsystems/hermes/issues/3845)) + + ``` + # HELP simulate_errors_total Number of errors observed by Hermes when simulating a Tx + # TYPE simulate_errors_total counter + simulate_errors_total{account="osmo17ndx5qfku28ymxgmq6zq4a6d02dvpfjjul0hyh",error_description="Unknown error",recoverable="false",service_name="unknown_service",otel_scope_name="hermes",otel_scope_version=""} 4 + ``` diff --git a/.changelog/unreleased/improvements/ibc-relayer-cli/3792-legacy-simulation.md b/.changelog/unreleased/improvements/ibc-relayer-cli/3792-legacy-simulation.md new file mode 100644 index 0000000000..f09d5088c1 --- /dev/null +++ b/.changelog/unreleased/improvements/ibc-relayer-cli/3792-legacy-simulation.md @@ -0,0 +1,2 @@ +- Recover from gas simulation failures on legacy chains. + ([\#3792](https://github.com/informalsystems/hermes/issues/3792)) diff --git a/.changelog/unreleased/improvements/ibc-relayer/3540-ordered-channels-resilience.md b/.changelog/unreleased/improvements/ibc-relayer/3540-ordered-channels-resilience.md new file mode 100644 index 0000000000..88899c96f5 --- /dev/null +++ b/.changelog/unreleased/improvements/ibc-relayer/3540-ordered-channels-resilience.md @@ -0,0 +1,6 @@ +- Improve resilience when relaying on ordered channels. + When relaying packets on an ordered channel, Hermes will now attempt + to detect whether the next message to send has the sequence number + expected on that channel. If there is a mismatch, then Hermes will trigger a packet + clear on the channel to unblock it before resuming operations on that channel. + ([\#3540](https://github.com/informalsystems/hermes/issues/3540)) diff --git a/.github/workflows/cargo-doc.yaml b/.github/workflows/cargo-doc.yaml index ebc03618b6..1cb9f346a6 100644 --- a/.github/workflows/cargo-doc.yaml +++ b/.github/workflows/cargo-doc.yaml @@ -28,7 +28,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions-rs/toolchain@v1 with: - toolchain: nightly-2023-07-13 + toolchain: nightly-2024-03-03 override: true - name: Build API documentation diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 5b3ac9891e..14e6875d8a 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -13,15 +13,9 @@ env: jobs: docker-build: - runs-on: ubuntu-latest + runs-on: macos-14 strategy: fail-fast: false - matrix: - platform: - - id: linux/amd64 - name: amd64 - - id: linux/arm64 - name: arm64 steps: - name: Checkout uses: actions/checkout@v4 @@ -31,13 +25,15 @@ jobs: uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY_IMAGE }} - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + tags: | + type=ref,event=tag + type=ref,event=workflow_dispatch - name: Set up Docker Buildx id: buildx uses: docker/setup-buildx-action@v3 + with: + platforms: linux/amd64,linux/arm64 - name: Login to Docker Hub uses: docker/login-action@v3 @@ -45,78 +41,27 @@ jobs: username: ${{ secrets.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} - - name: Build and push by digest + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push id: build uses: docker/build-push-action@v5 with: context: . file: ./ci/release/hermes.Dockerfile - platforms: ${{ matrix.platform.id }} - labels: ${{ steps.meta.outputs.labels }} - outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true + platforms: linux/amd64,linux/arm64 + push: true cache-from: type=gha cache-to: type=gha,mode=max - - - name: Export digest - run: | - mkdir -p /tmp/digests - digest="${{ steps.build.outputs.digest }}" - touch "/tmp/digests/${digest#sha256:}" - - - name: Upload digest - uses: actions/upload-artifact@v4 - with: - name: digests-${{ matrix.platform.name }} - path: /tmp/digests/* - if-no-files-found: error - retention-days: 1 - - docker-merge: - runs-on: ubuntu-latest - needs: - - docker-build - steps: - - name: Download digests - uses: actions/download-artifact@v4 - with: - pattern: digests-* - merge-multiple: true - path: /tmp/digests - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Docker meta - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.REGISTRY_IMAGE }} - - - name: Login to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_HUB_USERNAME }} - password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} - - - name: Create manifest list and push - working-directory: /tmp/digests - run: | - docker buildx imagetools create --tag ${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.version }} \ - $(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *) + tags: | + ${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.version }} + ghcr.io/${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.version }} - name: Inspect image run: | docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.version }} - - - name: Login to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Push image to GHCR - run: | - docker buildx imagetools create \ - --tag ghcr.io/${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.version }} \ - ${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.version }} diff --git a/.github/workflows/misbehaviour.yml b/.github/workflows/misbehaviour.yml index 724e55b93f..c3adff0afc 100644 --- a/.github/workflows/misbehaviour.yml +++ b/.github/workflows/misbehaviour.yml @@ -55,7 +55,7 @@ jobs: substituters = https://cache.nixos.org https://cosmosnix-store.s3.us-east-2.amazonaws.com trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= cosmosnix.store-1:O28HneR1MPtgY3WYruWFuXCimRPwY7em5s0iynkQxdk= - name: Install sconfig - uses: jaxxstorm/action-install-gh-release@v1.10.0 + uses: jaxxstorm/action-install-gh-release@v1.11.0 with: repo: freshautomations/sconfig platform: linux @@ -64,7 +64,7 @@ jobs: rename-to: sconfig chmod: 0755 - name: Install stoml - uses: jaxxstorm/action-install-gh-release@v1.10.0 + uses: jaxxstorm/action-install-gh-release@v1.11.0 with: repo: freshautomations/stoml platform: linux @@ -106,7 +106,7 @@ jobs: substituters = https://cache.nixos.org https://cosmosnix-store.s3.us-east-2.amazonaws.com trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= cosmosnix.store-1:O28HneR1MPtgY3WYruWFuXCimRPwY7em5s0iynkQxdk= - name: Install sconfig - uses: jaxxstorm/action-install-gh-release@v1.10.0 + uses: jaxxstorm/action-install-gh-release@v1.11.0 with: repo: freshautomations/sconfig platform: linux @@ -115,7 +115,7 @@ jobs: rename-to: sconfig chmod: 0755 - name: Install stoml - uses: jaxxstorm/action-install-gh-release@v1.10.0 + uses: jaxxstorm/action-install-gh-release@v1.11.0 with: repo: freshautomations/stoml platform: linux @@ -157,7 +157,7 @@ jobs: substituters = https://cache.nixos.org https://cosmosnix-store.s3.us-east-2.amazonaws.com trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= cosmosnix.store-1:O28HneR1MPtgY3WYruWFuXCimRPwY7em5s0iynkQxdk= - name: Install sconfig - uses: jaxxstorm/action-install-gh-release@v1.10.0 + uses: jaxxstorm/action-install-gh-release@v1.11.0 with: repo: freshautomations/sconfig platform: linux @@ -166,7 +166,7 @@ jobs: rename-to: sconfig chmod: 0755 - name: Install stoml - uses: jaxxstorm/action-install-gh-release@v1.10.0 + uses: jaxxstorm/action-install-gh-release@v1.11.0 with: repo: freshautomations/stoml platform: linux @@ -209,7 +209,7 @@ jobs: substituters = https://cache.nixos.org https://cosmosnix-store.s3.us-east-2.amazonaws.com trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= cosmosnix.store-1:O28HneR1MPtgY3WYruWFuXCimRPwY7em5s0iynkQxdk= - name: Install sconfig - uses: jaxxstorm/action-install-gh-release@v1.10.0 + uses: jaxxstorm/action-install-gh-release@v1.11.0 with: repo: freshautomations/sconfig platform: linux @@ -218,7 +218,7 @@ jobs: rename-to: sconfig chmod: 0755 - name: Install stoml - uses: jaxxstorm/action-install-gh-release@v1.10.0 + uses: jaxxstorm/action-install-gh-release@v1.11.0 with: repo: freshautomations/stoml platform: linux diff --git a/Cargo.lock b/Cargo.lock index 54100bfeb2..d2ce93d898 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -81,9 +81,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.6" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" [[package]] name = "anstyle-parse" @@ -115,9 +115,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.79" +version = "1.0.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" +checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" [[package]] name = "arc-swap" @@ -894,9 +894,9 @@ dependencies = [ [[package]] name = "ed25519-dalek" -version = "2.1.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" +checksum = "1f628eaec48bfd21b865dc2950cfa014450c01d2fa2b69a86c2fd5844ec523c0" dependencies = [ "curve25519-dalek", "ed25519", @@ -1044,9 +1044,9 @@ dependencies = [ [[package]] name = "fiat-crypto" -version = "0.2.6" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1676f435fc1dadde4d03e43f5d62b259e1ce5f40bd4ffb21db2b42ebe59c1382" +checksum = "27573eac26f4dd11e2b1916c3fe1baa56407c83c71a773a8ba17ec0bca03b6b7" [[package]] name = "fixed-hash" @@ -1260,7 +1260,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap 2.2.2", + "indexmap 2.2.1", "slab", "tokio", "tokio-util", @@ -1311,9 +1311,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.3.5" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c62115964e08cb8039170eb33c1d0e2388a256930279edca206fff675f82c3" +checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f" [[package]] name = "hex" @@ -1477,14 +1477,15 @@ dependencies = [ "tendermint", "tendermint-rpc", "time", - "toml 0.8.10", + "toml 0.8.8", "tonic", ] [[package]] name = "ibc-proto" -version = "0.41.0" -source = "git+https://github.com/cosmos/ibc-proto-rs.git?branch=romac/channel-upgrade-only#0d28c0068634ef3c863e7a56a34c2c2fce222164" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80701a1b0e0ade6f28071d7d2d6412e01869f22bb7039f52722161ecdeb083e0" dependencies = [ "base64", "bytes", @@ -1560,7 +1561,7 @@ dependencies = [ "tiny-keccak", "tokio", "tokio-stream", - "toml 0.8.10", + "toml 0.8.8", "tonic", "tracing", "tracing-subscriber", @@ -1619,7 +1620,7 @@ dependencies = [ "reqwest", "serde", "tokio", - "toml 0.8.10", + "toml 0.8.8", "tracing", ] @@ -1700,7 +1701,7 @@ dependencies = [ "subtle-encoding", "tendermint-rpc", "tokio", - "toml 0.8.10", + "toml 0.8.8", "tonic", "tracing", "tracing-subscriber", @@ -1768,9 +1769,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.2" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "824b2ae422412366ba479e8111fd301f7b5faece8149317bb81925979a53f520" +checksum = "433de089bd45971eecf4668ee0ee8f4cec17db4f8bd8f7bc3197a6ce37aa7d9b" dependencies = [ "equivalent", "hashbrown 0.14.3", @@ -1818,9 +1819,9 @@ checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "js-sys" -version = "0.3.68" +version = "0.3.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" +checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1" dependencies = [ "wasm-bindgen", ] @@ -1854,9 +1855,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.153" +version = "0.2.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" [[package]] name = "libredox" @@ -1926,9 +1927,9 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "miniz_oxide" -version = "0.7.2" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" dependencies = [ "adler", ] @@ -1987,12 +1988,6 @@ dependencies = [ "serde", ] -[[package]] -name = "num-conv" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" - [[package]] name = "num-derive" version = "0.3.3" @@ -2042,7 +2037,7 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi 0.3.5", + "hermit-abi 0.3.4", "libc", ] @@ -2230,18 +2225,18 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project" -version = "1.1.4" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0302c4a0442c456bd56f841aee5c3bfd17967563f6fadc9ceb9f9c23cf3807e0" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.4" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", @@ -2387,11 +2382,11 @@ checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" [[package]] name = "pulldown-cmark" -version = "0.9.6" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57206b407293d2bcd3af849ce869d52068623f19e1b5ff8e8778e3309439682b" +checksum = "77a1a2f1f0a7ecff9c31abbe177637be0e97a0aef46cf8738ece09327985d998" dependencies = [ - "bitflags 2.4.2", + "bitflags 1.3.2", "memchr", "unicase", ] @@ -2487,7 +2482,7 @@ checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.5", + "regex-automata 0.4.4", "regex-syntax 0.8.2", ] @@ -2502,9 +2497,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.5" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" +checksum = "3b7fa1134405e2ec9353fd416b17f8dacd46c473d7d3fd1cf202706a14eb792a" dependencies = [ "aho-corasick", "memchr", @@ -2525,9 +2520,9 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "reqwest" -version = "0.11.24" +version = "0.11.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251" +checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41" dependencies = [ "base64", "bytes", @@ -2552,7 +2547,6 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "sync_wrapper", "system-configuration", "tokio", "tokio-rustls", @@ -2805,9 +2799,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.196" +version = "1.0.195" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" +checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" dependencies = [ "serde_derive", ] @@ -2833,9 +2827,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.196" +version = "1.0.195" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" +checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" dependencies = [ "proc-macro2", "quote", @@ -2844,9 +2838,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.113" +version = "1.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79" +checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" dependencies = [ "itoa", "ryu", @@ -2901,7 +2895,7 @@ version = "0.9.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adf8a49373e98a4c5f0ceb5d05aa7c648d75f63774981ed95b7c7443bbd50c6e" dependencies = [ - "indexmap 2.2.2", + "indexmap 2.2.1", "itoa", "ryu", "serde", @@ -3208,9 +3202,9 @@ checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" [[package]] name = "tempfile" -version = "3.10.0" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if 1.0.0", "fastrand", @@ -3327,9 +3321,9 @@ dependencies = [ [[package]] name = "tendermint-proto" -version = "0.34.0" +version = "0.34.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cc728a4f9e891d71adf66af6ecaece146f9c7a11312288a3107b3e1d6979aaf" +checksum = "b797dd3d2beaaee91d2f065e7bdf239dc8d80bba4a183a288bc1279dd5a69a1e" dependencies = [ "bytes", "flex-error", @@ -3431,18 +3425,18 @@ checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" [[package]] name = "thiserror" -version = "1.0.56" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" +checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.56" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" +checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" dependencies = [ "proc-macro2", "quote", @@ -3461,12 +3455,11 @@ dependencies = [ [[package]] name = "time" -version = "0.3.34" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" +checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e" dependencies = [ "deranged", - "num-conv", "powerfmt", "serde", "time-core", @@ -3481,11 +3474,10 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.17" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" +checksum = "26197e33420244aeb70c3e8c78376ca46571bc4e701e4791c2cd9f57dcb3a43f" dependencies = [ - "num-conv", "time-core", ] @@ -3534,9 +3526,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.36.0" +version = "1.35.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" +checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" dependencies = [ "backtrace", "bytes", @@ -3618,9 +3610,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.10" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a9aad4a3066010876e8dcf5a8a06e70a558751117a145c6ce2b82c2e2054290" +checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35" dependencies = [ "serde", "serde_spanned", @@ -3639,11 +3631,11 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.4" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c9ffdf896f8daaabf9b66ba8e77ea1ed5ed0f72821b398aba62352e95062951" +checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" dependencies = [ - "indexmap 2.2.2", + "indexmap 2.2.1", "serde", "serde_spanned", "toml_datetime", @@ -4011,9 +4003,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.91" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f" +checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -4021,9 +4013,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.91" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b" +checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd" dependencies = [ "bumpalo", "log", @@ -4036,9 +4028,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.41" +version = "0.4.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877b9c3f61ceea0e56331985743b13f3d25c406a7098d45180fb5f09bc19ed97" +checksum = "bde2032aeb86bdfaecc8b261eef3cba735cc426c1f3a3416d1e0791be95fc461" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -4048,9 +4040,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.91" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed" +checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4058,9 +4050,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.91" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" +checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7" dependencies = [ "proc-macro2", "quote", @@ -4071,15 +4063,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.91" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" +checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" [[package]] name = "web-sys" -version = "0.3.68" +version = "0.3.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446" +checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed" dependencies = [ "js-sys", "wasm-bindgen", @@ -4250,9 +4242,9 @@ checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" [[package]] name = "winnow" -version = "0.5.39" +version = "0.5.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5389a154b01683d28c77f8f68f49dea75f0a4da32557a58f68ee51ebba472d29" +checksum = "b7cf47b659b318dccbd69cc4797a39ae128f533dce7902a1096044d1967b9c16" dependencies = [ "memchr", ] @@ -4286,3 +4278,8 @@ dependencies = [ "quote", "syn 2.0.48", ] + +[[patch.unused]] +name = "ibc-proto" +version = "0.41.0" +source = "git+https://github.com/cosmos/ibc-proto-rs.git?branch=romac/channel-upgrade-only#0d28c0068634ef3c863e7a56a34c2c2fce222164" diff --git a/README.md b/README.md index b816b659e0..98adbc745a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Hermes IBC relayer +![hermes-banner](https://github.com/informalsystems/hermes/assets/1757002/0878ab2a-1c6f-4137-a089-66352f948407) + [![Cosmos ecosystem][cosmos-shield]][cosmos-link] [![Build Status][build-image]][build-link] diff --git a/config.toml b/config.toml index f45e2289cf..63c5881060 100644 --- a/config.toml +++ b/config.toml @@ -369,6 +369,13 @@ trust_threshold = '2/3' # operational debugging information, e.g., relayer build version. memo_prefix = '' +# If this is set to a string, it will overwrite the memo used by Hermes for each transaction +# it submits to this chain. +# Default: not set. +# This is used for chains which have a very small character limit for the memo, +# and the additional information appended by Hermes would overflow that limit. +# memo_overwrite = '' + # This section specifies the filters for policy based relaying. # # Default: no policy / filters, allow all packets on all channels. diff --git a/crates/chain-registry/Cargo.toml b/crates/chain-registry/Cargo.toml index d64dcf5f9e..e90d844b40 100644 --- a/crates/chain-registry/Cargo.toml +++ b/crates/chain-registry/Cargo.toml @@ -13,7 +13,7 @@ description = """ [dependencies] ibc-relayer-types = { version = "0.27.0", path = "../relayer-types" } -ibc-proto = { version = "0.41.0", features = ["serde"] } +ibc-proto = { version = "0.42.0", features = ["serde"] } tendermint-rpc = { version = "0.34.0", features = ["http-client", "websocket-client"] } async-trait = "0.1.72" diff --git a/crates/relayer-cli/Cargo.toml b/crates/relayer-cli/Cargo.toml index 8087507ffb..82303a33ff 100644 --- a/crates/relayer-cli/Cargo.toml +++ b/crates/relayer-cli/Cargo.toml @@ -38,7 +38,7 @@ console = "0.15.5" crossbeam-channel = "0.5.11" dialoguer = "0.11.0" dirs-next = "2.0.0" -eyre = "0.6.8" +eyre = "0.6.12" flex-error = { version = "0.4.4", default-features = false, features = ["std", "eyre_tracer"] } futures = "0.3.27" hdpath = "0.6.3" diff --git a/crates/relayer-cli/src/chain_registry.rs b/crates/relayer-cli/src/chain_registry.rs index 04b28f21c6..93f68821b1 100644 --- a/crates/relayer-cli/src/chain_registry.rs +++ b/crates/relayer-cli/src/chain_registry.rs @@ -160,6 +160,7 @@ where client_refresh_rate: default::client_refresh_rate(), ccv_consumer_chain: false, memo_prefix: Memo::default(), + memo_overwrite: None, proof_specs: Default::default(), trust_threshold: TrustThreshold::default(), gas_price: GasPrice { diff --git a/crates/relayer-cli/src/commands.rs b/crates/relayer-cli/src/commands.rs index 60879018f5..e922061029 100644 --- a/crates/relayer-cli/src/commands.rs +++ b/crates/relayer-cli/src/commands.rs @@ -151,7 +151,11 @@ impl Configurable for CliCmd { for ccfg in config.chains.iter_mut() { #[allow(irrefutable_let_patterns)] if let ChainConfig::CosmosSdk(ref mut cosmos_ccfg) = ccfg { - cosmos_ccfg.memo_prefix.apply_suffix(&suffix); + if let Some(memo) = &cosmos_ccfg.memo_overwrite { + cosmos_ccfg.memo_prefix = memo.clone(); + } else { + cosmos_ccfg.memo_prefix.apply_suffix(&suffix); + } } } diff --git a/crates/relayer-types/Cargo.toml b/crates/relayer-types/Cargo.toml index d21e814c52..832a660597 100644 --- a/crates/relayer-types/Cargo.toml +++ b/crates/relayer-types/Cargo.toml @@ -24,7 +24,7 @@ mocks = ["tendermint-testgen", "clock"] [dependencies] # Proto definitions for all IBC-related interfaces, e.g., connections or channels. -ibc-proto = { version = "0.41.0", features = ["serde"] } +ibc-proto = { version = "0.42.0", features = ["serde"] } ics23 = { version = "0.11.1", features = ["std", "host-functions"] } time = { version = "0.3" } serde_derive = { version = "1.0.104" } @@ -47,7 +47,7 @@ version = "0.34.0" features = ["clock"] [dependencies.tendermint-proto] -version = "0.34.0" +version = "0.34.1" [dependencies.tendermint-light-client-verifier] version = "0.34.0" diff --git a/crates/relayer-types/src/applications/ics27_ica/error.rs b/crates/relayer-types/src/applications/ics27_ica/error.rs index 52f2027496..4143b2467f 100644 --- a/crates/relayer-types/src/applications/ics27_ica/error.rs +++ b/crates/relayer-types/src/applications/ics27_ica/error.rs @@ -8,11 +8,15 @@ define_error! { Error { Owner [ SignerError ] - | _ | { "failed to parse owner" }, + | _ | { "failed to parse owner" }, InvalidConnectionIdentifier [ ValidationError ] - | _ | { "connection identifier error" }, + | _ | { "connection identifier error" }, + + InvalidOrdering + { ordering: i32 } + | e | { format_args!("invalid ordering: {}", e.ordering) }, InvalidPacketData | _ | { "packet data is None" }, diff --git a/crates/relayer-types/src/applications/ics27_ica/msgs/register.rs b/crates/relayer-types/src/applications/ics27_ica/msgs/register.rs index 3e2797005a..082cae11ed 100644 --- a/crates/relayer-types/src/applications/ics27_ica/msgs/register.rs +++ b/crates/relayer-types/src/applications/ics27_ica/msgs/register.rs @@ -4,6 +4,7 @@ use ibc_proto::ibc::applications::interchain_accounts::controller::v1::MsgRegist use ibc_proto::Protobuf; use crate::applications::ics27_ica::error::Error; +use crate::core::ics04_channel::channel::Ordering; use crate::core::ics04_channel::version::Version; use crate::core::ics24_host::error::ValidationError; use crate::core::ics24_host::identifier::ConnectionId; @@ -18,6 +19,7 @@ pub struct MsgRegisterInterchainAccount { pub owner: Signer, pub connection_id: ConnectionId, pub version: Version, + pub ordering: Ordering, } impl Msg for MsgRegisterInterchainAccount { @@ -46,6 +48,8 @@ impl TryFrom for MsgRegisterInterchainAccount { .parse() .map_err(Error::invalid_connection_identifier)?, version: value.version.into(), + ordering: Ordering::from_i32(value.ordering) + .map_err(|_| Error::invalid_ordering(value.ordering))?, }) } } @@ -56,7 +60,65 @@ impl From for RawMsgRegisterInterchainAccount { owner: value.owner.to_string(), connection_id: value.connection_id.to_string(), version: value.version.to_string(), - ordering: 2, + ordering: value.ordering as i32, + } + } +} + +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct LegacyRawMsgRegisterInterchainAccount { + #[prost(string, tag = "1")] + pub owner: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub connection_id: ::prost::alloc::string::String, + #[prost(string, tag = "3")] + pub version: ::prost::alloc::string::String, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct LegacyMsgRegisterInterchainAccount { + pub owner: Signer, + pub connection_id: ConnectionId, + pub version: Version, +} + +impl Msg for LegacyMsgRegisterInterchainAccount { + type ValidationError = ValidationError; + type Raw = LegacyRawMsgRegisterInterchainAccount; + + fn route(&self) -> String { + crate::keys::ROUTER_KEY.to_string() + } + + fn type_url(&self) -> String { + TYPE_URL.to_string() + } +} + +impl Protobuf for LegacyMsgRegisterInterchainAccount {} + +impl TryFrom for LegacyMsgRegisterInterchainAccount { + type Error = Error; + + fn try_from(value: LegacyRawMsgRegisterInterchainAccount) -> Result { + Ok(LegacyMsgRegisterInterchainAccount { + owner: value.owner.parse().map_err(Error::owner)?, + connection_id: value + .connection_id + .parse() + .map_err(Error::invalid_connection_identifier)?, + version: value.version.into(), + }) + } +} + +impl From for LegacyRawMsgRegisterInterchainAccount { + fn from(value: LegacyMsgRegisterInterchainAccount) -> Self { + LegacyRawMsgRegisterInterchainAccount { + owner: value.owner.to_string(), + connection_id: value.connection_id.to_string(), + version: value.version.to_string(), } } } diff --git a/crates/relayer-types/src/core/ics04_channel/channel.rs b/crates/relayer-types/src/core/ics04_channel/channel.rs index 8b984f483c..90b8ba8d4c 100644 --- a/crates/relayer-types/src/core/ics04_channel/channel.rs +++ b/crates/relayer-types/src/core/ics04_channel/channel.rs @@ -11,7 +11,6 @@ use ibc_proto::ibc::core::channel::v1::{ IdentifiedChannel as RawIdentifiedChannel, }; -use crate::core::ics04_channel::packet::Sequence; use crate::core::ics04_channel::{error::Error, version::Version}; use crate::core::ics24_host::identifier::{ChannelId, ConnectionId, PortId}; @@ -70,7 +69,7 @@ impl From for RawIdentifiedChannel { version: value.channel_end.version.to_string(), port_id: value.port_id.to_string(), channel_id: value.channel_id.to_string(), - upgrade_sequence: 1, + upgrade_sequence: value.channel_end.upgrade_sequence, } } } @@ -82,15 +81,15 @@ pub struct ChannelEnd { pub remote: Counterparty, pub connection_hops: Vec, pub version: Version, - pub upgrade_sequence: Sequence, + pub upgrade_sequence: u64, } impl Display for ChannelEnd { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { write!( f, - "ChannelEnd {{ state: {}, ordering: {}, remote: {}, connection_hops: {}, version: {} }}", - self.state, self.ordering, self.remote, PrettySlice(&self.connection_hops), self.version + "ChannelEnd {{ state: {}, ordering: {}, remote: {}, connection_hops: {}, version: {}, upgrade_sequence: {} }}", + self.state, self.ordering, self.remote, PrettySlice(&self.connection_hops), self.version, self.upgrade_sequence ) } } @@ -103,7 +102,7 @@ impl Default for ChannelEnd { remote: Counterparty::default(), connection_hops: Vec::new(), version: Version::default(), - upgrade_sequence: Sequence::from(0), // The value of 0 indicates the channel has never been upgraded + upgrade_sequence: 0, } } } @@ -144,7 +143,7 @@ impl TryFrom for ChannelEnd { remote, connection_hops, version, - Sequence::from(value.upgrade_sequence), + value.upgrade_sequence, )) } } @@ -161,7 +160,7 @@ impl From for RawChannel { .map(|v| v.as_str().to_string()) .collect(), version: value.version.to_string(), - upgrade_sequence: value.upgrade_sequence.into(), + upgrade_sequence: value.upgrade_sequence, } } } @@ -174,7 +173,7 @@ impl ChannelEnd { remote: Counterparty, connection_hops: Vec, version: Version, - upgrade_sequence: Sequence, + upgrade_sequence: u64, ) -> Self { Self { state, @@ -238,25 +237,25 @@ impl ChannelEnd { /// Helper function to compare the state of this end with another state. pub fn state_matches(&self, other: &State) -> bool { - self.state() == other + self.state.eq(other) } /// Helper function to compare the order of this end with another order. pub fn order_matches(&self, other: &Ordering) -> bool { - self.ordering() == other + self.ordering.eq(other) } #[allow(clippy::ptr_arg)] pub fn connection_hops_matches(&self, other: &Vec) -> bool { - self.connection_hops() == other + self.connection_hops.eq(other) } pub fn counterparty_matches(&self, other: &Counterparty) -> bool { - self.counterparty() == other + self.counterparty().eq(other) } pub fn version_matches(&self, other: &Version) -> bool { - self.version() == other + self.version().eq(other) } } @@ -333,7 +332,7 @@ impl From for RawCounterparty { } } -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, Default)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize, Default)] pub enum Ordering { Uninitialized = 0, #[default] @@ -543,7 +542,7 @@ pub mod test_util { counterparty: Some(get_dummy_raw_counterparty()), connection_hops: vec![ConnectionId::default().to_string()], version: "ics20".to_string(), // The version is not validated. - upgrade_sequence: 0, // The value of 0 indicates the channel has never been upgraded + upgrade_sequence: 0, } } } diff --git a/crates/relayer-types/src/core/ics04_channel/error.rs b/crates/relayer-types/src/core/ics04_channel/error.rs index 3006423d05..8fef18e1c2 100644 --- a/crates/relayer-types/src/core/ics04_channel/error.rs +++ b/crates/relayer-types/src/core/ics04_channel/error.rs @@ -227,6 +227,14 @@ define_error! { e.given_sequence, e.next_sequence) }, + InvalidPacketData + { + data: String, + } + | e | { + format_args!("Invalid packet data, not a valid hex-encoded string: {}", e.data) + }, + LowPacketHeight { chain_height: Height, diff --git a/crates/relayer-types/src/core/ics04_channel/events.rs b/crates/relayer-types/src/core/ics04_channel/events.rs index 6405303457..5fcf32f1b6 100644 --- a/crates/relayer-types/src/core/ics04_channel/events.rs +++ b/crates/relayer-types/src/core/ics04_channel/events.rs @@ -1,8 +1,9 @@ //! Types for the IBC events emitted from Tendermint Websocket by the channels module. -use serde_derive::{Deserialize, Serialize}; use std::fmt::{Display, Error as FmtError, Formatter}; use std::str; + +use serde_derive::{Deserialize, Serialize}; use tendermint::abci; use crate::core::ics04_channel::channel::Ordering; @@ -23,7 +24,7 @@ pub const COUNTERPARTY_PORT_ID_ATTRIBUTE_KEY: &str = "counterparty_port_id"; /// Packet event attribute keys pub const PKT_SEQ_ATTRIBUTE_KEY: &str = "packet_sequence"; -pub const PKT_DATA_ATTRIBUTE_KEY: &str = "packet_data"; +pub const PKT_DATA_ATTRIBUTE_KEY: &str = "packet_data_hex"; pub const PKT_SRC_PORT_ATTRIBUTE_KEY: &str = "packet_src_port"; pub const PKT_SRC_CHANNEL_ATTRIBUTE_KEY: &str = "packet_src_channel"; pub const PKT_DST_PORT_ATTRIBUTE_KEY: &str = "packet_dst_port"; diff --git a/crates/relayer-types/src/core/ics04_channel/msgs/chan_close_confirm.rs b/crates/relayer-types/src/core/ics04_channel/msgs/chan_close_confirm.rs index 44d810eb89..33b4873899 100644 --- a/crates/relayer-types/src/core/ics04_channel/msgs/chan_close_confirm.rs +++ b/crates/relayer-types/src/core/ics04_channel/msgs/chan_close_confirm.rs @@ -3,7 +3,6 @@ use ibc_proto::Protobuf; use ibc_proto::ibc::core::channel::v1::MsgChannelCloseConfirm as RawMsgChannelCloseConfirm; use crate::core::ics04_channel::error::Error; -use crate::core::ics04_channel::packet::Sequence; use crate::core::ics24_host::identifier::{ChannelId, PortId}; use crate::proofs::Proofs; use crate::signer::Signer; @@ -11,31 +10,27 @@ use crate::tx_msg::Msg; pub const TYPE_URL: &str = "/ibc.core.channel.v1.MsgChannelCloseConfirm"; +/// /// Message definition for the second step in the channel close handshake (the `ChanCloseConfirm` /// datagram). +/// #[derive(Clone, Debug, PartialEq, Eq)] pub struct MsgChannelCloseConfirm { pub port_id: PortId, pub channel_id: ChannelId, pub proofs: Proofs, pub signer: Signer, - pub counterparty_upgrade_sequence: Sequence, + pub counterparty_upgrade_sequence: u64, } impl MsgChannelCloseConfirm { - pub fn new( - port_id: PortId, - channel_id: ChannelId, - proofs: Proofs, - signer: Signer, - counterparty_upgrade_sequence: Sequence, - ) -> Self { + pub fn new(port_id: PortId, channel_id: ChannelId, proofs: Proofs, signer: Signer) -> Self { Self { port_id, channel_id, proofs, signer, - counterparty_upgrade_sequence, + counterparty_upgrade_sequence: 0, } } } @@ -80,7 +75,7 @@ impl TryFrom for MsgChannelCloseConfirm { channel_id: raw_msg.channel_id.parse().map_err(Error::identifier)?, proofs, signer: raw_msg.signer.parse().map_err(Error::signer)?, - counterparty_upgrade_sequence: raw_msg.counterparty_upgrade_sequence.into(), + counterparty_upgrade_sequence: raw_msg.counterparty_upgrade_sequence, }) } } @@ -93,7 +88,7 @@ impl From for RawMsgChannelCloseConfirm { proof_init: domain_msg.proofs.object_proof().clone().into(), proof_height: Some(domain_msg.proofs.height().into()), signer: domain_msg.signer.to_string(), - counterparty_upgrade_sequence: domain_msg.counterparty_upgrade_sequence.into(), + counterparty_upgrade_sequence: domain_msg.counterparty_upgrade_sequence, } } } @@ -118,7 +113,7 @@ pub mod test_util { revision_height: proof_height, }), signer: get_dummy_bech32_account(), - counterparty_upgrade_sequence: 1, + counterparty_upgrade_sequence: 0, } } } diff --git a/crates/relayer-types/src/core/ics04_channel/msgs/timeout_on_close.rs b/crates/relayer-types/src/core/ics04_channel/msgs/timeout_on_close.rs index 17a47fe2a0..c83ee0417e 100644 --- a/crates/relayer-types/src/core/ics04_channel/msgs/timeout_on_close.rs +++ b/crates/relayer-types/src/core/ics04_channel/msgs/timeout_on_close.rs @@ -18,7 +18,7 @@ pub struct MsgTimeoutOnClose { pub next_sequence_recv: Sequence, pub proofs: Proofs, pub signer: Signer, - pub counterparty_upgrade_sequence: Sequence, + pub counterparty_upgrade_sequence: u64, } impl MsgTimeoutOnClose { @@ -27,7 +27,7 @@ impl MsgTimeoutOnClose { next_sequence_recv: Sequence, proofs: Proofs, signer: Signer, - counterparty_upgrade_sequence: Sequence, + counterparty_upgrade_sequence: u64, ) -> MsgTimeoutOnClose { Self { packet, @@ -89,7 +89,7 @@ impl TryFrom for MsgTimeoutOnClose { next_sequence_recv: Sequence::from(raw_msg.next_sequence_recv), signer: raw_msg.signer.parse().map_err(Error::signer)?, proofs, - counterparty_upgrade_sequence: raw_msg.counterparty_upgrade_sequence.into(), + counterparty_upgrade_sequence: raw_msg.counterparty_upgrade_sequence, }) } } @@ -106,7 +106,7 @@ impl From for RawMsgTimeoutOnClose { proof_height: Some(domain_msg.proofs.height().into()), next_sequence_recv: domain_msg.next_sequence_recv.into(), signer: domain_msg.signer.to_string(), - counterparty_upgrade_sequence: domain_msg.counterparty_upgrade_sequence.into(), + counterparty_upgrade_sequence: domain_msg.counterparty_upgrade_sequence, } } } diff --git a/crates/relayer-types/src/events.rs b/crates/relayer-types/src/events.rs index 92a03853bb..ea4156b7f6 100644 --- a/crates/relayer-types/src/events.rs +++ b/crates/relayer-types/src/events.rs @@ -66,9 +66,9 @@ define_error! { [ TraceError ] | _ | { "error decoding protobuf" }, - SubtleEncoding - [ TraceError ] - | _ | { "error decoding hex" }, + InvalidPacketData + { data: String } + | e | { format_args!("error decoding hex-encoded packet data: {}", e.data) }, MissingActionString | _ | { "missing action string" }, diff --git a/crates/relayer-types/src/mock/client_state.rs b/crates/relayer-types/src/mock/client_state.rs index c4081ab3bf..98917b0ef0 100644 --- a/crates/relayer-types/src/mock/client_state.rs +++ b/crates/relayer-types/src/mock/client_state.rs @@ -57,6 +57,8 @@ impl From for RawMockClientState { height: Some(value.header.height().into()), timestamp: value.header.timestamp.nanoseconds(), }), + frozen: false, + trusting_period: 14 * 24 * 60 * 60, } } } diff --git a/crates/relayer/Cargo.toml b/crates/relayer/Cargo.toml index 5ed88c5893..1dbe5efd62 100644 --- a/crates/relayer/Cargo.toml +++ b/crates/relayer/Cargo.toml @@ -20,7 +20,7 @@ default = ["flex-error/std", "flex-error/eyre_tracer"] telemetry = ["ibc-telemetry"] [dependencies] -ibc-proto = { version = "0.41.0", features = ["serde"] } +ibc-proto = { version = "0.42.0", features = ["serde"] } ibc-telemetry = { version = "0.27.0", path = "../telemetry", optional = true } ibc-relayer-types = { version = "0.27.0", path = "../relayer-types", features = ["mocks"] } @@ -28,7 +28,7 @@ subtle-encoding = "0.5" humantime-serde = "1.1.1" serde = "1.0" serde_derive = "1.0" -thiserror = "1.0.56" +thiserror = "1.0.57" toml = "0.8" tracing = "0.1.36" tokio = { version = "1.0", features = ["rt-multi-thread", "time", "sync"] } @@ -58,7 +58,7 @@ anyhow = "1.0" semver = "1.0" humantime = "2.1.0" regex = "1" -moka = { version = "0.12.0", features = ["sync"] } +moka = { version = "0.12.5", features = ["sync"] } uuid = { version = "1.7.0", features = ["v4"] } bs58 = "0.5.0" digest = "0.10.6" @@ -66,7 +66,7 @@ ed25519 = "2.2.2" ed25519-dalek = { version = "2.0.0", features = ["serde"] } ed25519-dalek-bip32 = "0.3.0" generic-array = "0.14.7" -secp256k1 = { version = "0.28.1", features = ["rand-std"] } +secp256k1 = { version = "0.28.2", features = ["rand-std"] } strum = { version = "0.25", features = ["derive"] } tokio-stream = "0.1.14" once_cell = "1.19.0" @@ -90,7 +90,7 @@ version = "0.34.0" features = ["secp256k1"] [dependencies.tendermint-proto] -version = "0.34.0" +version = "0.34.1" [dependencies.tendermint-rpc] version = "0.34.0" diff --git a/crates/relayer/src/chain/cosmos.rs b/crates/relayer/src/chain/cosmos.rs index f07579fc84..031fc81778 100644 --- a/crates/relayer/src/chain/cosmos.rs +++ b/crates/relayer/src/chain/cosmos.rs @@ -1066,17 +1066,17 @@ impl ChainEndpoint for CosmosSdkChain { /// further checks. fn health_check(&mut self) -> Result { if let Err(e) = do_health_check(self) { - warn!("Health checkup for chain '{}' failed", self.id()); - warn!(" Reason: {}", e.detail()); - warn!(" Some Hermes features may not work in this mode!"); + warn!("health check failed for chain '{}'", self.id()); + warn!("reason: {}", e.detail()); + warn!("some Hermes features may not work in this mode!"); return Ok(HealthCheck::Unhealthy(Box::new(e))); } if let Err(e) = self.validate_params() { - warn!("Hermes might be misconfigured for chain '{}'", self.id()); - warn!(" Reason: {}", e.detail()); - warn!(" Some Hermes features may not work in this mode!"); + warn!("found potential misconfiguration for chain '{}'", self.id()); + warn!("reason: {}", e.detail()); + warn!("some Hermes features may not work in this mode!"); return Ok(HealthCheck::Unhealthy(Box::new(e))); } @@ -2498,7 +2498,7 @@ fn do_health_check(chain: &CosmosSdkChain) -> Result<(), Error> { if !found_matching_denom { warn!( - "Chain '{}' has no minimum gas price of denomination '{}' \ + "chain '{}' has no minimum gas price of denomination '{}' \ that is strictly less than the `gas_price` specified for \ that chain in the Hermes configuration. \ This is usually a sign of misconfiguration, please check your chain and Hermes configurations", @@ -2507,7 +2507,7 @@ fn do_health_check(chain: &CosmosSdkChain) -> Result<(), Error> { } } else { warn!( - "Chain '{}' has no minimum gas price value configured for denomination '{}'. \ + "chain '{}' has no minimum gas price value configured for denomination '{}'. \ This is usually a sign of misconfiguration, please check your chain and \ relayer configurations", chain_id, relayer_gas_price.denom @@ -2517,7 +2517,7 @@ fn do_health_check(chain: &CosmosSdkChain) -> Result<(), Error> { let version_specs = chain.block_on(fetch_version_specs(&chain.config.id, &chain.grpc_addr))?; if let Err(diagnostic) = compatibility::run_diagnostic(&version_specs) { - return Err(Error::sdk_module_version( + return Err(Error::compat_check_failed( chain_id.clone(), grpc_address, diagnostic.to_string(), diff --git a/crates/relayer/src/chain/cosmos/compatibility.rs b/crates/relayer/src/chain/cosmos/compatibility.rs index 990fc0b60a..f997081d92 100644 --- a/crates/relayer/src/chain/cosmos/compatibility.rs +++ b/crates/relayer/src/chain/cosmos/compatibility.rs @@ -24,12 +24,18 @@ const IBC_GO_MODULE_VERSION_REQ: &str = ">=4.1.1, <9"; #[derive(Error, Debug)] pub enum Diagnostic { + #[error("SDK module version not found, required {requirements}")] + MissingSdkModuleVersion { requirements: String }, + + #[error("IBC-Go module version not found, required {requirements}")] + MissingIbcGoModuleVersion { requirements: String }, + #[error( "SDK module at version '{found}' does not meet compatibility requirements {requirements}" )] MismatchingSdkModuleVersion { requirements: String, found: String }, - #[error("Ibc-Go module at version '{found}' does not meet compatibility requirements {requirements}")] + #[error("IBC-Go module at version '{found}' does not meet compatibility requirements {requirements}")] MismatchingIbcGoModuleVersion { requirements: String, found: String }, } @@ -44,40 +50,44 @@ pub enum Diagnostic { /// Sdk module by name, as well as the constants /// [`SDK_MODULE_VERSION_REQ`] and [`IBC_GO_MODULE_VERSION_REQ`] /// for establishing compatibility requirements. -pub(crate) fn run_diagnostic(v: &version::Specs) -> Result<(), Diagnostic> { - debug!("running diagnostic on version info {}", v); +pub(crate) fn run_diagnostic(specs: &version::Specs) -> Result<(), Diagnostic> { + debug!("running diagnostic on version specs: {specs}"); - sdk_diagnostic(&v.cosmos_sdk)?; - ibc_go_diagnostic(v.ibc_go.as_ref())?; + sdk_diagnostic(specs.cosmos_sdk.as_ref())?; + ibc_go_diagnostic(specs.ibc_go.as_ref())?; Ok(()) } -fn sdk_diagnostic(version: &semver::Version) -> Result<(), Diagnostic> { +fn sdk_diagnostic(version: Option<&semver::Version>) -> Result<(), Diagnostic> { // Parse the SDK requirements into a semver let sdk_reqs = semver::VersionReq::parse(SDK_MODULE_VERSION_REQ) .expect("parsing the SDK module requirements into semver"); - // Finally, check the version requirements - match sdk_reqs.matches(version) { - true => Ok(()), - false => Err(Diagnostic::MismatchingSdkModuleVersion { + match version { + None => Err(Diagnostic::MissingSdkModuleVersion { requirements: SDK_MODULE_VERSION_REQ.to_string(), - found: version.to_string(), }), + + Some(version) => match sdk_reqs.matches(version) { + true => Ok(()), + false => Err(Diagnostic::MismatchingSdkModuleVersion { + requirements: SDK_MODULE_VERSION_REQ.to_string(), + found: version.to_string(), + }), + }, } } -fn ibc_go_diagnostic(version_info: Option<&semver::Version>) -> Result<(), Diagnostic> { +fn ibc_go_diagnostic(version: Option<&semver::Version>) -> Result<(), Diagnostic> { // Parse the IBC-go module requirements into a semver let ibc_reqs = semver::VersionReq::parse(IBC_GO_MODULE_VERSION_REQ) .expect("parsing the IBC-Go module requirements into semver"); - // Find the Ibc-Go module - match version_info { - // If binary lacks the ibc-go dependency it is _not_ an error, - // we support chains without the standalone ibc-go module. - None => Ok(()), + match version { + None => Err(Diagnostic::MissingIbcGoModuleVersion { + requirements: IBC_GO_MODULE_VERSION_REQ.to_string(), + }), Some(version) => match ibc_reqs.matches(version) { true => Ok(()), false => Err(Diagnostic::MismatchingIbcGoModuleVersion { diff --git a/crates/relayer/src/chain/cosmos/config.rs b/crates/relayer/src/chain/cosmos/config.rs index 57314bccf5..a81ede704a 100644 --- a/crates/relayer/src/chain/cosmos/config.rs +++ b/crates/relayer/src/chain/cosmos/config.rs @@ -106,6 +106,9 @@ pub struct CosmosSdkConfig { #[serde(default)] pub memo_prefix: Memo, + #[serde(default)] + pub memo_overwrite: Option, + // This is an undocumented and hidden config to make the relayer wait for // DeliverTX before sending the next transaction when sending messages in // multiple batches. We will instruct relayer operators to turn this on diff --git a/crates/relayer/src/chain/cosmos/estimate.rs b/crates/relayer/src/chain/cosmos/estimate.rs index 87a18a26b4..461ab6ae14 100644 --- a/crates/relayer/src/chain/cosmos/estimate.rs +++ b/crates/relayer/src/chain/cosmos/estimate.rs @@ -14,6 +14,7 @@ use crate::chain::cosmos::types::gas::GasConfig; use crate::config::types::Memo; use crate::error::Error; use crate::keyring::Secp256k1KeyPair; +use crate::telemetry; use crate::util::pretty::PrettyFee; pub async fn estimate_tx_fees( @@ -51,6 +52,7 @@ pub async fn estimate_tx_fees( &config.rpc_address, &config.chain_id, tx, + account, ) .await?; @@ -63,6 +65,7 @@ async fn estimate_fee_with_tx( rpc_address: &Url, chain_id: &ChainId, tx: Tx, + account: &Account, ) -> Result { let estimated_gas = { crate::time!( @@ -72,7 +75,7 @@ async fn estimate_fee_with_tx( } ); - estimate_gas_with_tx(gas_config, grpc_address, tx).await + estimate_gas_with_tx(gas_config, grpc_address, tx, account).await }?; if estimated_gas > gas_config.max_gas { @@ -112,6 +115,7 @@ async fn estimate_gas_with_tx( gas_config: &GasConfig, grpc_address: &Uri, tx: Tx, + account: &Account, ) -> Result { let simulated_gas = send_tx_simulate(grpc_address, tx) .await @@ -147,6 +151,13 @@ async fn estimate_gas_with_tx( e.detail() ); + telemetry!( + simulate_errors, + &account.address.to_string(), + true, + get_error_text(&e), + ); + Ok(gas_config.default_gas) } @@ -155,6 +166,14 @@ async fn estimate_gas_with_tx( "failed to simulate tx. propagating error to caller: {}", e.detail() ); + + telemetry!( + simulate_errors, + &account.address.to_string(), + false, + get_error_text(&e), + ); + // Propagate the error, the retrying mechanism at caller may catch & retry. Err(e) } @@ -171,7 +190,17 @@ fn can_recover_from_simulation_failure(e: &Error) -> bool { detail.is_client_state_height_too_low() || detail.is_account_sequence_mismatch_that_can_be_ignored() || detail.is_out_of_order_packet_sequence_error() + || detail.is_empty_tx_error() } _ => false, } } + +fn get_error_text(e: &Error) -> String { + use crate::error::ErrorDetail::*; + + match e.detail() { + GrpcStatus(detail) => detail.status.code().to_string(), + detail => detail.to_string(), + } +} diff --git a/crates/relayer/src/chain/cosmos/query/custom.rs b/crates/relayer/src/chain/cosmos/query/custom.rs index e842510b1b..80740aa0dd 100644 --- a/crates/relayer/src/chain/cosmos/query/custom.rs +++ b/crates/relayer/src/chain/cosmos/query/custom.rs @@ -10,7 +10,7 @@ pub async fn cross_chain_query_via_rpc( client: &HttpClient, cross_chain_query_request: CrossChainQueryRequest, ) -> Result { - let hex_decoded_request = hex::decode(cross_chain_query_request.request) + let hex_decoded_request = hex::decode(cross_chain_query_request.request.to_lowercase()) .map_err(|_| Error::ics31(CrossChainQueryError::parse()))?; let response = client diff --git a/crates/relayer/src/chain/cosmos/types/events/channel.rs b/crates/relayer/src/chain/cosmos/types/events/channel.rs index fa09493913..442d73a21d 100644 --- a/crates/relayer/src/chain/cosmos/types/events/channel.rs +++ b/crates/relayer/src/chain/cosmos/types/events/channel.rs @@ -66,11 +66,7 @@ macro_rules! impl_try_from_raw_obj_for_packet { type Error = EventError; fn try_from(obj: RawObject<'_>) -> Result { - let data_str: String = extract_attribute(&obj, &format!("{}.{}", obj.action, PKT_DATA_ATTRIBUTE_KEY))?; - - let mut packet = Packet::try_from(obj)?; - packet.data = Vec::from(data_str.as_str().as_bytes()); - + let packet = Packet::try_from(obj)?; Ok(Self { packet }) } })+ @@ -89,13 +85,10 @@ impl TryFrom> for WriteAcknowledgement { type Error = EventError; fn try_from(obj: RawObject<'_>) -> Result { - let data_str: String = - extract_attribute(&obj, &format!("{}.{}", obj.action, PKT_DATA_ATTRIBUTE_KEY))?; let ack = extract_attribute(&obj, &format!("{}.{}", obj.action, PKT_ACK_ATTRIBUTE_KEY))? .into_bytes(); - let mut packet = Packet::try_from(obj)?; - packet.data = Vec::from(data_str.as_bytes()); + let packet = Packet::try_from(obj)?; Ok(Self { packet, ack }) } @@ -117,7 +110,14 @@ pub fn parse_timeout_height(s: &str) -> Result { impl TryFrom> for Packet { type Error = EventError; + fn try_from(obj: RawObject<'_>) -> Result { + let data_str = + extract_attribute(&obj, &format!("{}.{}", obj.action, PKT_DATA_ATTRIBUTE_KEY))?; + + let data = hex::decode(data_str.to_lowercase()) + .map_err(|_| EventError::invalid_packet_data(data_str))?; + Ok(Packet { sequence: extract_attribute( &obj, @@ -125,31 +125,37 @@ impl TryFrom> for Packet { )? .parse() .map_err(EventError::channel)?, + source_port: extract_attribute( &obj, &format!("{}.{}", obj.action, PKT_SRC_PORT_ATTRIBUTE_KEY), )? .parse() .map_err(EventError::parse)?, + source_channel: extract_attribute( &obj, &format!("{}.{}", obj.action, PKT_SRC_CHANNEL_ATTRIBUTE_KEY), )? .parse() .map_err(EventError::parse)?, + destination_port: extract_attribute( &obj, &format!("{}.{}", obj.action, PKT_DST_PORT_ATTRIBUTE_KEY), )? .parse() .map_err(EventError::parse)?, + destination_channel: extract_attribute( &obj, &format!("{}.{}", obj.action, PKT_DST_CHANNEL_ATTRIBUTE_KEY), )? .parse() .map_err(EventError::parse)?, - data: vec![], + + data, + timeout_height: { let timeout_height_str = extract_attribute( &obj, diff --git a/crates/relayer/src/chain/cosmos/version.rs b/crates/relayer/src/chain/cosmos/version.rs index e6987eebb5..a75c4ed822 100644 --- a/crates/relayer/src/chain/cosmos/version.rs +++ b/crates/relayer/src/chain/cosmos/version.rs @@ -22,20 +22,15 @@ use ibc_proto::cosmos::base::tendermint::v1beta1::VersionInfo; /// sum: "h1:yaD4PyOx0LnyfiWasC5egg1U76lT83GRxjJjupPo7Gk=", /// }, /// ``` -const SDK_MODULE_NAME: &str = "cosmos/cosmos-sdk"; -const IBC_GO_MODULE_NAME: &str = "cosmos/ibc-go"; -const TENDERMINT_MODULE_NAME: &str = "tendermint/tendermint"; -const COMET_MODULE_NAME: &str = "cometbft/cometbft"; +const SDK_MODULE_NAME: &str = "github.com/cosmos/cosmos-sdk"; +const IBC_GO_MODULE_NAME: &str = "github.com/cosmos/ibc-go"; +const TENDERMINT_MODULE_NAME: &str = "github.com/tendermint/tendermint"; +const COMET_MODULE_NAME: &str = "github.com/cometbft/cometbft"; -/// Captures the version(s) specification of different -/// modules of a network. -/// -/// Assumes that the network runs on Cosmos SDK. -/// Stores both the SDK version as well as -/// the IBC-go module version (if existing). +/// Captures the version(s) specification of different modules of a network. #[derive(Debug)] pub struct Specs { - pub cosmos_sdk: semver::Version, + pub cosmos_sdk: Option, pub ibc_go: Option, pub tendermint: Option, pub comet: Option, @@ -43,6 +38,12 @@ pub struct Specs { impl Display for Specs { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { + let cosmos_sdk = self + .cosmos_sdk + .as_ref() + .map(|v| v.to_string()) + .unwrap_or_else(|| "UNKNOWN".to_string()); + let ibc_go = self .ibc_go .as_ref() @@ -64,20 +65,13 @@ impl Display for Specs { write!( f, "Cosmos SDK {}, IBC-Go {}, Tendermint {}, CometBFT {}", - self.cosmos_sdk, ibc_go, tendermint, comet + cosmos_sdk, ibc_go, tendermint, comet ) } } define_error! { Error { - SdkModuleNotFound - { - address: String, - app: AppInfo, - } - |e| { format!("failed to find the SDK module dependency ('{}') for application {}", e.address, e.app) }, - ConsensusModuleNotFound { tendermint: String, @@ -121,7 +115,7 @@ impl TryFrom for Specs { application = %raw_version.app_name, version = %raw_version.version, git_commit = %raw_version.git_commit, - sdk_version = %sdk_version, + sdk_version = ?sdk_version, ibc_go_status = ?ibc_go_version, tendermint_version = ?tendermint_version, comet_version = ?comet_version, @@ -137,33 +131,8 @@ impl TryFrom for Specs { } } -fn parse_sdk_version(version_info: &VersionInfo) -> Result { - let module = version_info - .build_deps - .iter() - .find(|&m| m.path.contains(SDK_MODULE_NAME)) - .ok_or_else(|| { - Error::sdk_module_not_found(SDK_MODULE_NAME.to_string(), AppInfo::from(version_info)) - })?; - - // The raw version number has a leading 'v', trim it out; - let plain_version = module.version.trim_start_matches('v'); - - // Parse the module version - let mut version = semver::Version::parse(plain_version).map_err(|e| { - Error::version_parsing_failed( - module.path.clone(), - module.version.clone(), - e.to_string(), - AppInfo::from(version_info), - ) - })?; - - // Remove the pre-release version to ensure we treat pre-releases of the SDK - // as their normal version, eg. 0.42.0-rc2 should satisfy >=0.41.3, <= 0.42.6. - version.pre = semver::Prerelease::EMPTY; - - Ok(version) +fn parse_sdk_version(version_info: &VersionInfo) -> Result, Error> { + parse_optional_version(version_info, SDK_MODULE_NAME) } fn parse_ibc_go_version(version_info: &VersionInfo) -> Result, Error> { @@ -185,7 +154,7 @@ fn parse_optional_version( match version_info .build_deps .iter() - .find(|&m| m.path.contains(module_name)) + .find(|&m| m.path == module_name) { None => Ok(None), Some(module) => { diff --git a/crates/relayer/src/chain/tracking.rs b/crates/relayer/src/chain/tracking.rs index 62f89ca096..589ad305f2 100644 --- a/crates/relayer/src/chain/tracking.rs +++ b/crates/relayer/src/chain/tracking.rs @@ -15,7 +15,7 @@ pub enum TrackingId { /// the CLI or during packet clearing. Static(&'static str), /// Random identifier used to track latency of packet clearing. - ClearedUuid(Uuid), + PacketClearing(Uuid), } impl TrackingId { @@ -29,8 +29,13 @@ impl TrackingId { Self::Static(s) } - pub fn new_cleared_uuid() -> Self { - Self::ClearedUuid(Uuid::new_v4()) + pub fn new_packet_clearing() -> Self { + Self::PacketClearing(Uuid::new_v4()) + } + + /// Indicates whether a packet clearing process is currently in-progress. + pub fn is_clearing(&self) -> bool { + matches!(self, Self::PacketClearing(_)) } } @@ -43,7 +48,7 @@ impl Display for TrackingId { s.fmt(f) } TrackingId::Static(s) => s.fmt(f), - TrackingId::ClearedUuid(u) => { + TrackingId::PacketClearing(u) => { let mut uuid = "cleared/".to_owned(); let mut s = u.to_string(); s.truncate(8); diff --git a/crates/relayer/src/channel.rs b/crates/relayer/src/channel.rs index 9b38a3f7a1..3f24009692 100644 --- a/crates/relayer/src/channel.rs +++ b/crates/relayer/src/channel.rs @@ -3,7 +3,6 @@ use ibc_proto::ibc::core::channel::v1::QueryUpgradeRequest; use ibc_relayer_types::core::ics04_channel::msgs::chan_upgrade_ack::MsgChannelUpgradeAck; use ibc_relayer_types::core::ics04_channel::msgs::chan_upgrade_confirm::MsgChannelUpgradeConfirm; use ibc_relayer_types::core::ics04_channel::msgs::chan_upgrade_open::MsgChannelUpgradeOpen; -use ibc_relayer_types::core::ics04_channel::packet::Sequence; use ibc_relayer_types::core::ics04_channel::upgrade_fields::UpgradeFields; use core::fmt::{Display, Error as FmtError, Formatter}; @@ -899,7 +898,7 @@ impl Channel { counterparty, vec![self.dst_connection_id().clone()], version, - Sequence::from(0), + 0, ); // Build the domain type message @@ -979,7 +978,7 @@ impl Channel { counterparty, vec![self.dst_connection_id().clone()], Version::empty(), - Sequence::from(0), + 0, ); // Retrieve existing channel @@ -1071,7 +1070,7 @@ impl Channel { counterparty, vec![self.dst_connection_id().clone()], version, - Sequence::from(0), + 0, ); // Get signer @@ -1546,10 +1545,6 @@ impl Channel { } if let Some(new_ordering) = new_ordering { - if new_ordering == Ordering::Uninitialized || new_ordering > channel_end.ordering { - return Err(ChannelError::invalid_channel_upgrade_ordering()); - } - channel_end.ordering = new_ordering; } @@ -1735,7 +1730,7 @@ impl Channel { channel_id: dst_channel_id.clone(), proposed_upgrade_connection_hops: dst_channel_end.connection_hops, counterparty_upgrade_fields: upgrade.fields, - counterparty_upgrade_sequence: channel_end.upgrade_sequence, + counterparty_upgrade_sequence: channel_end.upgrade_sequence.into(), proof_channel: src_proof.object_proof().clone(), proof_upgrade, proof_height: src_proof.height(), @@ -2055,7 +2050,7 @@ impl Channel { port_id: dst_port_id.clone(), channel_id: dst_channel_id.clone(), counterparty_channel_state: src_channel_end.state, - counterparty_upgrade_sequence, + counterparty_upgrade_sequence: counterparty_upgrade_sequence.into(), proof_channel: proofs.object_proof().clone(), proof_height: proofs.height(), signer, diff --git a/crates/relayer/src/config.rs b/crates/relayer/src/config.rs index 2f335e13d0..ec0afdaf34 100644 --- a/crates/relayer/src/config.rs +++ b/crates/relayer/src/config.rs @@ -414,6 +414,9 @@ pub struct Packets { pub ics20_max_memo_size: Ics20FieldSizeLimit, #[serde(default = "default::ics20_max_receiver_size")] pub ics20_max_receiver_size: Ics20FieldSizeLimit, + + #[serde(skip)] + pub force_disable_clear_on_start: bool, } impl Default for Packets { @@ -426,6 +429,7 @@ impl Default for Packets { auto_register_counterparty_payee: default::auto_register_counterparty_payee(), ics20_max_memo_size: default::ics20_max_memo_size(), ics20_max_receiver_size: default::ics20_max_receiver_size(), + force_disable_clear_on_start: false, } } } diff --git a/crates/relayer/src/error.rs b/crates/relayer/src/error.rs index 857c0057ea..3392a0b452 100644 --- a/crates/relayer/src/error.rs +++ b/crates/relayer/src/error.rs @@ -494,14 +494,14 @@ define_error! { format!("semantic config validation failed for option `gas_multiplier` of chain '{}', reason: gas multiplier ({}) is smaller than `1.1`, which could trigger gas fee errors in production", e.chain_id, e.gas_multiplier) }, - SdkModuleVersion + CompatCheckFailed { chain_id: ChainId, address: String, cause: String } |e| { - format!("Hermes health check failed while verifying the application compatibility for chain {0}:{1}; caused by: {2}", + format!("compatibility check failed for chain '{0}' at '{1}': {2}", e.chain_id, e.address, e.cause) }, @@ -717,6 +717,14 @@ impl GrpcStatusSubdetail { Some((expected, got)) => expected < got, } } + + /// Check whether this gRPC error message contains the string "invalid empty tx". + /// + /// ## Note + /// This error may happen for older chains that does not properly support simulation. + pub fn is_empty_tx_error(&self) -> bool { + self.status.message().contains("invalid empty tx") + } } /// Assumes that the cosmos-sdk account sequence mismatch error message, that may be seen diff --git a/crates/relayer/src/event.rs b/crates/relayer/src/event.rs index c4498389dd..adad3596d5 100644 --- a/crates/relayer/src/event.rs +++ b/crates/relayer/src/event.rs @@ -1,11 +1,18 @@ use core::fmt::{Display, Error as FmtError, Formatter}; use serde::Serialize; use std::str::FromStr; +use subtle_encoding::hex; use tendermint::abci::Event as AbciEvent; use ibc_relayer_types::{ applications::ics29_fee::events::{DistributeFeePacket, IncentivizedPacket}, applications::ics31_icq::events::CrossChainQueryPacket, + core::ics02_client::{ + error::Error as ClientError, + events::{self as client_events, Attributes as ClientAttributes, HEADER_ATTRIBUTE_KEY}, + header::{decode_header, AnyHeader}, + height::HeightErrorDetail, + }, core::ics03_connection::{ error::Error as ConnectionError, events::{self as connection_events, Attributes as ConnectionAttributes}, @@ -20,12 +27,6 @@ use ibc_relayer_types::{ timeout::TimeoutHeight, }, core::{ - ics02_client::{ - error::Error as ClientError, - events::{self as client_events, Attributes as ClientAttributes, HEADER_ATTRIBUTE_KEY}, - header::{decode_header, AnyHeader}, - height::HeightErrorDetail, - }, ics04_channel::{channel::Ordering, packet::Sequence, version::Version}, ics24_host::identifier::ConnectionId, }, @@ -402,8 +403,8 @@ fn client_extract_attributes_from_tx(event: &AbciEvent) -> Result Result { for tag in &event.attributes { if tag.key == HEADER_ATTRIBUTE_KEY { - let header_bytes = - hex::decode(&tag.value).map_err(|_| ClientError::malformed_header())?; + let header_bytes = hex::decode(tag.value.to_lowercase()) + .map_err(|_| ClientError::malformed_header())?; return decode_header(&header_bytes); } } @@ -524,9 +525,11 @@ pub fn extract_packet_and_write_ack_from_tx( ) -> Result<(Packet, Vec), ChannelError> { let mut packet = Packet::default(); let mut write_ack: Vec = Vec::new(); + for tag in &event.attributes { let key = tag.key.as_str(); let value = tag.value.as_str(); + match key { channel_events::PKT_SRC_PORT_ATTRIBUTE_KEY => { packet.source_port = value.parse().map_err(ChannelError::identifier)?; @@ -553,7 +556,8 @@ pub fn extract_packet_and_write_ack_from_tx( packet.timeout_timestamp = value.parse().unwrap(); } channel_events::PKT_DATA_ATTRIBUTE_KEY => { - packet.data = Vec::from(value.as_bytes()); + packet.data = hex::decode(value.to_lowercase()) + .map_err(|_| ChannelError::invalid_packet_data(value.to_string()))?; } channel_events::PKT_ACK_ATTRIBUTE_KEY => { write_ack = Vec::from(value.as_bytes()); diff --git a/crates/relayer/src/event/source/websocket/extract.rs b/crates/relayer/src/event/source/websocket/extract.rs index 66301ff192..30addd9653 100644 --- a/crates/relayer/src/event/source/websocket/extract.rs +++ b/crates/relayer/src/event/source/websocket/extract.rs @@ -321,7 +321,7 @@ fn extract_block_events( ); append_events::( &mut events, - extract_events(height, block_events, "send_packet", "packet_data"), + extract_events(height, block_events, "send_packet", "packet_data_hex"), height, ); append_events::( diff --git a/crates/relayer/src/link/error.rs b/crates/relayer/src/link/error.rs index adf8142ec8..694bc22f70 100644 --- a/crates/relayer/src/link/error.rs +++ b/crates/relayer/src/link/error.rs @@ -73,7 +73,7 @@ define_error! { Send { event: IbcEvent } |e| { - format!("chain error when sending messages: {0}", e.event) + format!("failed to send message: {0}", e.event) }, MissingChannelId diff --git a/crates/relayer/src/link/relay_path.rs b/crates/relayer/src/link/relay_path.rs index b73ce1363e..2cbc3b85e7 100644 --- a/crates/relayer/src/link/relay_path.rs +++ b/crates/relayer/src/link/relay_path.rs @@ -429,7 +429,7 @@ impl RelayPath { fn relay_pending_packets(&self, height: Option) -> Result<(), LinkError> { let _span = span!(Level::ERROR, "relay_pending_packets", ?height).entered(); - let tracking_id = TrackingId::new_cleared_uuid(); + let tracking_id = TrackingId::new_packet_clearing(); telemetry!(received_event_batch, tracking_id); let src_config = self.src_chain().config().map_err(LinkError::relayer)?; @@ -712,12 +712,12 @@ impl RelayPath { return Ok(reply); } - Err(LinkError(error::LinkErrorDetail::Send(e), _)) => { - // This error means we could retry - error!("error {}", e.event); + Err(LinkError(error::LinkErrorDetail::Send(_), _)) => { if i + 1 == MAX_RETRIES { - error!("{}/{} retries exhausted. giving up", i + 1, MAX_RETRIES) + error!("{}/{} retries exhausted, giving up", i + 1, MAX_RETRIES) } else { + debug!("{}/{} retries exhausted, retrying with newly-generated operational data", i + 1, MAX_RETRIES); + // If we haven't exhausted all retries, regenerate the op. data & retry match self.regenerate_operational_data(odata.clone()) { None => return Ok(S::Reply::empty()), // Nothing to retry diff --git a/crates/relayer/src/link/relay_sender.rs b/crates/relayer/src/link/relay_sender.rs index c4d29fe91a..a8c70cb9f0 100644 --- a/crates/relayer/src/link/relay_sender.rs +++ b/crates/relayer/src/link/relay_sender.rs @@ -95,10 +95,11 @@ impl Submit for AsyncSender { type Reply = AsyncReply; fn submit(target: &impl ChainHandle, msgs: TrackedMsgs) -> Result { - let a = target + let responses = target .send_messages_and_wait_check_tx(msgs) .map_err(LinkError::relayer)?; - let reply = AsyncReply { responses: a }; + + let reply = AsyncReply { responses }; // Note: There may be errors in the reply, for example: // `Response { code: Err(11), data: Data([]), log: Log("Too much gas wanted: 35000000, maximum is 25000000: out of gas")` diff --git a/crates/relayer/src/upgrade_chain.rs b/crates/relayer/src/upgrade_chain.rs index 3c6b407f5c..81ec1bc579 100644 --- a/crates/relayer/src/upgrade_chain.rs +++ b/crates/relayer/src/upgrade_chain.rs @@ -17,6 +17,7 @@ use ibc_relayer_types::clients::ics07_tendermint::client_state::UpgradeOptions; use ibc_relayer_types::core::ics02_client::client_state::ClientState; use ibc_relayer_types::core::ics24_host::identifier::{ChainId, ClientId}; use ibc_relayer_types::{downcast, Height}; +use tracing::warn; use crate::chain::handle::ChainHandle; use crate::chain::requests::{IncludeProof, QueryClientStateRequest, QueryHeight}; @@ -96,22 +97,32 @@ pub fn build_and_send_ibc_upgrade_proposal( /// Looks at the ibc-go version to determine if the legacy `UpgradeProposal` message /// or if the newer `MsgIBCSoftwareUpdate` message should be used to upgrade the chain. /// If the ibc-go version returned isn't reliable, a deprecated version, then the version -/// of Cosmos SDK is used. +/// of Cosmos SDK is used, if any. If there is no SDK version, we assume that the legacy upgrade is required. pub fn requires_legacy_upgrade_proposal(dst_chain: impl ChainHandle) -> bool { - let version_specs = dst_chain.version_specs().unwrap(); + let Ok(version_specs) = dst_chain.version_specs() else { + warn!("failed to get version specs, assuming legacy upgrade proposal is required"); + return true; + }; + + let sdk_before_50 = version_specs + .cosmos_sdk + .as_ref() + .map(|s| s.minor < 50) + .unwrap_or(true); + match version_specs.ibc_go { + None => sdk_before_50, Some(ibc_version) => { // Some ibc-go simapps return unreliable ibc-go versions, such as simapp v8.0.0 // returns version v1.0.0. So if the ibc-go version matches which is not maintained // anymore, use the Cosmos SDK version to determine if the legacy upgrade proposal // has to be used if ibc_version.major < 4 { - version_specs.cosmos_sdk.minor < 50 + sdk_before_50 } else { ibc_version.major < 8 } } - None => version_specs.cosmos_sdk.minor < 50, } } diff --git a/crates/relayer/src/util/profiling.rs b/crates/relayer/src/util/profiling.rs index c6310f95fc..a6371e53fe 100644 --- a/crates/relayer/src/util/profiling.rs +++ b/crates/relayer/src/util/profiling.rs @@ -98,7 +98,6 @@ impl Drop for Timer { pub fn open_or_create_profile_file(file_name: &Path) { let file = OpenOptions::new() - .write(true) .append(true) .create(true) .open(file_name) diff --git a/crates/relayer/src/worker.rs b/crates/relayer/src/worker.rs index 400ff811af..40f73b67de 100644 --- a/crates/relayer/src/worker.rs +++ b/crates/relayer/src/worker.rs @@ -128,7 +128,7 @@ pub fn spawn_worker_tasks( Ok(link) => { let channel_ordering = link.a_to_b.channel().ordering; let should_clear_on_start = - packets_config.clear_on_start || channel_ordering == Ordering::Ordered; + should_clear_on_start(&packets_config, channel_ordering); let (cmd_tx, cmd_rx) = crossbeam_channel::unbounded(); let link = Arc::new(Mutex::new(link)); @@ -217,3 +217,11 @@ pub fn spawn_worker_tasks( WorkerHandle::new(id, object, data, cmd_tx, task_handles) } + +fn should_clear_on_start(config: &crate::config::Packets, channel_ordering: Ordering) -> bool { + if config.force_disable_clear_on_start { + false + } else { + config.clear_on_start || channel_ordering == Ordering::Ordered + } +} diff --git a/crates/relayer/src/worker/packet.rs b/crates/relayer/src/worker/packet.rs index 2eb85f633c..5daf122466 100644 --- a/crates/relayer/src/worker/packet.rs +++ b/crates/relayer/src/worker/packet.rs @@ -17,14 +17,17 @@ use ibc_proto::ibc::apps::fee::v1::{IdentifiedPacketFees, QueryIncentivizedPacke use ibc_proto::ibc::core::channel::v1::PacketId; use ibc_relayer_types::applications::ics29_fee::events::IncentivizedPacket; use ibc_relayer_types::applications::transfer::{Amount, Coin, RawCoin}; +use ibc_relayer_types::core::ics04_channel::channel::Ordering; use ibc_relayer_types::core::ics04_channel::events::WriteAcknowledgement; use ibc_relayer_types::core::ics04_channel::packet::Sequence; use ibc_relayer_types::events::{IbcEvent, IbcEventType}; use ibc_relayer_types::Height; use crate::chain::handle::ChainHandle; +use crate::chain::requests::QueryHeight; use crate::config::filter::FeePolicy; use crate::event::source::EventBatch; +use crate::event::IbcEventWithHeight; use crate::foreign_client::HasExpiredOrFrozenError; use crate::link::Resubmit; use crate::link::{error::LinkError, Link}; @@ -204,6 +207,24 @@ fn handle_packet_cmd( ) -> Result<(), TaskError> { // Handle packet clearing which is triggered from a command let (do_clear, maybe_height) = match &cmd { + WorkerCmd::IbcEvents { batch } if link.a_to_b.channel().ordering == Ordering::Ordered => { + let lowest_sequence = lowest_sequence(&batch.events); + + let next_sequence = query_next_sequence_receive( + link.a_to_b.dst_chain(), + link.a_to_b.dst_port_id(), + link.a_to_b.dst_channel_id(), + QueryHeight::Specific(batch.height), + ) + .ok(); + + if *should_clear_on_start || next_sequence < lowest_sequence { + (true, Some(batch.height)) + } else { + (false, None) + } + } + WorkerCmd::IbcEvents { batch } => { if *should_clear_on_start { (true, Some(batch.height)) @@ -439,6 +460,34 @@ fn handle_execute_schedule( Ok(()) } +fn query_next_sequence_receive( + chain: &Chain, + port_id: &PortId, + channel_id: &ChannelId, + height: QueryHeight, +) -> Result { + use crate::chain::requests::{IncludeProof, QueryNextSequenceReceiveRequest}; + + chain + .query_next_sequence_receive( + QueryNextSequenceReceiveRequest { + port_id: port_id.clone(), + channel_id: channel_id.clone(), + height, + }, + IncludeProof::No, + ) + .map(|(seq, _height)| seq) + .map_err(|e| LinkError::query(chain.id(), e)) +} + +fn lowest_sequence(events: &[IbcEventWithHeight]) -> Option { + events + .iter() + .flat_map(|event| event.event.packet().map(|p| p.sequence)) + .min() +} + #[cfg(feature = "telemetry")] use crate::link::RelaySummary; diff --git a/crates/telemetry/Cargo.toml b/crates/telemetry/Cargo.toml index 60d3357d71..16706aea52 100644 --- a/crates/telemetry/Cargo.toml +++ b/crates/telemetry/Cargo.toml @@ -19,7 +19,7 @@ once_cell = "1.19.0" opentelemetry = { version = "0.19.0", features = ["metrics"] } opentelemetry-prometheus = "0.12.0" prometheus = "0.13.2" -moka = { version = "0.12.0", features = ["sync"] } +moka = { version = "0.12.5", features = ["sync"] } dashmap = "5.4.0" serde_json = "1.0.111" serde = "1.0.195" diff --git a/crates/telemetry/src/state.rs b/crates/telemetry/src/state.rs index bf48381d1f..465a367044 100644 --- a/crates/telemetry/src/state.rs +++ b/crates/telemetry/src/state.rs @@ -201,6 +201,9 @@ pub struct TelemetryState { /// Number of errors observed by Hermes when broadcasting a Tx broadcast_errors: Counter, + /// Number of errors observed by Hermes when simulating a Tx + simulate_errors: Counter, + /// The EIP-1559 base fee queried dynamic_gas_queried_fees: ObservableGauge, @@ -394,6 +397,13 @@ impl TelemetryState { ) .init(), + simulate_errors: meter + .u64_counter("simulate_errors") + .with_description( + "Number of errors observed by Hermes when simulating a Tx", + ) + .init(), + dynamic_gas_queried_fees: meter .f64_observable_gauge("dynamic_gas_queried_fees") .with_description("The EIP-1559 base fee queried") @@ -1160,6 +1170,20 @@ impl TelemetryState { self.broadcast_errors.add(&cx, 1, labels); } + /// Add an error and its description to the list of errors observed after simulating + /// a Tx with a specific account. + pub fn simulate_errors(&self, address: &String, recoverable: bool, error_description: String) { + let cx = Context::current(); + + let labels = &[ + KeyValue::new("account", address.to_string()), + KeyValue::new("recoverable", recoverable.to_string()), + KeyValue::new("error_description", error_description.to_owned()), + ]; + + self.simulate_errors.add(&cx, 1, labels); + } + pub fn dynamic_gas_queried_fees(&self, chain_id: &ChainId, amount: f64) { let cx = Context::current(); diff --git a/guide/src/documentation/telemetry/operators.md b/guide/src/documentation/telemetry/operators.md index 2496b6e91a..dd0d137d2e 100644 --- a/guide/src/documentation/telemetry/operators.md +++ b/guide/src/documentation/telemetry/operators.md @@ -142,6 +142,7 @@ If this metric is increasing, it signals that the packet queue is increasing and | `cleared_send_packet_count_total`  | Number of SendPacket events received during the initial and periodic clearing, per chain, counterparty chain, channel and port | `u64` Counter | Packet workers enabled, and periodic packet clearing or clear on start enabled | | `cleared_acknowledgment_count_total` | Number of WriteAcknowledgement events received during the initial and periodic clearing, per chain, counterparty chain, channel and port | `u64` Counter | Packet workers enabled, and periodic packet clearing or clear on start enabled | | `broadcast_errors_total` | Number of errors observed by Hermes when broadcasting a Tx, per error type and account | `u64` Counter | Packet workers enabled | +| `simulate_errors_total` | Number of errors observed by Hermes when simulating a Tx, per error type, account and whether the error is recoverable or not | `u64` Counter | Packet workers enabled | | `filtered_packets` | Number of ICS-20 packets filtered because the memo and/or the receiver fields were exceeding the configured limits | `u64` Counter | Packet workers enabled, and `ics20_max_memo_size` and/or `ics20_max_receiver_size` enabled | Notes: diff --git a/tools/integration-test/Cargo.toml b/tools/integration-test/Cargo.toml index b6bf34be3e..ff9d416b45 100644 --- a/tools/integration-test/Cargo.toml +++ b/tools/integration-test/Cargo.toml @@ -50,7 +50,7 @@ name = "test_setup_with_binary_channel" doc = true [dev-dependencies] -tempfile = "3.6.0" +tempfile = "3.10.1" [dependencies.tendermint] version = "0.34.0" diff --git a/tools/integration-test/src/tests/ica.rs b/tools/integration-test/src/tests/ica.rs index 03d367b675..024e82a4d9 100644 --- a/tools/integration-test/src/tests/ica.rs +++ b/tools/integration-test/src/tests/ica.rs @@ -2,13 +2,10 @@ use std::collections::HashMap; use std::str::FromStr; use ibc_relayer::chain::handle::ChainHandle; -use ibc_relayer::chain::tracking::TrackedMsgs; use ibc_relayer::config::{ filter::{ChannelFilters, ChannelPolicy, FilterPattern}, ChainConfig, PacketFilter, }; -use ibc_relayer::event::IbcEventWithHeight; -use ibc_relayer_types::applications::ics27_ica::msgs::send_tx::MsgSendTx; use ibc_relayer_types::applications::ics27_ica::packet_data::InterchainAccountPacketData; use ibc_relayer_types::applications::{ ics27_ica::cosmos_tx::CosmosTx, @@ -26,6 +23,7 @@ use ibc_test_framework::prelude::*; use ibc_test_framework::relayer::channel::{ assert_eventually_channel_closed, assert_eventually_channel_established, query_channel_end, }; +use ibc_test_framework::util::interchain_security::interchain_send_tx; #[test] fn test_ica_filter_default() -> Result<(), Error> { @@ -187,6 +185,7 @@ impl BinaryConnectionTest for IcaFilterTestAllow { Ok(()) } } + pub struct IcaFilterTestDeny; impl TestOverrides for IcaFilterTestDeny { @@ -373,26 +372,3 @@ impl BinaryConnectionTest for ICACloseChannelTest { }) } } - -fn interchain_send_tx( - chain: &ChainA, - from: &Signer, - connection: &ConnectionId, - msg: InterchainAccountPacketData, - relative_timeout: Timestamp, -) -> Result, Error> { - let msg = MsgSendTx { - owner: from.clone(), - connection_id: connection.clone(), - packet_data: msg, - relative_timeout, - }; - - let msg_any = msg.to_any(); - - let tm = TrackedMsgs::new_static(vec![msg_any], "SendTx"); - - chain - .send_messages_and_wait_commit(tm) - .map_err(Error::relayer) -} diff --git a/tools/integration-test/src/tests/interchain_security/ica_ordered_channel.rs b/tools/integration-test/src/tests/interchain_security/ica_ordered_channel.rs new file mode 100644 index 0000000000..56a0075ff3 --- /dev/null +++ b/tools/integration-test/src/tests/interchain_security/ica_ordered_channel.rs @@ -0,0 +1,226 @@ +//! Verifies the behaviour of ordered channels with Interchain Accounts. +//! +//! In order to ensure that ordered channels correctly clear packets on ICA +//! channels, this test sends some sequential packets with the supervisor enabled, +//! sends the next packet *without* the supervisor enabled, then sends additional +//! packets with the supervisor enabled again. The pending packet that was sent +//! without the supervisor enabled should be relayed in order along with the +//! other packets, as expected of ordered channel behaviour. + +use std::str::FromStr; + +use ibc_relayer_types::applications::ics27_ica::cosmos_tx::CosmosTx; +use ibc_relayer_types::applications::ics27_ica::packet_data::InterchainAccountPacketData; +use ibc_relayer_types::applications::transfer::msgs::send::MsgSend; +use ibc_relayer_types::applications::transfer::{Amount, Coin}; +use ibc_relayer_types::bigint::U256; +use ibc_relayer_types::signer::Signer; +use ibc_relayer_types::timestamp::Timestamp; +use ibc_relayer_types::tx_msg::Msg; +use ibc_test_framework::chain::ext::ica::register_interchain_account; +use ibc_test_framework::framework::binary::channel::run_binary_interchain_security_channel_test; +use ibc_test_framework::prelude::*; +use ibc_test_framework::relayer::channel::assert_eventually_channel_established; +use ibc_test_framework::relayer::channel::query_channel_end; +use ibc_test_framework::util::interchain_security::{ + interchain_send_tx, update_genesis_for_consumer_chain, update_relayer_config_for_consumer_chain, +}; + +#[test] +fn test_ica_ordered_channel() -> Result<(), Error> { + run_binary_interchain_security_channel_test(&IcaOrderedChannelTest) +} + +struct IcaOrderedChannelTest; + +impl TestOverrides for IcaOrderedChannelTest { + fn modify_genesis_file(&self, genesis: &mut serde_json::Value) -> Result<(), Error> { + use serde_json::Value; + + // Allow MsgSend messages over ICA + let allow_messages = genesis + .get_mut("app_state") + .and_then(|app_state| app_state.get_mut("interchainaccounts")) + .and_then(|ica| ica.get_mut("host_genesis_state")) + .and_then(|state| state.get_mut("params")) + .and_then(|params| params.get_mut("allow_messages")) + .and_then(|allow_messages| allow_messages.as_array_mut()); + + if let Some(allow_messages) = allow_messages { + allow_messages.push(Value::String("/cosmos.bank.v1beta1.MsgSend".to_string())); + } else { + return Err(Error::generic(eyre!("failed to update genesis file"))); + } + + update_genesis_for_consumer_chain(genesis)?; + + Ok(()) + } + + fn modify_relayer_config(&self, config: &mut Config) { + config.mode.clients.misbehaviour = false; + + config.mode.channels.enabled = true; + + // Disable packet clearing so that packets sent without the supervisor + // enabled enter a pending state. + config.mode.packets.enabled = true; + config.mode.packets.clear_on_start = false; + config.mode.packets.clear_interval = 0; + // This is needed for ordered channels + config.mode.packets.force_disable_clear_on_start = true; + + update_relayer_config_for_consumer_chain(config); + } + + fn should_spawn_supervisor(&self) -> bool { + false + } +} + +impl BinaryChannelTest for IcaOrderedChannelTest { + fn run( + &self, + _config: &TestConfig, + relayer: RelayerDriver, + chains: ConnectedChains, + channel: ConnectedChannel, + ) -> Result<(), Error> { + let connection_b_to_a = channel.connection.clone().flip(); + let (wallet, channel_id, port_id) = + register_interchain_account(&chains.node_b, chains.handle_b(), &connection_b_to_a)?; + + relayer.with_supervisor(|| { + // Check that the corresponding ICA channel is eventually established. + let _counterparty_channel_id = assert_eventually_channel_established( + chains.handle_b(), + chains.handle_a(), + &channel_id.as_ref(), + &port_id.as_ref(), + )?; + + Ok(()) + })?; + + // Assert that the channel returned by `register_interchain_account` is an ordered channel + let channel_end = + query_channel_end(chains.handle_b(), &channel_id.as_ref(), &port_id.as_ref())?; + + assert_eq!(channel_end.value().ordering(), &Ordering::Ordered); + + // Query the controller chain for the address of the ICA wallet on the host chain. + let ica_address = chains.node_b.chain_driver().query_interchain_account( + &wallet.address(), + &channel.connection.connection_id_b.as_ref(), + )?; + + let stake_denom: MonoTagged = MonoTagged::new(Denom::base("stake")); + + chains.node_a.chain_driver().assert_eventual_wallet_amount( + &ica_address.as_ref(), + &stake_denom.with_amount(0u64).as_ref(), + )?; + + // Send funds to the interchain account. + let ica_fund = 42000u64; + + chains.node_a.chain_driver().local_transfer_token( + &chains.node_a.wallets().user1(), + &ica_address.as_ref(), + &stake_denom.with_amount(ica_fund).as_ref(), + )?; + + chains.node_a.chain_driver().assert_eventual_wallet_amount( + &ica_address.as_ref(), + &stake_denom.with_amount(ica_fund).as_ref(), + )?; + + let amount = 1200; + + let msg = MsgSend { + from_address: ica_address.to_string(), + to_address: chains.node_a.wallets().user2().address().to_string(), + amount: vec![Coin { + denom: stake_denom.to_string(), + amount: Amount(U256::from(amount)), + }], + }; + + let raw_msg = msg.to_any(); + + let cosmos_tx = CosmosTx { + messages: vec![raw_msg], + }; + + let raw_cosmos_tx = cosmos_tx.to_any(); + + let interchain_account_packet_data = InterchainAccountPacketData::new(raw_cosmos_tx.value); + + let signer = Signer::from_str(&wallet.address().to_string()).unwrap(); + + let user2_balance = chains.node_a.chain_driver().query_balance( + &chains.node_a.wallets().user2().address(), + &stake_denom.as_ref(), + )?; + + relayer.with_supervisor(|| { + let ica_events = interchain_send_tx( + chains.handle_b(), + &signer, + &channel.connection.connection_id_b.0, + interchain_account_packet_data.clone(), + Timestamp::from_nanoseconds(120000000000).unwrap(), + )?; + + // Check that the ICA account's balance has been debited the sent amount. + chains.node_a.chain_driver().assert_eventual_wallet_amount( + &ica_address.as_ref(), + &stake_denom.with_amount(ica_fund - amount).as_ref(), + )?; + + chains.node_a.chain_driver().assert_eventual_wallet_amount( + &chains.node_a.wallets().user2().address(), + &(user2_balance.clone() + amount).as_ref(), + )?; + + info!("First ICA transfer made with supervisor: {ica_events:#?}"); + + Ok(()) + })?; + + let ica_events = interchain_send_tx( + chains.handle_b(), + &signer, + &channel.connection.connection_id_b.0, + interchain_account_packet_data.clone(), + Timestamp::from_nanoseconds(120000000000).unwrap(), + )?; + + info!("Second ICA transfer made without supervisor: {ica_events:#?}"); + + relayer.with_supervisor(|| { + let ica_events = interchain_send_tx( + chains.handle_b(), + &signer, + &channel.connection.connection_id_b.0, + interchain_account_packet_data, + Timestamp::from_nanoseconds(120000000000).unwrap(), + )?; + + // Check that the ICA account's balance has been debited the sent amount. + chains.node_a.chain_driver().assert_eventual_wallet_amount( + &ica_address.as_ref(), + &stake_denom.with_amount(ica_fund - 3 * amount).as_ref(), + )?; + + info!("Third ICA transfer made with supervisor: {ica_events:#?}"); + + chains.node_a.chain_driver().assert_eventual_wallet_amount( + &chains.node_a.wallets().user2().address(), + &(user2_balance + (3 * amount)).as_ref(), + )?; + + Ok(()) + }) + } +} diff --git a/tools/integration-test/src/tests/interchain_security/ica_transfer.rs b/tools/integration-test/src/tests/interchain_security/ica_transfer.rs index fc44ff7fd2..5377f206da 100644 --- a/tools/integration-test/src/tests/interchain_security/ica_transfer.rs +++ b/tools/integration-test/src/tests/interchain_security/ica_transfer.rs @@ -3,10 +3,7 @@ //! the second chain a Consumer chain. use std::str::FromStr; -use ibc_relayer::chain::tracking::TrackedMsgs; -use ibc_relayer::event::IbcEventWithHeight; use ibc_relayer_types::applications::ics27_ica::cosmos_tx::CosmosTx; -use ibc_relayer_types::applications::ics27_ica::msgs::send_tx::MsgSendTx; use ibc_relayer_types::applications::ics27_ica::packet_data::InterchainAccountPacketData; use ibc_relayer_types::applications::transfer::msgs::send::MsgSend; use ibc_relayer_types::applications::transfer::{Amount, Coin}; @@ -19,7 +16,7 @@ use ibc_test_framework::framework::binary::channel::run_binary_interchain_securi use ibc_test_framework::prelude::*; use ibc_test_framework::relayer::channel::assert_eventually_channel_established; use ibc_test_framework::util::interchain_security::{ - update_genesis_for_consumer_chain, update_relayer_config_for_consumer_chain, + interchain_send_tx, update_genesis_for_consumer_chain, update_relayer_config_for_consumer_chain, }; #[test] @@ -151,29 +148,7 @@ impl BinaryChannelTest for InterchainSecurityIcaTransferTest { &ica_address.as_ref(), &stake_denom.with_amount(ica_fund - amount).as_ref(), )?; + Ok(()) } } - -fn interchain_send_tx( - chain: &ChainA, - from: &Signer, - connection: &ConnectionId, - msg: InterchainAccountPacketData, - relative_timeout: Timestamp, -) -> Result, Error> { - let msg = MsgSendTx { - owner: from.clone(), - connection_id: connection.clone(), - packet_data: msg, - relative_timeout, - }; - - let msg_any = msg.to_any(); - - let tm = TrackedMsgs::new_static(vec![msg_any], "SendTx"); - - chain - .send_messages_and_wait_commit(tm) - .map_err(Error::relayer) -} diff --git a/tools/integration-test/src/tests/interchain_security/mod.rs b/tools/integration-test/src/tests/interchain_security/mod.rs index a2acc74d92..0772d733db 100644 --- a/tools/integration-test/src/tests/interchain_security/mod.rs +++ b/tools/integration-test/src/tests/interchain_security/mod.rs @@ -1,4 +1,6 @@ #[cfg(any(doc, feature = "ica"))] +pub mod ica_ordered_channel; +#[cfg(any(doc, feature = "ica"))] pub mod ica_transfer; #[cfg(any(doc, feature = "ics31"))] pub mod icq; diff --git a/tools/integration-test/src/tests/memo.rs b/tools/integration-test/src/tests/memo.rs index 2b44b90d48..52de0c5602 100644 --- a/tools/integration-test/src/tests/memo.rs +++ b/tools/integration-test/src/tests/memo.rs @@ -12,6 +12,8 @@ use ibc_test_framework::ibc::denom::derive_ibc_denom; use ibc_test_framework::prelude::*; use ibc_test_framework::util::random::{random_string, random_u128_range}; +const OVERWRITE_MEMO: &str = "Overwritten memo"; + #[test] fn test_memo() -> Result<(), Error> { let memo = Memo::new(random_string()).unwrap(); @@ -19,6 +21,13 @@ fn test_memo() -> Result<(), Error> { run_binary_channel_test(&test) } +#[test] +fn test_memo_overwrite() -> Result<(), Error> { + let memo = Memo::new(random_string()).unwrap(); + let test = MemoTest { memo }; + run_binary_channel_test(&test) +} + pub struct MemoTest { memo: Memo, } @@ -82,6 +91,70 @@ impl BinaryChannelTest for MemoTest { } } +pub struct MemoOverwriteTest { + memo: Memo, +} + +impl TestOverrides for MemoOverwriteTest { + fn modify_relayer_config(&self, config: &mut Config) { + for chain in config.chains.iter_mut() { + match chain { + ChainConfig::CosmosSdk(chain_config) => { + chain_config.memo_prefix = self.memo.clone(); + chain_config.memo_overwrite = Some(Memo::new(OVERWRITE_MEMO).unwrap()) + } + } + } + } +} + +impl BinaryChannelTest for MemoOverwriteTest { + fn run( + &self, + _config: &TestConfig, + _relayer: RelayerDriver, + chains: ConnectedChains, + channel: ConnectedChannel, + ) -> Result<(), Error> { + info!( + "testing IBC transfer with memo configured: \"{}\"", + self.memo + ); + + let denom_a = chains.node_a.denom(); + + let a_to_b_amount = random_u128_range(1000, 5000); + + chains.node_a.chain_driver().ibc_transfer_token( + &channel.port_a.as_ref(), + &channel.channel_id_a.as_ref(), + &chains.node_a.wallets().user1(), + &chains.node_b.wallets().user1().address(), + &denom_a.with_amount(a_to_b_amount).as_ref(), + )?; + + let denom_b = derive_ibc_denom( + &channel.port_b.as_ref(), + &channel.channel_id_b.as_ref(), + &denom_a, + )?; + + chains.node_b.chain_driver().assert_eventual_wallet_amount( + &chains.node_b.wallets().user1().address(), + &denom_b.with_amount(a_to_b_amount).as_ref(), + )?; + + let tx_info = chains + .node_b + .chain_driver() + .query_recipient_transactions(&chains.node_b.wallets().user1().address())?; + + assert_tx_memo_equals(&tx_info, OVERWRITE_MEMO)?; + + Ok(()) + } +} + fn assert_tx_memo_equals(tx_info: &json::Value, expected_memo: &str) -> Result<(), Error> { debug!("comparing memo field from json value {}", tx_info); diff --git a/tools/test-framework/Cargo.toml b/tools/test-framework/Cargo.toml index a33bbf74d4..21616613ca 100644 --- a/tools/test-framework/Cargo.toml +++ b/tools/test-framework/Cargo.toml @@ -17,14 +17,14 @@ description = """ ibc-relayer-types = { version = "=0.27.0", path = "../../crates/relayer-types" } ibc-relayer = { version = "=0.27.0", path = "../../crates/relayer" } ibc-relayer-cli = { version = "=1.8.0", path = "../../crates/relayer-cli" } -ibc-proto = { version = "0.41.0", features = ["serde"] } +ibc-proto = { version = "0.42.0", features = ["serde"] } tendermint-rpc = { version = "0.34.0", features = ["http-client", "websocket-client"] } http = "0.2.9" tokio = { version = "1.0", features = ["full"] } tracing = "0.1.36" tracing-subscriber = "0.3.14" -eyre = "0.6.8" +eyre = "0.6.12" color-eyre = "0.6" rand = "0.8.5" hex = "0.4.3" diff --git a/tools/test-framework/src/chain/ext/ica.rs b/tools/test-framework/src/chain/ext/ica.rs index 766f03e1e1..bd5b142fd2 100644 --- a/tools/test-framework/src/chain/ext/ica.rs +++ b/tools/test-framework/src/chain/ext/ica.rs @@ -1,6 +1,6 @@ use ibc_relayer::chain::handle::ChainHandle; use ibc_relayer::chain::tracking::TrackedMsgs; -use ibc_relayer_types::applications::ics27_ica::msgs::register::MsgRegisterInterchainAccount; +use ibc_relayer_types::applications::ics27_ica::msgs::register::LegacyMsgRegisterInterchainAccount; use ibc_relayer_types::core::ics04_channel::version::Version; use ibc_relayer_types::events::IbcEvent; use ibc_relayer_types::tx_msg::Msg; @@ -80,7 +80,7 @@ pub fn register_interchain_account Result<(), Error> { // Consumer chain doesn't have a gov key. if genesis @@ -32,3 +39,28 @@ pub fn update_relayer_config_for_consumer_chain(config: &mut Config) { } } } + +/// Sends a message containing `InterchainAccountPacketData` from the `Signer` +/// to the `Chain`. +pub fn interchain_send_tx( + chain: &ChainA, + from: &Signer, + connection: &ConnectionId, + msg: InterchainAccountPacketData, + relative_timeout: Timestamp, +) -> Result, Error> { + let msg = MsgSendTx { + owner: from.clone(), + connection_id: connection.clone(), + packet_data: msg, + relative_timeout, + }; + + let msg_any = msg.to_any(); + + let tm = TrackedMsgs::new_static(vec![msg_any], "SendTx"); + + chain + .send_messages_and_wait_commit(tm) + .map_err(Error::relayer) +}