diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml index dbdd309dfe..4062e2ad63 100644 --- a/.github/workflows/integration.yaml +++ b/.github/workflows/integration.yaml @@ -100,7 +100,7 @@ jobs: run: curl -LsSf https://get.nexte.st/latest/linux | tar zxf - -C ${CARGO_HOME:-~/.cargo}/bin - name: Run integration test env: - RUST_LOG: info + RUST_LOG: info,ibc_relayer_runtime=trace RUST_BACKTRACE: 1 NO_COLOR_LOG: 1 NEXTEST_RETRIES: 2 @@ -110,6 +110,82 @@ jobs: nix shell .#python .#${{ matrix.chain.package }} -c \ cargo nextest run -p ibc-integration-test --no-fail-fast --failure-output final --test-threads=2 + relayer-next-integration-test: + runs-on: ubuntu-20.04 + timeout-minutes: 60 + strategy: + fail-fast: false + matrix: + chain: + - package: gaia6 + command: gaiad + account_prefix: cosmos + - package: gaia7 + command: gaiad + account_prefix: cosmos + - package: gaia8 + command: gaiad + account_prefix: cosmos + - package: ibc-go-v2-simapp + command: simd + account_prefix: cosmos + - package: ibc-go-v3-simapp + command: simd + account_prefix: cosmos + - package: ibc-go-v4-simapp + command: simd + account_prefix: cosmos + - package: ibc-go-v5-simapp + command: simd + account_prefix: cosmos + - package: ibc-go-v6-simapp + command: simd + account_prefix: cosmos + - package: ibc-go-v7-simapp + command: simd + account_prefix: cosmos + - package: wasmd + command: wasmd + account_prefix: wasm + - package: evmos + command: evmosd + account_prefix: evmos + - package: osmosis + command: osmosisd + account_prefix: osmo + + steps: + - uses: actions/checkout@v2 + - uses: cachix/install-nix-action@v18 + with: + install_url: https://nixos-nix-install-tests.cachix.org/serve/vij683ly7sl95nnhb67bdjjfabclr85m/install + install_options: '--tarball-url-prefix https://nixos-nix-install-tests.cachix.org/serve' + extra_nix_config: | + experimental-features = nix-command flakes + - uses: cachix/cachix-action@v12 + with: + name: cosmos + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + - uses: Swatinem/rust-cache@v1 + - uses: actions-rs/cargo@v1 + with: + command: test + args: -p ibc-integration-test --no-fail-fast --no-run + - name: run integration tests + env: + RUST_LOG: info,ibc_relayer_runtime=trace + RUST_BACKTRACE: 1 + NO_COLOR_LOG: 1 + CHAIN_COMMAND_PATHS: ${{ matrix.chain.command }} + ACCOUNT_PREFIXES: ${{ matrix.chain.account_prefix }} + run: | + nix shell .#python .#${{ matrix.chain.package }} -c cargo \ + test -p ibc-integration-test --no-fail-fast -- \ + --nocapture --test-threads=2 + ordered-channel-test: runs-on: ubuntu-20.04 timeout-minutes: 60 diff --git a/.gitignore b/.gitignore index 2e9b0e8816..7425685d58 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ # Generated by Cargo # will have compiled files and executables -target/ +/target/ +/tools/verification/target/ +proto-compiler/target/ +ci/no-std-check/target/ # These are backup files generated by rustfmt **/*.rs.bk diff --git a/.rustfmt.toml b/.rustfmt.toml index 8c83f08221..c276722c0a 100644 --- a/.rustfmt.toml +++ b/.rustfmt.toml @@ -7,3 +7,4 @@ reorder_imports = true # format_strings = false # comment_width = 100 # wrap_comments = true +# group_imports = "StdExternalCrate" diff --git a/Cargo.lock b/Cargo.lock index b4b72cf6f8..5e637c67f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -65,11 +65,26 @@ dependencies = [ "memchr", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anyhow" -version = "1.0.71" +version = "1.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" +checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854" [[package]] name = "arc-swap" @@ -77,6 +92,35 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" +[[package]] +name = "async-io" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +dependencies = [ + "async-lock", + "autocfg", + "cfg-if 1.0.0", + "concurrent-queue", + "futures-lite", + "log", + "parking", + "polling", + "rustix 0.37.23", + "slab", + "socket2", + "waker-fn", +] + +[[package]] +name = "async-lock" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7" +dependencies = [ + "event-listener", +] + [[package]] name = "async-stream" version = "0.3.5" @@ -96,7 +140,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.28", ] [[package]] @@ -107,7 +151,7 @@ checksum = "cc6dde6e4ed435a4c1ee4e73592f5ba9da2151af10076cc04858746af9352d09" dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.28", ] [[package]] @@ -145,9 +189,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "axum" -version = "0.6.18" +version = "0.6.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8175979259124331c1d7bf6586ee7e0da434155e4b2d48ec2c8386281d8df39" +checksum = "a6a1de45611fdb535bfde7b7de4fd54f4fd2b17b1737c0a59b69bf9b92074b8c" dependencies = [ "async-trait", "axum-core", @@ -360,9 +404,9 @@ checksum = "e6e9e01327e6c86e92ec72b1c798d4a94810f147209bbe3ffab6a86954937a6f" [[package]] name = "cargo-platform" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27" +checksum = "2cfa25e60aea747ec7e1124f238816749faa93759c6ff5b31f1ccdda137f4479" dependencies = [ "serde", ] @@ -398,6 +442,21 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "time 0.1.45", + "wasm-bindgen", + "winapi", +] + [[package]] name = "clap" version = "3.2.25" @@ -473,6 +532,15 @@ dependencies = [ "tracing-error", ] +[[package]] +name = "concurrent-queue" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" +dependencies = [ + "crossbeam-utils 0.8.16", +] + [[package]] name = "console" version = "0.15.7" @@ -643,7 +711,7 @@ checksum = "83fdaf97f4804dcebfa5862639bc9ce4121e82140bec2a987ac5140294865b5b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.28", ] [[package]] @@ -659,6 +727,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "dashmap" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e77a43b28d0668df09411cb0bc9a8c2adc40f9a048afe863e05fd43251e8e39c" +dependencies = [ + "cfg-if 1.0.0", + "num_cpus", +] + [[package]] name = "dashmap" version = "5.5.0" @@ -682,6 +760,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "deranged" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8810e7e2cf385b1e9b50d68264908ec367ba642c96d02edfe61c39e88e2a3c01" + [[package]] name = "derivation-path" version = "0.2.0" @@ -761,9 +845,9 @@ checksum = "304e6508efa593091e97a9abbc10f90aa7ca635b6d2784feff3c89d41dd12272" [[package]] name = "ecdsa" -version = "0.16.7" +version = "0.16.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0997c976637b606099b9985693efa3581e84e41f5c11ba5255f88711058ad428" +checksum = "a4b1e0c257a9e9f25f90ff76d7a68360ed497ee519c8e428d1825ef0000799d4" dependencies = [ "der", "digest 0.10.7", @@ -825,9 +909,9 @@ dependencies = [ [[package]] name = "either" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "elliptic-curve" @@ -893,9 +977,9 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" dependencies = [ "errno-dragonfly", "libc", @@ -921,6 +1005,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + [[package]] name = "eyre" version = "0.6.8" @@ -940,6 +1030,12 @@ dependencies = [ "instant", ] +[[package]] +name = "fastrand" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" + [[package]] name = "ff" version = "0.13.0" @@ -1045,6 +1141,21 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand 1.9.0", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + [[package]] name = "futures-macro" version = "0.3.28" @@ -1053,7 +1164,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.28", ] [[package]] @@ -1106,7 +1217,7 @@ dependencies = [ "cfg-if 1.0.0", "js-sys", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "wasm-bindgen", ] @@ -1403,6 +1514,29 @@ dependencies = [ "tokio-io-timeout", ] +[[package]] +name = "iana-time-zone" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "ibc-chain-registry" version = "0.25.0" @@ -1428,13 +1562,19 @@ version = "0.25.0" dependencies = [ "http", "ibc-relayer", + "ibc-relayer-all-in-one", + "ibc-relayer-components", + "ibc-relayer-components-extra", + "ibc-relayer-cosmos", + "ibc-relayer-runtime", "ibc-relayer-types", "ibc-test-framework", "prost", "serde", "serde_json", "tempfile", - "time", + "time 0.3.24", + "tokio", "toml 0.7.6", "tonic", ] @@ -1486,7 +1626,7 @@ dependencies = [ "ibc-relayer-types", "ibc-telemetry", "itertools", - "moka", + "moka 0.11.3", "num-bigint", "num-rational", "once_cell", @@ -1519,7 +1659,18 @@ dependencies = [ "tonic", "tracing", "tracing-subscriber", - "uuid 1.4.0", + "uuid 1.4.1", +] + +[[package]] +name = "ibc-relayer-all-in-one" +version = "0.1.0" +dependencies = [ + "async-trait", + "futures-core", + "futures-util", + "ibc-relayer-components", + "ibc-relayer-components-extra", ] [[package]] @@ -1527,6 +1678,8 @@ name = "ibc-relayer-cli" version = "1.6.0" dependencies = [ "abscissa_core", + "async-trait", + "chrono", "clap", "clap_complete", "color-eyre", @@ -1542,12 +1695,17 @@ dependencies = [ "humantime", "ibc-chain-registry", "ibc-relayer", + "ibc-relayer-all-in-one", + "ibc-relayer-components", + "ibc-relayer-cosmos", "ibc-relayer-rest", + "ibc-relayer-runtime", "ibc-relayer-types", "ibc-telemetry", "itertools", "once_cell", "oneline-eyre", + "opentelemetry 0.17.0", "regex", "serde", "serde_json", @@ -1557,12 +1715,73 @@ dependencies = [ "tendermint", "tendermint-light-client-verifier", "tendermint-rpc", - "time", + "time 0.3.24", "tokio", "tracing", "tracing-subscriber", ] +[[package]] +name = "ibc-relayer-components" +version = "0.1.0" +dependencies = [ + "async-trait", + "futures-core", + "futures-util", +] + +[[package]] +name = "ibc-relayer-components-extra" +version = "0.1.0" +dependencies = [ + "async-trait", + "futures-core", + "futures-util", + "ibc-relayer-components", +] + +[[package]] +name = "ibc-relayer-cosmos" +version = "0.25.0" +dependencies = [ + "async-trait", + "eyre", + "flex-error", + "futures", + "ibc-proto", + "ibc-relayer", + "ibc-relayer-all-in-one", + "ibc-relayer-components", + "ibc-relayer-components-extra", + "ibc-relayer-runtime", + "ibc-relayer-types", + "ibc-telemetry", + "itertools", + "moka 0.10.3", + "opentelemetry 0.17.0", + "prost", + "serde", + "serde_derive", + "tendermint", + "tendermint-rpc", + "tokio", + "tonic", + "tracing", +] + +[[package]] +name = "ibc-relayer-mock" +version = "0.1.0" +dependencies = [ + "async-trait", + "eyre", + "flex-error", + "ibc-relayer-components", + "ibc-relayer-runtime", + "tokio", + "tracing", +] + [[package]] name = "ibc-relayer-rest" version = "0.25.0" @@ -1578,6 +1797,21 @@ dependencies = [ "tracing", ] +[[package]] +name = "ibc-relayer-runtime" +version = "0.1.0" +dependencies = [ + "async-trait", + "flex-error", + "futures", + "ibc-relayer-all-in-one", + "ibc-relayer-components", + "ibc-relayer-components-extra", + "tokio", + "tokio-stream", + "tracing", +] + [[package]] name = "ibc-relayer-types" version = "0.25.0" @@ -1605,7 +1839,7 @@ dependencies = [ "tendermint-rpc", "tendermint-testgen", "test-log", - "time", + "time 0.3.24", "tracing", "tracing-subscriber", "uint", @@ -1616,11 +1850,11 @@ name = "ibc-telemetry" version = "0.25.0" dependencies = [ "axum", - "dashmap", + "dashmap 5.5.0", "ibc-relayer-types", - "moka", + "moka 0.11.3", "once_cell", - "opentelemetry", + "opentelemetry 0.19.0", "opentelemetry-prometheus", "prometheus", "serde", @@ -1634,6 +1868,7 @@ dependencies = [ name = "ibc-test-framework" version = "0.25.0" dependencies = [ + "cfg-if 1.0.0", "color-eyre", "crossbeam-channel 0.5.8", "eyre", @@ -1643,7 +1878,10 @@ dependencies = [ "http", "ibc-proto", "ibc-relayer", + "ibc-relayer-all-in-one", "ibc-relayer-cli", + "ibc-relayer-components", + "ibc-relayer-cosmos", "ibc-relayer-types", "itertools", "once_cell", @@ -1789,9 +2027,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "js-sys" @@ -1883,9 +2121,9 @@ dependencies = [ [[package]] name = "matchit" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40" +checksum = "67827e6ea8ee8a7c4a72227ef4fc08957040acffdb5f122733b24fa12daff41b" [[package]] name = "maybe-uninit" @@ -1930,10 +2168,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.48.0", ] +[[package]] +name = "moka" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713518e40360e799c7a0b991b63fcd59814b00901ad3acbe8ecfb5daa23723be" +dependencies = [ + "async-io", + "async-lock", + "crossbeam-channel 0.5.8", + "crossbeam-epoch", + "crossbeam-utils 0.8.16", + "futures-util", + "num_cpus", + "once_cell", + "parking_lot", + "quanta", + "rustc_version", + "scheduled-thread-pool", + "skeptic", + "smallvec", + "tagptr", + "thiserror", + "triomphe", + "uuid 1.4.1", +] + [[package]] name = "moka" version = "0.11.3" @@ -1953,7 +2217,7 @@ dependencies = [ "tagptr", "thiserror", "triomphe", - "uuid 1.4.0", + "uuid 1.4.1", ] [[package]] @@ -2014,9 +2278,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" dependencies = [ "autocfg", ] @@ -2067,6 +2331,27 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +[[package]] +name = "opentelemetry" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6105e89802af13fdf48c49d7646d3b533a70e536d818aae7e78ba0433d01acb8" +dependencies = [ + "async-trait", + "crossbeam-channel 0.5.8", + "dashmap 4.0.2", + "fnv", + "futures-channel", + "futures-executor", + "futures-util", + "js-sys", + "lazy_static", + "percent-encoding", + "pin-project", + "rand", + "thiserror", +] + [[package]] name = "opentelemetry" version = "0.19.0" @@ -2083,7 +2368,7 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a9f186f6293ebb693caddd0595e66b74a6068fa51048e26e0bf9c95478c639c" dependencies = [ - "opentelemetry", + "opentelemetry 0.19.0", "prometheus", "protobuf", ] @@ -2112,7 +2397,7 @@ checksum = "8b3a2a91fdbfdd4d212c0dcc2ab540de2c2bcbbd90be17de7a7daf8822d010c1" dependencies = [ "async-trait", "crossbeam-channel 0.5.8", - "dashmap", + "dashmap 5.5.0", "fnv", "futures-channel", "futures-executor", @@ -2142,6 +2427,12 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" +[[package]] +name = "parking" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" + [[package]] name = "parking_lot" version = "0.12.1" @@ -2167,9 +2458,9 @@ dependencies = [ [[package]] name = "paste" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4b27ab7be369122c218afc2079489cdcb4b517c0a3fc386ff11e1fedfcc2b35" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" [[package]] name = "pbkdf2" @@ -2230,7 +2521,7 @@ checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.28", ] [[package]] @@ -2261,6 +2552,22 @@ version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3d7ddaed09e0eb771a79ab0fd64609ba0afb0a8366421957936ad14cbd13630" +[[package]] +name = "polling" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if 1.0.0", + "concurrent-queue", + "libc", + "log", + "pin-project-lite", + "windows-sys 0.48.0", +] + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -2304,9 +2611,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.64" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78803b62cbf1f46fde80d7c0e803111524b9877184cfe7c3033659490ac7a7da" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" dependencies = [ "unicode-ident", ] @@ -2386,16 +2693,16 @@ dependencies = [ "mach2", "once_cell", "raw-cpuid", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "web-sys", "winapi", ] [[package]] name = "quote" -version = "1.0.29" +version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" +checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" dependencies = [ "proc-macro2", ] @@ -2721,15 +3028,15 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc31bd9b61a32c31f9650d18add92aa83a49ba979c143eefd27fe7177b05bd5f" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" [[package]] name = "ryu" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe232bdf6be8c8de797b22184ee71118d63780ea42ac85b61d1baa6d3b782ae9" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "same-file" @@ -2760,9 +3067,9 @@ dependencies = [ [[package]] name = "scopeguard" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sct" @@ -2786,9 +3093,9 @@ dependencies = [ [[package]] name = "sec1" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0aec48e813d6b90b15f0b8948af3c63483992dee44c03e9930b3eebdabe046e" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ "base16ct", "der", @@ -2831,9 +3138,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.9.1" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" dependencies = [ "bitflags 1.3.2", "core-foundation", @@ -2844,9 +3151,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" dependencies = [ "core-foundation-sys", "libc", @@ -2854,27 +3161,27 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" +checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" dependencies = [ "serde", ] [[package]] name = "serde" -version = "1.0.171" +version = "1.0.179" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9" +checksum = "0a5bf42b8d227d4abf38a1ddb08602e229108a517cd4e5bb28f9c7eaafdce5c0" dependencies = [ "serde_derive", ] [[package]] name = "serde_bytes" -version = "0.11.11" +version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a16be4fe5320ade08736447e3198294a5ea9a6d44dde6f35f0a5e06859c427a" +checksum = "ab33ec92f677585af6d88c65593ae2375adde54efdbf16d597f2cbc7a6d368ff" dependencies = [ "serde", ] @@ -2891,20 +3198,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.171" +version = "1.0.179" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389894603bd18c46fa56231694f8d827779c0951a667087194cf9de94ed24682" +checksum = "741e124f5485c7e60c03b043f79f320bff3527f4bbf12cf3831750dc46a0ec2c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.28", ] [[package]] name = "serde_json" -version = "1.0.102" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5062a995d481b2308b6064e9af76011f2921c35f97b0468811ed9f6cd91dfed" +checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c" dependencies = [ "itoa", "ryu", @@ -2913,9 +3220,9 @@ dependencies = [ [[package]] name = "serde_path_to_error" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc4422959dd87a76cb117c191dcbffc20467f06c9100b76721dab370f24d3a" +checksum = "4beec8bce849d58d06238cb50db2e1c417cfeafa4c63f692b15c82b7c80f8335" dependencies = [ "itoa", "serde", @@ -2923,13 +3230,13 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.14" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d89a8107374290037607734c0b73a85db7ed80cae314b3c5791f192a496e731" +checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.28", ] [[package]] @@ -2955,9 +3262,9 @@ dependencies = [ [[package]] name = "serde_yaml" -version = "0.9.22" +version = "0.9.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "452e67b9c20c37fa79df53201dc03839651086ed9bbe92b3ca585ca9fdaa7d85" +checksum = "1a49e178e4452f45cb61d0cd8cebc1b0fafd3e41929e996cef79aa3aca91f574" dependencies = [ "indexmap 2.0.0", "itoa", @@ -2972,7 +3279,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e56dd856803e253c8f298af3f4d7eb0ae5e23a737252cd90bb4f3b435033b2d" dependencies = [ - "dashmap", + "dashmap 5.5.0", "futures", "lazy_static", "log", @@ -2988,7 +3295,7 @@ checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.28", ] [[package]] @@ -3173,7 +3480,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.25", + "syn 2.0.28", ] [[package]] @@ -3210,9 +3517,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.25" +version = "2.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15e3fc8c0c74267e2df136e5e5fb656a464158aa57624053375eb9c8c6e25ae2" +checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" dependencies = [ "proc-macro2", "quote", @@ -3245,15 +3552,14 @@ checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" [[package]] name = "tempfile" -version = "3.6.0" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" +checksum = "5486094ee78b2e5038a6382ed7645bc084dc2ec433426ca4c3cb61e2007b8998" dependencies = [ - "autocfg", "cfg-if 1.0.0", - "fastrand", + "fastrand 2.0.0", "redox_syscall 0.3.5", - "rustix 0.37.23", + "rustix 0.38.4", "windows-sys 0.48.0", ] @@ -3284,7 +3590,7 @@ dependencies = [ "subtle", "subtle-encoding", "tendermint-proto", - "time", + "time 0.3.24", "zeroize", ] @@ -3322,7 +3628,7 @@ dependencies = [ "tendermint", "tendermint-light-client-verifier", "tendermint-rpc", - "time", + "time 0.3.24", "tokio", "tracing", ] @@ -3347,7 +3653,7 @@ dependencies = [ "tendermint-light-client", "tendermint-proto", "tendermint-rpc", - "time", + "time 0.3.24", "tracing", ] @@ -3361,7 +3667,7 @@ dependencies = [ "flex-error", "serde", "tendermint", - "time", + "time 0.3.24", ] [[package]] @@ -3379,7 +3685,7 @@ dependencies = [ "serde", "serde_bytes", "subtle-encoding", - "time", + "time 0.3.24", ] [[package]] @@ -3410,7 +3716,7 @@ dependencies = [ "tendermint-config", "tendermint-proto", "thiserror", - "time", + "time 0.3.24", "tokio", "tracing", "url", @@ -3431,7 +3737,7 @@ dependencies = [ "simple-error", "tempfile", "tendermint", - "time", + "time 0.3.24", ] [[package]] @@ -3462,22 +3768,22 @@ checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" [[package]] name = "thiserror" -version = "1.0.43" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a35fc5b8971143ca348fa6df4f024d4d55264f3468c71ad1c2f365b0a4d58c42" +checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.43" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f" +checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.28", ] [[package]] @@ -3492,10 +3798,22 @@ dependencies = [ [[package]] name = "time" -version = "0.3.22" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea9e1b3cf1243ae005d9e74085d4d542f3125458f3a81af210d901dcd7411efd" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + +[[package]] +name = "time" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b79eabcd964882a646b3584543ccabeae7869e9ac32a46f6f22b7a5bd405308b" +dependencies = [ + "deranged", "serde", "time-core", "time-macros", @@ -3509,9 +3827,9 @@ checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" [[package]] name = "time-macros" -version = "0.2.9" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" +checksum = "eb71511c991639bb078fd5bf97757e03914361c48100d52878b8e52b46fb92cd" dependencies = [ "time-core", ] @@ -3597,7 +3915,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.28", ] [[package]] @@ -3689,9 +4007,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.19.12" +version = "0.19.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c500344a19072298cd05a7224b3c0c629348b78692bf48466c5238656e315a78" +checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" dependencies = [ "indexmap 2.0.0", "serde", @@ -3785,7 +4103,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.28", ] [[package]] @@ -3918,9 +4236,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" [[package]] name = "unicode-normalization" @@ -3945,9 +4263,9 @@ checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" [[package]] name = "unsafe-libyaml" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1865806a559042e51ab5414598446a5871b561d21b6764f2eabb0dd481d880a6" +checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa" [[package]] name = "untrusted" @@ -3968,9 +4286,9 @@ dependencies = [ [[package]] name = "urlencoding" -version = "2.1.2" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8db7427f936968176eaa7cdf81b7f98b980b18495ec28f1b5791ac3bfe3eea9" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" [[package]] name = "utf-8" @@ -3992,9 +4310,9 @@ checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" [[package]] name = "uuid" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d023da39d1fde5a8a3fe1f3e01ca9632ada0a63e9797de55a879d6e2236277be" +checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" dependencies = [ "getrandom", ] @@ -4020,6 +4338,12 @@ dependencies = [ "libc", ] +[[package]] +name = "waker-fn" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" + [[package]] name = "walkdir" version = "2.3.3" @@ -4039,6 +4363,12 @@ dependencies = [ "try-lock", ] +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -4066,7 +4396,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.28", "wasm-bindgen-shared", ] @@ -4100,7 +4430,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.28", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4190,6 +4520,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets 0.48.1", +] + [[package]] name = "windows-sys" version = "0.45.0" @@ -4324,9 +4663,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "winnow" -version = "0.4.9" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81a2094c43cc94775293eaa0e499fbc30048a6d824ac82c0351a8c0bf9112529" +checksum = "8bd122eb777186e60c3fdf765a58ac76e41c582f1f535fbf3314434c6b58f3f7" dependencies = [ "memchr", ] @@ -4357,5 +4696,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.28", ] diff --git a/Cargo.toml b/Cargo.toml index 751221bc3a..2e347a7d8f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,12 @@ members = [ "crates/relayer-types", "crates/relayer-cli", "crates/relayer-rest", + "crates/relayer-components", + "crates/relayer-components-extra", + "crates/relayer-all-in-one", + "crates/relayer-mock", + "crates/relayer-runtime", + "crates/relayer-cosmos", "crates/telemetry", "crates/chain-registry", "tools/integration-test", diff --git a/crates/chain-registry/src/asset_list.rs b/crates/chain-registry/src/asset_list.rs index a4edc40a8c..b7592406eb 100644 --- a/crates/chain-registry/src/asset_list.rs +++ b/crates/chain-registry/src/asset_list.rs @@ -1,6 +1,5 @@ //! Contains models for serializing and deserializing `assets.json` for a given chain //! originally from - use std::path::PathBuf; use serde::{Deserialize, Serialize}; diff --git a/crates/relayer-all-in-one/Cargo.toml b/crates/relayer-all-in-one/Cargo.toml new file mode 100644 index 0000000000..c2aa6dc706 --- /dev/null +++ b/crates/relayer-all-in-one/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "ibc-relayer-all-in-one" +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" +readme = "README.md" +keywords = ["blockchain", "consensus", "cosmos", "ibc", "tendermint"] +repository = "https://github.com/informalsystems/ibc-rs" +authors = ["Informal Systems "] +rust-version = "1.68" +description = """ + Implementation of an IBC Relayer in Rust, as a library +""" + +[package.metadata.docs.rs] +all-features = true + +[features] + +[dependencies] +async-trait = "0.1.56" +futures-core = { version = "0.3", default-features = false, features = ["alloc"] } +futures-util = { version = "0.3", default-features = false, features = ["alloc"] } +ibc-relayer-components = { version = "0.1.0", path = "../relayer-components" } +ibc-relayer-components-extra = { version = "0.1.0", path = "../relayer-components-extra" } diff --git a/crates/relayer-all-in-one/src/all_for_one/birelay.rs b/crates/relayer-all-in-one/src/all_for_one/birelay.rs new file mode 100644 index 0000000000..136a6a07e7 --- /dev/null +++ b/crates/relayer-all-in-one/src/all_for_one/birelay.rs @@ -0,0 +1,36 @@ +use ibc_relayer_components::logger::traits::level::HasLoggerWithBaseLevels; +use ibc_relayer_components::relay::traits::components::auto_relayer::CanAutoRelay; +use ibc_relayer_components::relay::traits::two_way::HasTwoWayRelay; + +use crate::all_for_one::relay::AfoRelay; +use crate::all_for_one::runtime::HasAfoRuntime; + +pub trait AfoBiRelay: + Clone + + HasAfoRuntime + + HasLoggerWithBaseLevels + + CanAutoRelay + + HasTwoWayRelay +{ + type AfoRelayAToB: AfoRelay; + + type AfoRelayBToA: AfoRelay< + AfoSrcChain = ::AfoDstChain, + AfoDstChain = ::AfoSrcChain, + >; +} + +impl AfoBiRelay for BiRelay +where + RelayAToB: AfoRelay, + RelayBToA: AfoRelay, + BiRelay: Clone + + HasAfoRuntime + + HasLoggerWithBaseLevels + + CanAutoRelay + + HasTwoWayRelay, +{ + type AfoRelayAToB = RelayAToB; + + type AfoRelayBToA = RelayBToA; +} diff --git a/crates/relayer-all-in-one/src/all_for_one/builder.rs b/crates/relayer-all-in-one/src/all_for_one/builder.rs new file mode 100644 index 0000000000..607e0304ac --- /dev/null +++ b/crates/relayer-all-in-one/src/all_for_one/builder.rs @@ -0,0 +1,85 @@ +use async_trait::async_trait; +use ibc_relayer_components::build::traits::birelay::HasBiRelayType; +use ibc_relayer_components::build::traits::components::birelay_builder::CanBuildBiRelay; +use ibc_relayer_components::build::types::aliases::{ChainIdA, ChainIdB, ClientIdA, ClientIdB}; +use ibc_relayer_components::core::traits::error::HasErrorType; + +use crate::all_for_one::birelay::AfoBiRelay; +use crate::one_for_all::traits::builder::OfaBuilder; +use crate::one_for_all::types::birelay::OfaBiRelayWrapper; +use crate::one_for_all::types::builder::OfaBuilderWrapper; +use crate::std_prelude::*; + +#[async_trait] +pub trait CanBuildAfoBiRelay: HasBiRelayType + HasErrorType { + type AfoBiRelay: AfoBiRelay; + + async fn build_afo_birelay( + &self, + chain_id_a: &ChainIdA, + chain_id_b: &ChainIdB, + client_id_a: &ClientIdA, + client_id_b: &ClientIdB, + ) -> Result; +} + +#[async_trait] +impl CanBuildAfoBiRelay for Build +where + Build: CanBuildBiRelay, + Build::BiRelay: AfoBiRelay, +{ + type AfoBiRelay = Build::BiRelay; + + async fn build_afo_birelay( + &self, + chain_id_a: &ChainIdA, + chain_id_b: &ChainIdB, + client_id_a: &ClientIdA, + client_id_b: &ClientIdB, + ) -> Result { + self.build_birelay(chain_id_a, chain_id_b, client_id_a, client_id_b) + .await + } +} + +#[async_trait] +pub trait CanBuildAfoBiRelayFromOfa: + HasBiRelayType + HasErrorType +{ + type AfoBiRelay: AfoBiRelay; + + async fn build_afo_birelay_from_ofa( + &self, + chain_id_a: &ChainIdA, + chain_id_b: &ChainIdB, + client_id_a: &ClientIdA, + client_id_b: &ClientIdB, + ) -> Result; +} + +#[async_trait] +impl CanBuildAfoBiRelayFromOfa for OfaBuilderWrapper +where + Build: OfaBuilder, + // OfaBuilderWrapper: CanBuildBiRelayFromRelays +{ + type AfoBiRelay = OfaBiRelayWrapper; + + async fn build_afo_birelay_from_ofa( + &self, + chain_id_a: &ChainIdA, + chain_id_b: &ChainIdB, + client_id_a: &ClientIdA, + client_id_b: &ClientIdB, + ) -> Result, Build::Error> { + as CanBuildAfoBiRelay>::build_afo_birelay( + self, + chain_id_a, + chain_id_b, + client_id_a, + client_id_b, + ) + .await + } +} diff --git a/crates/relayer-all-in-one/src/all_for_one/chain.rs b/crates/relayer-all-in-one/src/all_for_one/chain.rs new file mode 100644 index 0000000000..e5b8d01520 --- /dev/null +++ b/crates/relayer-all-in-one/src/all_for_one/chain.rs @@ -0,0 +1,99 @@ +use ibc_relayer_components::chain::traits::client::create::HasCreateClientOptions; +use ibc_relayer_components::chain::traits::components::chain_status_querier::CanQueryChainStatus; +use ibc_relayer_components::chain::traits::components::consensus_state_querier::CanQueryConsensusState; +use ibc_relayer_components::chain::traits::components::packet_fields_reader::CanReadPacketFields; +use ibc_relayer_components::chain::traits::queries::packet_commitments::CanQueryPacketCommitments; +use ibc_relayer_components::chain::traits::queries::received_packet::CanQueryReceivedPacket; +use ibc_relayer_components::chain::traits::queries::send_packet::CanQuerySendPacketsFromSequences; +use ibc_relayer_components::chain::traits::queries::unreceived_packets::CanQueryUnreceivedPacketSequences; +use ibc_relayer_components::chain::traits::types::chain::HasChainTypes; +use ibc_relayer_components::chain::traits::types::channel::{ + HasChannelHandshakePayloads, HasInitChannelOptionsType, +}; +use ibc_relayer_components::chain::traits::types::connection::{ + HasConnectionHandshakePayloads, HasInitConnectionOptionsType, +}; +use ibc_relayer_components::chain::traits::types::consensus_state::HasConsensusStateType; +use ibc_relayer_components::chain::traits::types::ibc::HasIbcChainTypes; +use ibc_relayer_components::chain::traits::types::ibc_events::write_ack::HasWriteAcknowledgementEvent; +use ibc_relayer_components::chain::traits::types::packet::HasIbcPacketTypes; +use ibc_relayer_components::logger::traits::level::HasLoggerWithBaseLevels; +use ibc_relayer_components_extra::telemetry::traits::telemetry::HasTelemetry; + +use crate::all_for_one::runtime::HasAfoRuntime; + +pub trait AfoChain: + Clone + + HasAfoRuntime + + HasLoggerWithBaseLevels + + HasTelemetry + + HasChainTypes + + CanQueryChainStatus + + HasIbcChainTypes + + CanReadPacketFields + + HasWriteAcknowledgementEvent + + HasConsensusStateType + + CanQueryConsensusState + + CanQueryReceivedPacket + + CanQueryPacketCommitments + + CanQueryUnreceivedPacketSequences + + CanQuerySendPacketsFromSequences + + HasCreateClientOptions + + HasInitConnectionOptionsType + + HasConnectionHandshakePayloads + + HasInitChannelOptionsType + + HasChannelHandshakePayloads +where + Counterparty: AfoCounterpartyChain, +{ +} + +pub trait AfoCounterpartyChain: + HasIbcChainTypes + + HasConsensusStateType + + HasIbcPacketTypes< + Chain, + IncomingPacket = Chain::OutgoingPacket, + OutgoingPacket = Chain::IncomingPacket, + > +where + Chain: CanReadPacketFields, +{ +} + +impl AfoChain for Chain +where + Counterparty: AfoCounterpartyChain, + Chain: Clone + + HasAfoRuntime + + HasLoggerWithBaseLevels + + HasTelemetry + + HasChainTypes + + CanQueryChainStatus + + CanReadPacketFields + + HasWriteAcknowledgementEvent + + HasConsensusStateType + + CanQueryConsensusState + + CanQueryReceivedPacket + + CanQueryPacketCommitments + + CanQueryUnreceivedPacketSequences + + CanQuerySendPacketsFromSequences + + HasCreateClientOptions + + HasInitConnectionOptionsType + + HasConnectionHandshakePayloads + + HasInitChannelOptionsType + + HasChannelHandshakePayloads, +{ +} + +impl AfoCounterpartyChain for Counterparty +where + Chain: CanReadPacketFields, + Counterparty: HasConsensusStateType + + CanReadPacketFields< + Chain, + IncomingPacket = Chain::OutgoingPacket, + OutgoingPacket = Chain::IncomingPacket, + >, +{ +} diff --git a/crates/relayer-all-in-one/src/all_for_one/mod.rs b/crates/relayer-all-in-one/src/all_for_one/mod.rs new file mode 100644 index 0000000000..58bc64daec --- /dev/null +++ b/crates/relayer-all-in-one/src/all_for_one/mod.rs @@ -0,0 +1,9 @@ +/*! + The all-for-one consumer constructs for the minimal relayer. +*/ + +pub mod birelay; +pub mod builder; +pub mod chain; +pub mod relay; +pub mod runtime; diff --git a/crates/relayer-all-in-one/src/all_for_one/relay.rs b/crates/relayer-all-in-one/src/all_for_one/relay.rs new file mode 100644 index 0000000000..a47e019cca --- /dev/null +++ b/crates/relayer-all-in-one/src/all_for_one/relay.rs @@ -0,0 +1,96 @@ +use ibc_relayer_components::chain::types::aliases::{IncomingPacket, OutgoingPacket}; +use ibc_relayer_components::logger::traits::level::HasLoggerWithBaseLevels; +use ibc_relayer_components::relay::traits::chains::HasRelayChains; +use ibc_relayer_components::relay::traits::channel::open_handshake::CanRelayChannelOpenHandshake; +use ibc_relayer_components::relay::traits::channel::open_init::CanInitChannel; +use ibc_relayer_components::relay::traits::components::auto_relayer::CanAutoRelay; +use ibc_relayer_components::relay::traits::components::client_creator::CanCreateClient; +use ibc_relayer_components::relay::traits::components::event_relayer::CanRelayEvent; +use ibc_relayer_components::relay::traits::components::ibc_message_sender::{ + CanSendIbcMessages, MainSink, +}; +use ibc_relayer_components::relay::traits::components::packet_clearer::CanClearPackets; +use ibc_relayer_components::relay::traits::components::packet_filter::CanFilterPackets; +use ibc_relayer_components::relay::traits::components::packet_relayer::CanRelayPacket; +use ibc_relayer_components::relay::traits::components::packet_relayers::ack_packet::CanRelayAckPacket; +use ibc_relayer_components::relay::traits::components::packet_relayers::receive_packet::CanRelayReceivePacket; +use ibc_relayer_components::relay::traits::components::packet_relayers::timeout_unordered_packet::CanRelayTimeoutUnorderedPacket; +use ibc_relayer_components::relay::traits::components::update_client_message_builder::CanBuildUpdateClientMessage; +use ibc_relayer_components::relay::traits::connection::open_handshake::CanRelayConnectionOpenHandshake; +use ibc_relayer_components::relay::traits::connection::open_init::CanInitConnection; +use ibc_relayer_components::relay::traits::target::{DestinationTarget, SourceTarget}; +use ibc_relayer_components_extra::relay::components::packet_relayers::retry::SupportsPacketRetry; + +use crate::all_for_one::chain::AfoChain; +use crate::all_for_one::runtime::HasAfoRuntime; + +/// The functionality that a relay context gains access to once that relay +/// context implements the `OfaRelayWrapper` trait. +pub trait AfoRelay: + Clone + + HasAfoRuntime + + HasLoggerWithBaseLevels + + HasRelayChains + + CanBuildUpdateClientMessage + + CanBuildUpdateClientMessage + + CanSendIbcMessages + + CanSendIbcMessages + + CanRelayEvent + + CanRelayEvent + + CanAutoRelay + + CanFilterPackets + + CanRelayReceivePacket + + CanRelayPacket + + CanRelayAckPacket + + CanRelayTimeoutUnorderedPacket + + CanCreateClient + + CanCreateClient + + CanInitConnection + + CanRelayConnectionOpenHandshake + + CanInitChannel + + CanRelayChannelOpenHandshake + + SupportsPacketRetry + + CanClearPackets +{ + type AfoSrcChain: AfoChain; + + type AfoDstChain: AfoChain< + Self::AfoSrcChain, + IncomingPacket = OutgoingPacket, + OutgoingPacket = IncomingPacket, + >; +} + +impl AfoRelay for Relay +where + SrcChain: AfoChain, + DstChain: AfoChain, + Relay: Clone + + HasAfoRuntime + + HasLoggerWithBaseLevels + + HasRelayChains + + CanBuildUpdateClientMessage + + CanBuildUpdateClientMessage + + CanSendIbcMessages + + CanSendIbcMessages + + CanRelayEvent + + CanRelayEvent + + CanAutoRelay + + CanFilterPackets + + CanRelayReceivePacket + + CanRelayPacket + + CanRelayAckPacket + + CanRelayTimeoutUnorderedPacket + + CanCreateClient + + CanCreateClient + + CanInitConnection + + CanRelayConnectionOpenHandshake + + CanInitChannel + + CanRelayChannelOpenHandshake + + SupportsPacketRetry + + CanClearPackets, +{ + type AfoSrcChain = SrcChain; + + type AfoDstChain = DstChain; +} diff --git a/crates/relayer-all-in-one/src/all_for_one/runtime.rs b/crates/relayer-all-in-one/src/all_for_one/runtime.rs new file mode 100644 index 0000000000..054c133b5c --- /dev/null +++ b/crates/relayer-all-in-one/src/all_for_one/runtime.rs @@ -0,0 +1,57 @@ +use ibc_relayer_components::runtime::traits::mutex::HasMutex; +use ibc_relayer_components::runtime::traits::runtime::HasRuntime; +use ibc_relayer_components::runtime::traits::sleep::CanSleep; +use ibc_relayer_components::runtime::traits::time::HasTime; +use ibc_relayer_components_extra::runtime::traits::channel::{ + CanCloneSender, CanCreateChannels, CanStreamReceiver, CanUseChannels, HasChannelTypes, +}; +use ibc_relayer_components_extra::runtime::traits::channel_once::{ + CanCreateChannelsOnce, CanUseChannelsOnce, HasChannelOnceTypes, +}; +use ibc_relayer_components_extra::runtime::traits::spawn::HasSpawner; + +pub trait AfoRuntime: + Clone + + HasMutex + + CanSleep + + HasTime + + HasSpawner + + HasChannelTypes + + HasChannelOnceTypes + + CanCreateChannels + + CanCreateChannelsOnce + + CanStreamReceiver + + CanCloneSender + + CanUseChannels + + CanUseChannelsOnce +{ +} + +impl AfoRuntime for Runtime where + Runtime: Clone + + HasMutex + + CanSleep + + HasTime + + HasSpawner + + HasChannelTypes + + HasChannelOnceTypes + + CanCreateChannels + + CanCreateChannelsOnce + + CanStreamReceiver + + CanCloneSender + + CanUseChannels + + CanUseChannelsOnce +{ +} + +pub trait HasAfoRuntime: HasRuntime { + type AfoRuntime: AfoRuntime; +} + +impl HasAfoRuntime for Context +where + Context: HasRuntime, + Runtime: AfoRuntime, +{ + type AfoRuntime = Runtime; +} diff --git a/crates/relayer-all-in-one/src/docs/context_generic_programming.rs b/crates/relayer-all-in-one/src/docs/context_generic_programming.rs new file mode 100644 index 0000000000..1559484f7b --- /dev/null +++ b/crates/relayer-all-in-one/src/docs/context_generic_programming.rs @@ -0,0 +1,687 @@ +/*! + # A Crash Course on Context-Generic Programming + + This section provides a quick introduction to context-generic programming, + which is a programming technique that is used heavily by the IBC relayer + framework. For a more gentle and complete introduction to context-generic + programming, check out our full tutorial on + [context-generic programming](https://informalsystems.github.io/context-generic-programming/). + + In its essence, context-generic programming takes regular methods + implemented in object-oriented programming (OOP) style, and turn + them into modular components that can work over a generic context. + + For example, consider the following OOP-style code: + + ```rust + # mod example { + pub mod provider { + pub struct AppContext { /* ... */ } + impl AppContext { + pub fn perform_action(&self, /* ... */) { + # unimplemented!() + /* ... */ + } + + pub fn perform_another_action(&self, /* ... */) { + # unimplemented!() + /* ... */ + } + } + } + + mod consumer { + # use super::provider::AppContext; + fn do_something(context: AppContext, /* .... */) { + /* ... */ + context.perform_action(/* ... */); + /* ... */ + } + } + # } + ``` + + We have a `provider` module that implements a concrete `AppContext` type, + and two public methods, `perform_action`, and `perform_another_action`. + Other than that, we also have a `consumer` module that uses the `AppContext` + type provided by the `provider` module, as well as the `perform_action` + method. + + In practice, the `provider` and `consumer` modules may be different crates, + developed by different teams. However the use of the `AppContext` type and the + `perform_action` method tightly couples the `consumer` module with the + provider module. This may come with many downsides, in particular if the + `AppContext` type implemented by the `provider` module is very complex. + Aside from `perform_action`, the `provider` module may also implement + many other methods, which the `consumer` module do not really need, + such as `perform_another_action`. + + Context-generic programming offers a way to break down the monolithic design + of provider methods, and turn them into modular components that can be + selectively chosen by the consumers. Furthermore, the concrete context type + is replaced with a generic context parameter, so that different context + types can be used to implement the components depending on the subset of + components that are needed by the consumers. + + ## Consumer and Provider Traits + + With context-generic programming, we would first define the following traits + that correspond to the original `perform_action` method: + + ``` + trait CanPerformAction { + fn perform_action(&self, /* ... */); + } + + trait ActionPerformer { + fn perform_action(context: &Context, /* ... */); + } + ``` + + The `CanPerformAction` trait is a _consumer_ trait that is designed to + be used by _consumers_ of a component. On the other hand, the `ActionPerformer` + trait is a _provider_ trait that is designed to be used by _implementers_ + of a component. + + If we carefully contrast between the two traits, the main difference is + that in the consumer trait `CanPerformAction`, there is an implicit `Self` + type and a `&self` parameter. On the other hand, the provider trait + `ActionPerformer` has an explicit `Context` type parameter and a `context` + argument, which corresponds to the self parameters in the `CanPerformAction` + trait. + + In context-generic programming, we have a separation of consumer and + provider traits to separate the concerns of _using_ a component as + compared to _implementing_ a component. By doing so, we can allow + multiple implementations of a component to co-exist, while the users + of a component can assume that a generic context always provide + exactly one unique implementation of a component. + + ## Using a Consumer Trait + + We first look at how the `consumer` module can be modified to use the + consumer trait `CanPerformAction`. + + ``` + mod consumer { + # trait CanPerformAction { + # fn perform_action(&self, /* ... */); + # } + fn do_something(context: Context, /* .... */) + where + Context: CanPerformAction, + { + /* ... */ + context.perform_action(/* ... */); + /* ... */ + } + } + ``` + + Instead of using a concrete context directly, our consumer function is now + defined to be generic over any `Context` type, given that `Context` + implements `CanPerformAction`. Since the consumer trait `CanPerformAction` + makes refers to `Context` as `Self`, the consumer can call + `context.perform_action()` the same way as it did in the original OOP-style + program. + + ## Implementing a Provider Trait + + We now look at how a `provider` module can implement the action performer + component: + + ``` + mod provider { + # trait HasRequirementsToPerformSimpleAction {} + # trait ActionPerformer { + # fn perform_action(context: &Context, /* ... */); + # } + struct PerformSimpleAction; + impl ActionPerformer for PerformSimpleAction + where + Context: HasRequirementsToPerformSimpleAction, + { + fn perform_action(context: &Context, /* ... */) { + # unimplemented!() + /* ... */ + } + } + } + ``` + + The `provider` module first defines a `PerformSimpleAction` struct, which + is used to represent an implementation of the action performer component + at the _type level_. + + We then implement `ActionPerformer` for `PerformSimpleAction`, + for any generic `Context` type, as long as the type satisfies some requirements + as specified in `HasRequirementsToPerformSimpleAction`. + + One thing worth noting is that the constraint `HasRequirementsToPerformSimpleAction` + is _hidden_ behind the `impl` definition, as it is not shown in the + `ActionPerformer` trait itself. + Essentially, we have made use of the properties of Rust's trait system + to perform _dependency injection_ to propagate the constraints of + a particular implementation without polluting the trait signature of + either the producer or consumer traits. + + ## Linking Consumer and Provider traits + + At this stage, the `provider` module has defined a generic implementation + of `PerformSimpleAction`, but it has yet to implement `CanPerformAction` + for its concrete `AppContext`. We can now implement `CanPerformAction` + as follows: + + ``` + pub mod provider { + # trait HasRequirementsToPerformSimpleAction {} + # trait CanPerformAction { + # fn perform_action(&self, /* ... */); + # } + # trait ActionPerformer { + # fn perform_action(context: &Context, /* ... */); + # } + # struct PerformSimpleAction; + # impl ActionPerformer for PerformSimpleAction + # where + # Context: HasRequirementsToPerformSimpleAction, + # { + # fn perform_action(context: &Context, /* ... */) { + # unimplemented!() + # /* ... */ + # } + # } + pub struct AppContext { /* ... */ } + impl HasRequirementsToPerformSimpleAction for AppContext { /* ... */ } + + impl CanPerformAction for AppContext { + fn perform_action(&self, /* ... */) { + PerformSimpleAction::perform_action(self, /* ... */) + } + } + } + ``` + + We first ensure that the concrete type `AppContext` implements the + requirements that are needed to call `PerformSimpleAction`, such as by + implementing `HasRequirementsToPerformSimpleAction`. We then implement + `CanPerformAction` by simply forwarding to the call to + `PerformSimpleAction::perform_action`. + + ## Benefits of separating provider and consumer traits + + A benefit of separating the actual implementation of a provider + trait from the implementation of a consumer trait is that we can + reuse the same provider implementation on other consumer trait + implementations. For example, let's say the provider defines + a second concrete context `AnotherAppContext`, it would then + still able to reuse `PerformSimpleAction` without having to + copy the entire implementation code: + + ``` + pub mod provider { + # trait HasRequirementsToPerformSimpleAction {} + # trait CanPerformAction { + # fn perform_action(&self, /* ... */); + # } + # trait ActionPerformer { + # fn perform_action(context: &Context, /* ... */); + # } + # struct PerformSimpleAction; + # impl ActionPerformer for PerformSimpleAction + # where + # Context: HasRequirementsToPerformSimpleAction, + # { + # fn perform_action(context: &Context, /* ... */) { + # unimplemented!() + # /* ... */ + # } + # } + pub struct AnotherAppContext { /* ... */ } + # impl HasRequirementsToPerformSimpleAction for AnotherAppContext { /* ... */ } + impl CanPerformAction for AnotherAppContext { + fn perform_action(&self, /* ... */) { + PerformSimpleAction::perform_action(self, /* ... */) + } + } + } + ``` + + The separation also allows us to easily switch between different provider + components. For example, let's say there is another implementation of + `ActionPerformer`, `PerformComplexAction` that can be used by contexts + that implement additional requirements `HasAdditionalRequirementsToPerformAction`. + If the provider's `AppContext` satisfies the additional requirements, + we can then switch to the other component with a single line change: + + ``` + pub mod provider { + # trait HasAdditionalRequirementsToPerformAction {} + # trait CanPerformAction { + # fn perform_action(&self, /* ... */); + # } + # trait ActionPerformer { + # fn perform_action(context: &Context, /* ... */); + # } + # struct PerformComplexAction; + # impl ActionPerformer for PerformComplexAction + # where + # Context: HasAdditionalRequirementsToPerformAction, + # { + # fn perform_action(context: &Context, /* ... */) { + # unimplemented!() + # /* ... */ + # } + # } + pub struct AppContext { /* ... */ } + impl HasAdditionalRequirementsToPerformAction for AppContext { /* ... */ } + + impl CanPerformAction for AppContext { + fn perform_action(&self, /* ... */) { + PerformComplexAction::perform_action(self, /* ... */) + } + } + } + ``` + + ## Dual roles of providers and consumers + + A key idea in context-generic programming is that components can be + _both_ provider and consumers at the same time. + + For example, instead of having the `consumer` module to define a function + `do_something` that is generic over a context, we can turn the generic function + into a context-generic component. After all, the function `do_something` + is in fact a provider for some other modules that call that function. + + Our `consumer` module can as introduce a new something-doer component follows: + + ``` + mod consumer { + # trait CanPerformAction { + # fn perform_action(&self, /* ... */); + # } + # trait HasRequirementsToDoSomethingSmart {} + trait CanDoSomething { + fn do_something(&self, /* ... */); + } + trait SomethingDoer { + fn do_something(context: &Context, /* ... */); + } + + struct DoSomethingSmart; + impl SomethingDoer for DoSomethingSmart + where + Context: CanPerformAction, + Context: HasRequirementsToDoSomethingSmart, + { + fn do_something(context: &Context, /* ... */) { + /* ... */ + context.perform_action(/* ... */); + /* ... */ + } + } + } + ``` + + We now has a `CanDoSomething` consumer trait, and a `SomethingDoer` + provider trait. We then define a `DoSomethingSmart` component that + implements `SomethingDoer` for any generic `Context`, provided that + the `Context` provides `CanPerformAction`. + + With that, if a concrete context provides `CanPerformAction`, it can + also provide `CanDoSomething` by calling `DoSomethingSmart` in its + implementation for `CanDoSomething`. + + It is worth noting that the transitive dependency + `HasRequirementsToPerformSimpleAction` is not seen anywhere inside the + `consumer` module. In other words, the `consumer` module is _decoupled_ + from the requirements of implementing `CanPerformAction`. If the + concrete context implements `CanPerformAction` by using + `PerformSimpleAction`, then the constraints are propagated _automatically_ + while completely bypassing the definitions in `consumer`. + + ## All-In-One Traits + + Context-generic programming provides a lot of flexibility for context + implementations to choose on how to wire up consumer traits with provider + trait implementations. The late-bound nature of dependency injection + also means that we do not know the exact requirements the concrete + context needs to provide until all of the wirings have been decided. + + To make it easy for normal users to use the components, we can provide + an optional set of _all-in-one_ traits so that users can implement only + a handful of traits for their concrete contexts, and let the framework + do the wiring for them. The all-in-one traits provides the convenience of + less wirings, with the trade off of less customizability. + + One way to think of this is that we can imagine the all-in-one traits + being like a motherboard of a computer. It provides a fixed number of + sockets which you can only plug in with compatible components. If you + want to use a different component like an unsupported CPU, you would + have to get a different motherboard or build a new motherboard. But that + doesn't mean you need to rebuild everything from scratch, because you + can still reuse the other components from the old motherboard, such as + RAM and storage. + + ## One-For-All Traits + + The _one-for-all_ (OFA) trait is an all-in-one _provider_ trait that + the concrete context providers can use to implement low-level features. + The term one-for-all means here: + _implement this one trait, then all other traits are implemented for you automatically_. + + The one-for-all trait achieve this by gathering all the low-level + requirements requirements and define them in a single trait. Consider + our previous examples, we have the `PerformSimpleAction` and `DoSomethingSmart` + components that can be reused. However in order to use them, the context + would also have to satisfy the constraints + `HasRequirementsToPerformSimpleAction` and `HasRequirementsToDoSomethingSmart`. + + If we want to provide an all-in-one framework to implement contexts + that provides the consumer traits `PerformSimpleAction` and + `DoSomethingSmart`, it is sufficient that the context provides the + low level implementations of `HasRequirementsToPerformSimpleAction` + and `HasRequirementsToDoSomethingSmart`, and then delegate the high-level + implementations for `PerformSimpleAction` and `DoSomethingSmart`. + + We can do so with the following one-for-all definitions: + + ``` + trait OfaContext { + fn gather_requirements_to_perform_simple_action(); + + fn gather_requirements_to_do_something_smart(); + } + + struct OfaWrapper { + context: Context, + } + ``` + + The trait `OfaContext` defines the abstract methods a concrete context + needs to implement. The `OfaWrapper` struct is a wrapper struct that + wraps any `Context` type that implements `OfaContext`. Using the + methods provided in `OfaContext`, we can now implement `CanPerformAction` + for any `OfaWrapper` as follows: + + ``` + # trait HasRequirementsToPerformSimpleAction {} + # trait CanPerformAction { + # fn perform_action(&self, /* ... */); + # } + # trait ActionPerformer { + # fn perform_action(context: &Context, /* ... */); + # } + # struct PerformSimpleAction; + # impl ActionPerformer for PerformSimpleAction + # where + # Context: HasRequirementsToPerformSimpleAction, + # { + # fn perform_action(context: &Context, /* ... */) { + # unimplemented!() + # } + # } + # trait OfaContext { + # fn gather_requirements_to_perform_simple_action(); + # fn gather_requirements_to_do_something_smart(); + # } + # struct OfaWrapper { + # context: Context, + # } + impl + HasRequirementsToPerformSimpleAction + for OfaWrapper + where + Context: OfaContext, + { /* ... */ } + + impl CanPerformAction for OfaWrapper + where + Context: OfaContext, + { + fn perform_action(&self, /* ... */) { + PerformSimpleAction::perform_action(self, /* ... */) + } + } + ``` + + We first implement `HasRequirementsToPerformSimpleAction` for + `OfaWrapper`, given that if `Context` implements `OfaContext`. + The implementation can make use of the methods in `OfaContext`, + such as `gather_requirements_to_perform_simple_action`, to satisfy + the requirements for `HasRequirementsToPerformSimpleAction`. + + We then implement `CanPerformAction` for `OfaWrapper`, + also given that if `Context` implements `OfaContext`. The implementation + simply delegates the call to `PerformSimpleAction`, and this is allowed + because the constraint `HasRequirementsToPerformSimpleAction` has already + been satisfied. + + Similarly, we can implement `CanDoSomething` for `OfaWrapper` + following the same convention: + + ``` + # trait HasRequirementsToDoSomethingSmart {} + # trait CanDoSomething { + # fn do_something(&self, /* ... */); + # } + # trait SomethingDoer { + # fn do_something(context: &Context, /* ... */); + # } + # struct DoSomethingSmart; + # impl SomethingDoer for DoSomethingSmart + # where + # Context: HasRequirementsToDoSomethingSmart, + # { + # fn do_something(context: &Context, /* ... */) { + # } + # } + # trait OfaContext { + # fn gather_requirements_to_perform_simple_action(); + # fn gather_requirements_to_do_something_smart(); + # } + # struct OfaWrapper { + # context: Context, + # } + impl HasRequirementsToDoSomethingSmart + for OfaWrapper + where + Context: OfaContext, + { /* ... */ } + + impl CanDoSomething for OfaWrapper + where + Context: OfaContext, + { + fn do_something(&self, /* ... */) { + DoSomethingSmart::do_something(self, /* ... */) + } + } + ``` + + With the blanket implementations by `OfaWrapper`, a concrete context like + `AppContext` now only needs to implement `OfaContext`. It can then + call methods like `perform_action` and `do_something` by wrapping + the concrete context into `OfaWrapper`. + + ## All-For-One Traits + + The _all-for-one_ (AFO) trait is an all-in-one _consumer_ trait that + consumers of a generic context can use to consume all features offered + by the context. The term all-for-one means here: + _import all the traits available by importing only this one trait_. + + The all-for-one trait achieve this by specifying all consumer traits + as its _parent_ trait. With that, when the all-for-one trait is imported + in the `where` clause, Rust's trait system would help to also automatically + import all its parent traits and make them available for use. + + Using our previous examples, consider an external consumer that want to + use both `CanPerformAction` and `CanDoSomething`. They can define a + context-generic function as follows: + + ``` + # trait CanPerformAction { + # fn perform_action(&self, /* ... */); + # } + # trait CanDoSomething { + # fn do_something(&self, /* ... */); + # } + fn run_assembly(context: Context, /* ... */) + where + Context: CanPerformAction + CanDoSomething, + { /* ... */ } + ``` + + With only two components, it is still manageable to list everything as + explicit constraints. But as the number of components increase, the + explicit list can become tedious to repeat. + + We can instead define an all-for-one context as follows: + + ``` + # trait CanPerformAction { + # fn perform_action(&self, /* ... */); + # } + # trait CanDoSomething { + # fn do_something(&self, /* ... */); + # } + trait AfoContext: CanPerformAction + CanDoSomething {} + impl AfoContext for Context + where + Context: CanPerformAction + CanDoSomething, + {} + ``` + + We define the `AfoContext` with `CanPerformAction` and `CanDoSomething` + as its parent traits. We then immediately define a blanket implementation + for `AfoContext` for any generic `Context` type that implements + `CanPerformAction` and `CanDoSomething`. This means that `AfoContext` + is automatically implemented for any `Context` type that implements + `CanPerformAction` and `CanDoSomething`. + + With that, functions that consume any subset of the components can simply + import `AfoContext` without having to know the detailed traits for + each API they want to use: + + ``` + # trait AfoContext { } + fn run_assembly(context: Context, /* ... */) + where + Context: AfoContext, + { /* ... */ } + ``` + + Note that the all-for-one trait is meant for _external_ consumption. + This means that any internal components such as `PerformSimpleAction` + and `DoSomethingSmart` should _not_ use `AfoContext` to import their + dependencies, as otherwise it would cause cyclic dependencies in the + implementation graph. + + ## One-For-All and All-For-One + + The main difference between a one-for-all trait and an all-for-one trait + is that the former is a _provider_ trait to be used by concrete context + implementers, while the latter is a _consumer_ trait to be used by + consumers of a generic context. + + There is also a relation between one-for-all and all-for-one, in that + for any collection of components, an implementation of the one-for-all trait + for that collection should be sufficient to implement all requirements + in the all-in-one trait. + + In other words, we can construct a _proof_ that one-for-all implements + all-for-one: + + ``` + # trait CanPerformAction { + # fn perform_action(&self, /* ... */); + # } + # trait ActionPerformer { + # fn perform_action(context: &Context, /* ... */); + # } + # struct PerformSimpleAction; + # impl ActionPerformer for PerformSimpleAction + # { + # fn perform_action(context: &Context, /* ... */) { + # unimplemented!() + # } + # } + # trait CanDoSomething { + # fn do_something(&self, /* ... */); + # } + # trait SomethingDoer { + # fn do_something(context: &Context, /* ... */); + # } + # struct DoSomethingSmart; + # impl SomethingDoer for DoSomethingSmart + # { + # fn do_something(context: &Context, /* ... */) { + # } + # } + # trait OfaContext {} + # struct OfaWrapper { + # context: Context, + # } + # impl CanDoSomething for OfaWrapper + # where + # Context: OfaContext, + # { + # fn do_something(&self, /* ... */) { + # DoSomethingSmart::do_something(self, /* ... */) + # } + # } + # impl CanPerformAction for OfaWrapper + # where + # Context: OfaContext, + # { + # fn perform_action(&self, /* ... */) { + # PerformSimpleAction::perform_action(self, /* ... */) + # } + # } + # trait AfoContext: CanPerformAction + CanDoSomething {} + # impl AfoContext for Context + # where + # Context: CanPerformAction + CanDoSomething, + # {} + fn ofa_implements_afo(context: Context) -> impl AfoContext + where + Context: OfaContext, + { + OfaWrapper { context } + } + ``` + + In the function `ofa_implements_afo`, it accepts any generic context + type `Context` that implements `OfaContext`. It then wraps the context + inside `OfaWrapper` and returns the wrapped context. However the + return type of the function is `impl AfoContext`, indicating that it + is an _existential type_ that implements `AfoContext`. But since the + actual return type is `OfaWrapper`, Rust would have to + automatically ensures that `OfaWrapper` implements `AfoContext`. + + From the `impl` definitions for `OfaWrapper` earlier, we can confirm + that `OfaWrapper` indeed implements `CanPerformAction` and `CanDoSomething`, + which in turns satisfies all the requirements for `AfoContext`. Behind + the scene, Rust also automatically confirm that the requirements are + satisfied, and thus the proof compiles successfully. + + Proof functions like `ofa_implements_afo` are useful for us to statically + checks that the relation between our one-for-all trait and our all-for-one + trait are _sound_. If we were to make a mistake and miss something, such as + forgetting to implement `HasAdditionalRequirementsToPerformAction`, + this would result in a compile-time error on the `ofa_implements_afo`. + This helps us to catch any mistakes early, and fix it before the concrete + context is used anywhere. + + ## Conclusion + + This concludes the short introduction to context-generic programming. + With this, you should be able to start understanding the design patterns + used in the IBC relayer framework. + + To learn more in depth techniques that are used by the relayer framework, + check out our full tutorial on context-generic programming + [here](https://informalsystems.github.io/context-generic-programming/). +*/ diff --git a/crates/relayer-all-in-one/src/docs/mod.rs b/crates/relayer-all-in-one/src/docs/mod.rs new file mode 100644 index 0000000000..fb5ca163b0 --- /dev/null +++ b/crates/relayer-all-in-one/src/docs/mod.rs @@ -0,0 +1,5 @@ +/*! + Documentation-only module to contain long-form documentation and tutorials. +*/ + +pub mod context_generic_programming; diff --git a/crates/relayer-all-in-one/src/lib.rs b/crates/relayer-all-in-one/src/lib.rs new file mode 100644 index 0000000000..5f4c90ccf5 --- /dev/null +++ b/crates/relayer-all-in-one/src/lib.rs @@ -0,0 +1,90 @@ +#![no_std] +#![allow(clippy::type_complexity)] +#![allow(clippy::too_many_arguments)] +#![allow(clippy::needless_lifetimes)] + +/*! + ## Overview + + The IBC relayer framework provides a _fully abstract_ implementation of + IBC relayer. Users can import this crate as a library to implement a + concrete relayer instance for use in different use cases and environments. + + There is currently one official concrete implementation of the relayer, + `ibc-relayer-cosmos`, which implements IBC relaying between two Cosmos + SDK chains. There are also plans to provide other implementations of the + relayer, such as support for non-SDK chains and non-Cosmos chains. + + ## Context-Generic Programming + + The IBC relayer framework is implemented using a new design pattern we + developed, known as _context-generic programming_. + + For a quick introduction, check here for a + [crash course on context-generic programming](crate::docs::context_generic_programming) + + ## All-In-One Traits + + The relayer framework provides + _[all-in-one traits](crate::docs::context_generic_programming#all-in-one-traits)_ + for users to easily implement and use custom relayers. The all-in-one traits are + configured with a _preset_ list of components, and is best suited + for users who find the presets to be sufficient. + + A good starting point to learn about the all-in-one traits is to look at + the _one-for-all_ consumer traits like + [`OfaChain`](one_for_all::traits::chain::OfaChain) and + [`OfaRelay`](one_for_all::traits::relay::OfaRelay). + + The all-in-one traits provided by the relayer framework for convenience, + and they are _not_ meant to cover all possible use cases of using the relayer. + If users want to customize further on how the relayer should + behave, they can skip the all-in-one traits and make use + of context-generic programming to implement their own all-in-one + traits, or to implement the relay context directly by implementing + the individual traits. + + ## Relayer Framework Internals + + For basic users who just want quick and easy way to creat custom relayers, + it is usually sufficient to learn how to use the all-in-one traits like + [`OfaRelay`](crate::one_for_all::traits::relay::OfaRelay). + But for power users who want to have more customization, or developers + who are maintaining the relayer framework itself, it is necessary to + have a deeper understanding of how context-generic programming works, + and explore the individual components that are defined in the + relayer framework. + + A good starting point to understand the relayer framework internals + is to look at how abstract types are defined in + [`HasChainTypes`](ibc_relayer_components::chain::traits::types::chain::HasChainTypes) and + [`HasRelayChains`](ibc_relayer_components::relay::traits::chains::HasRelayChains). + There are also simple components like + [`CanQueryChainStatus`](ibc_relayer_components::chain::traits::components::chain_status_querier::CanQueryChainStatus) + that can be understood as standalone pieces. + + The core logic of IBC relaying is encapsulated behind the + [`CanRelayPacket`](ibc_relayer_components::relay::traits::components::packet_relayer::CanRelayPacket) trait. + The [`FullCycleRelayer`](ibc_relayer_components::relay::impls::packet_relayers::general::full_relay::FullCycleRelayer) + component is one of the top-level components that performs the full cycle of + relaying an IBC packet from a source chain to a destination chain. + + A key feature that the relayer framework provides is to allow for + customization on different strategies of how the messages should be + submitted to the chain. The + [`CanSendMessages`](ibc_relayer_components::chain::traits::message_sender::CanSendMessages) + trait provides the interface for sending messages to a chain. In contrast, + the [`CanSendIbcMessages`](ibc_relayer_components::relay::traits::components::ibc_message_sender::CanSendIbcMessages) + trait provides the interface for sending messages from a _relay_ context. The + [`SendIbcMessagesWithUpdateClient`](ibc_relayer_components::relay::impls::message_senders::update_client::SendIbcMessagesWithUpdateClient) + component is one example of an IBC message sender _middleware_ that attaches + an UpdateClient message before sending the modified message to a downstream + message sender. +*/ + +mod std_prelude; +extern crate alloc; + +pub mod all_for_one; +pub mod docs; +pub mod one_for_all; diff --git a/crates/relayer-all-in-one/src/one_for_all/impls/birelay/components.rs b/crates/relayer-all-in-one/src/one_for_all/impls/birelay/components.rs new file mode 100644 index 0000000000..dc083d9b89 --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/impls/birelay/components.rs @@ -0,0 +1,20 @@ +use ibc_relayer_components::core::traits::component::{DelegateComponent, HasComponents}; +use ibc_relayer_components::core::traits::sync::Async; +use ibc_relayer_components_extra::components::extra::birelay::ExtraBiRelayComponents; + +use crate::one_for_all::types::birelay::OfaBiRelayWrapper; +use crate::one_for_all::types::component::OfaComponents; + +impl HasComponents for OfaBiRelayWrapper +where + BiRelay: Async, +{ + type Components = ExtraBiRelayComponents; +} + +impl DelegateComponent for OfaBiRelayWrapper +where + BiRelay: Async, +{ + type Delegate = ExtraBiRelayComponents; +} diff --git a/crates/relayer-all-in-one/src/one_for_all/impls/birelay/error.rs b/crates/relayer-all-in-one/src/one_for_all/impls/birelay/error.rs new file mode 100644 index 0000000000..eda946bfc6 --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/impls/birelay/error.rs @@ -0,0 +1,11 @@ +use ibc_relayer_components::core::traits::error::HasErrorType; + +use crate::one_for_all::traits::birelay::OfaBiRelay; +use crate::one_for_all::types::birelay::OfaBiRelayWrapper; + +impl HasErrorType for OfaBiRelayWrapper +where + BiRelay: OfaBiRelay, +{ + type Error = BiRelay::Error; +} diff --git a/crates/relayer-all-in-one/src/one_for_all/impls/birelay/logger.rs b/crates/relayer-all-in-one/src/one_for_all/impls/birelay/logger.rs new file mode 100644 index 0000000000..3bb8c7080a --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/impls/birelay/logger.rs @@ -0,0 +1,20 @@ +use ibc_relayer_components::logger::traits::has_logger::{HasLogger, HasLoggerType}; + +use crate::one_for_all::traits::birelay::OfaBiRelay; +use crate::one_for_all::types::birelay::OfaBiRelayWrapper; + +impl HasLoggerType for OfaBiRelayWrapper +where + BiRelay: OfaBiRelay, +{ + type Logger = BiRelay::Logger; +} + +impl HasLogger for OfaBiRelayWrapper +where + BiRelay: OfaBiRelay, +{ + fn logger(&self) -> &Self::Logger { + self.birelay.logger() + } +} diff --git a/crates/relayer-all-in-one/src/one_for_all/impls/birelay/mod.rs b/crates/relayer-all-in-one/src/one_for_all/impls/birelay/mod.rs new file mode 100644 index 0000000000..7d47a6f099 --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/impls/birelay/mod.rs @@ -0,0 +1,5 @@ +pub mod components; +pub mod error; +pub mod logger; +pub mod runtime; +pub mod two_way; diff --git a/crates/relayer-all-in-one/src/one_for_all/impls/birelay/runtime.rs b/crates/relayer-all-in-one/src/one_for_all/impls/birelay/runtime.rs new file mode 100644 index 0000000000..f6dea570f0 --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/impls/birelay/runtime.rs @@ -0,0 +1,20 @@ +use ibc_relayer_components::core::traits::error::HasErrorType; +use ibc_relayer_components::runtime::traits::runtime::HasRuntime; + +use crate::one_for_all::traits::birelay::OfaBiRelay; +use crate::one_for_all::types::birelay::OfaBiRelayWrapper; + +impl HasRuntime for OfaBiRelayWrapper +where + BiRelay: OfaBiRelay, +{ + type Runtime = BiRelay::Runtime; + + fn runtime(&self) -> &Self::Runtime { + self.birelay.runtime() + } + + fn runtime_error(e: ::Error) -> BiRelay::Error { + BiRelay::runtime_error(e) + } +} diff --git a/crates/relayer-all-in-one/src/one_for_all/impls/birelay/two_way.rs b/crates/relayer-all-in-one/src/one_for_all/impls/birelay/two_way.rs new file mode 100644 index 0000000000..cd58ef7436 --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/impls/birelay/two_way.rs @@ -0,0 +1,27 @@ +use ibc_relayer_components::core::traits::error::HasErrorType; +use ibc_relayer_components::relay::traits::two_way::HasTwoWayRelay; + +use crate::one_for_all::traits::birelay::OfaBiRelay; +use crate::one_for_all::types::birelay::OfaBiRelayWrapper; +use crate::one_for_all::types::relay::OfaRelayWrapper; + +impl HasTwoWayRelay for OfaBiRelayWrapper +where + BiRelay: OfaBiRelay, +{ + type RelayAToB = OfaRelayWrapper; + + type RelayBToA = OfaRelayWrapper; + + fn relay_a_to_b(&self) -> &Self::RelayAToB { + self.birelay.relay_a_to_b() + } + + fn relay_b_to_a(&self) -> &Self::RelayBToA { + self.birelay.relay_b_to_a() + } + + fn relay_error(e: ::Error) -> Self::Error { + BiRelay::relay_error(e) + } +} diff --git a/crates/relayer-all-in-one/src/one_for_all/impls/builder/batch.rs b/crates/relayer-all-in-one/src/one_for_all/impls/builder/batch.rs new file mode 100644 index 0000000000..7b19a3ac5d --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/impls/builder/batch.rs @@ -0,0 +1,14 @@ +use ibc_relayer_components_extra::batch::traits::config::HasBatchConfig; +use ibc_relayer_components_extra::batch::types::config::BatchConfig; + +use crate::one_for_all::traits::builder::OfaBuilder; +use crate::one_for_all::types::builder::OfaBuilderWrapper; + +impl HasBatchConfig for OfaBuilderWrapper +where + Builder: OfaBuilder, +{ + fn batch_config(&self) -> &BatchConfig { + self.builder.batch_config() + } +} diff --git a/crates/relayer-all-in-one/src/one_for_all/impls/builder/birelay.rs b/crates/relayer-all-in-one/src/one_for_all/impls/builder/birelay.rs new file mode 100644 index 0000000000..f03749155e --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/impls/builder/birelay.rs @@ -0,0 +1,26 @@ +use async_trait::async_trait; +use ibc_relayer_components::build::traits::components::birelay_from_relay_builder::BiRelayFromRelayBuilder; + +use crate::one_for_all::traits::builder::{OfaBuilder, RelayAToB, RelayBToA}; +use crate::one_for_all::types::birelay::OfaBiRelayWrapper; +use crate::one_for_all::types::builder::OfaBuilderWrapper; +use crate::one_for_all::types::component::OfaComponents; +use crate::one_for_all::types::relay::OfaRelayWrapper; +use crate::std_prelude::*; + +#[async_trait] +impl BiRelayFromRelayBuilder> for OfaComponents +where + Build: OfaBuilder, +{ + async fn build_birelay_from_relays( + build: &OfaBuilderWrapper, + relay_a_to_b: OfaRelayWrapper>, + relay_b_to_a: OfaRelayWrapper>, + ) -> Result, Build::Error> { + let birelay = + OfaBuilder::build_birelay(build.builder.as_ref(), relay_a_to_b, relay_b_to_a).await?; + + Ok(OfaBiRelayWrapper::new(birelay)) + } +} diff --git a/crates/relayer-all-in-one/src/one_for_all/impls/builder/cache.rs b/crates/relayer-all-in-one/src/one_for_all/impls/builder/cache.rs new file mode 100644 index 0000000000..2a51dbed16 --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/impls/builder/cache.rs @@ -0,0 +1,73 @@ +use async_trait::async_trait; +use ibc_relayer_components::build::traits::cache::{HasChainCache, HasRelayCache}; +use ibc_relayer_components::build::traits::target::chain::{ChainATarget, ChainBTarget}; +use ibc_relayer_components::build::traits::target::relay::{RelayAToBTarget, RelayBToATarget}; +use ibc_relayer_components::build::types::aliases::{ + ChainACache, ChainBCache, RelayAToBCache, RelayBToACache, +}; +use ibc_relayer_components_extra::build::traits::cache::HasBatchSenderCache; + +use crate::one_for_all::traits::builder::{ + BatchSenderCacheA, BatchSenderCacheB, OfaBuilder, RelayError, +}; +use crate::one_for_all::types::builder::OfaBuilderWrapper; + +#[async_trait] +impl HasChainCache for OfaBuilderWrapper +where + Builder: OfaBuilder, +{ + fn chain_cache(&self) -> &ChainACache { + &self.chain_a_cache + } +} + +#[async_trait] +impl HasChainCache for OfaBuilderWrapper +where + Builder: OfaBuilder, +{ + fn chain_cache(&self) -> &ChainBCache { + &self.chain_b_cache + } +} + +#[async_trait] +impl HasRelayCache for OfaBuilderWrapper +where + Builder: OfaBuilder, +{ + fn relay_cache(&self) -> &RelayAToBCache { + &self.relay_a_to_b_cache + } +} + +#[async_trait] +impl HasRelayCache for OfaBuilderWrapper +where + Builder: OfaBuilder, +{ + fn relay_cache(&self) -> &RelayBToACache { + &self.relay_b_to_a_cache + } +} + +#[async_trait] +impl HasBatchSenderCache> for OfaBuilderWrapper +where + Build: OfaBuilder, +{ + fn batch_sender_cache(&self, _target: ChainATarget) -> &BatchSenderCacheA { + &self.batch_sender_cache_a + } +} + +#[async_trait] +impl HasBatchSenderCache> for OfaBuilderWrapper +where + Build: OfaBuilder, +{ + fn batch_sender_cache(&self, _target: ChainBTarget) -> &BatchSenderCacheB { + &self.batch_sender_cache_b + } +} diff --git a/crates/relayer-all-in-one/src/one_for_all/impls/builder/chain.rs b/crates/relayer-all-in-one/src/one_for_all/impls/builder/chain.rs new file mode 100644 index 0000000000..e387e00760 --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/impls/builder/chain.rs @@ -0,0 +1,39 @@ +use async_trait::async_trait; +use ibc_relayer_components::build::traits::components::chain_builder::ChainBuilder; +use ibc_relayer_components::build::traits::target::chain::{ChainATarget, ChainBTarget}; + +use crate::one_for_all::traits::builder::{ChainA, ChainB, ChainIdA, ChainIdB, OfaBuilder}; +use crate::one_for_all::types::builder::OfaBuilderWrapper; +use crate::one_for_all::types::chain::OfaChainWrapper; +use crate::one_for_all::types::component::OfaComponents; +use crate::std_prelude::*; + +#[async_trait] +impl ChainBuilder, ChainATarget> for OfaComponents +where + Builder: OfaBuilder, +{ + async fn build_chain( + builder: &OfaBuilderWrapper, + chain_id: &ChainIdA, + ) -> Result>, Builder::Error> { + let chain = builder.builder.build_chain_a(chain_id).await?; + + Ok(OfaChainWrapper::new(chain)) + } +} + +#[async_trait] +impl ChainBuilder, ChainBTarget> for OfaComponents +where + Builder: OfaBuilder, +{ + async fn build_chain( + builder: &OfaBuilderWrapper, + chain_id: &ChainIdB, + ) -> Result>, Builder::Error> { + let chain = builder.builder.build_chain_b(chain_id).await?; + + Ok(OfaChainWrapper::new(chain)) + } +} diff --git a/crates/relayer-all-in-one/src/one_for_all/impls/builder/components.rs b/crates/relayer-all-in-one/src/one_for_all/impls/builder/components.rs new file mode 100644 index 0000000000..1812d3e83f --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/impls/builder/components.rs @@ -0,0 +1,16 @@ +use ibc_relayer_components::core::traits::component::HasComponents; +use ibc_relayer_components_extra::components::extra::build::ExtraBuildComponents; +use ibc_relayer_components_extra::components::extra::closures::build::CanUseExtraBuilderComponents; + +use crate::one_for_all::traits::builder::OfaBuilder; +use crate::one_for_all::types::builder::OfaBuilderWrapper; +use crate::one_for_all::types::component::OfaComponents; + +impl HasComponents for OfaBuilderWrapper +where + Build: OfaBuilder, +{ + type Components = ExtraBuildComponents; +} + +impl CanUseExtraBuilderComponents for OfaBuilderWrapper where Build: OfaBuilder {} diff --git a/crates/relayer-all-in-one/src/one_for_all/impls/builder/error.rs b/crates/relayer-all-in-one/src/one_for_all/impls/builder/error.rs new file mode 100644 index 0000000000..05192e61b6 --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/impls/builder/error.rs @@ -0,0 +1,11 @@ +use ibc_relayer_components::core::traits::error::HasErrorType; + +use crate::one_for_all::traits::builder::OfaBuilder; +use crate::one_for_all::types::builder::OfaBuilderWrapper; + +impl HasErrorType for OfaBuilderWrapper +where + Builder: OfaBuilder, +{ + type Error = Builder::Error; +} diff --git a/crates/relayer-all-in-one/src/one_for_all/impls/builder/logger.rs b/crates/relayer-all-in-one/src/one_for_all/impls/builder/logger.rs new file mode 100644 index 0000000000..6368f6f540 --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/impls/builder/logger.rs @@ -0,0 +1,20 @@ +use ibc_relayer_components::logger::traits::has_logger::{HasLogger, HasLoggerType}; + +use crate::one_for_all::traits::builder::OfaBuilder; +use crate::one_for_all::types::builder::OfaBuilderWrapper; + +impl HasLoggerType for OfaBuilderWrapper +where + Build: OfaBuilder, +{ + type Logger = Build::Logger; +} + +impl HasLogger for OfaBuilderWrapper +where + Build: OfaBuilder, +{ + fn logger(&self) -> &Self::Logger { + self.builder.logger() + } +} diff --git a/crates/relayer-all-in-one/src/one_for_all/impls/builder/mod.rs b/crates/relayer-all-in-one/src/one_for_all/impls/builder/mod.rs new file mode 100644 index 0000000000..8eb8f00737 --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/impls/builder/mod.rs @@ -0,0 +1,10 @@ +pub mod batch; +pub mod birelay; +pub mod cache; +pub mod chain; +pub mod components; +pub mod error; +pub mod logger; +pub mod relay; +pub mod runtime; +pub mod types; diff --git a/crates/relayer-all-in-one/src/one_for_all/impls/builder/relay.rs b/crates/relayer-all-in-one/src/one_for_all/impls/builder/relay.rs new file mode 100644 index 0000000000..4d3ed03ee6 --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/impls/builder/relay.rs @@ -0,0 +1,71 @@ +use async_trait::async_trait; +use ibc_relayer_components::build::traits::target::relay::{RelayAToBTarget, RelayBToATarget}; +use ibc_relayer_components_extra::build::traits::components::relay_with_batch_builder::RelayWithBatchBuilder; + +use crate::one_for_all::traits::builder::{ + ChainA, ChainB, ClientIdA, ClientIdB, OfaBuilder, RelayAToB, RelayBToA, RelayError, +}; +use crate::one_for_all::types::batch::aliases::MessageBatchSender; +use crate::one_for_all::types::builder::OfaBuilderWrapper; +use crate::one_for_all::types::chain::OfaChainWrapper; +use crate::one_for_all::types::component::OfaComponents; +use crate::one_for_all::types::relay::OfaRelayWrapper; +use crate::std_prelude::*; + +#[async_trait] +impl RelayWithBatchBuilder, RelayAToBTarget> for OfaComponents +where + Build: OfaBuilder, +{ + async fn build_relay_with_batch( + build: &OfaBuilderWrapper, + src_client_id: &ClientIdA, + dst_client_id: &ClientIdB, + src_chain: OfaChainWrapper>, + dst_chain: OfaChainWrapper>, + src_batch_sender: MessageBatchSender, RelayError>, + dst_batch_sender: MessageBatchSender, RelayError>, + ) -> Result>, Build::Error> { + let relay_a_to_b = OfaBuilder::build_relay_a_to_b( + build.builder.as_ref(), + src_client_id, + dst_client_id, + src_chain, + dst_chain, + src_batch_sender, + dst_batch_sender, + ) + .await?; + + Ok(OfaRelayWrapper::new(relay_a_to_b)) + } +} + +#[async_trait] +impl RelayWithBatchBuilder, RelayBToATarget> for OfaComponents +where + Build: OfaBuilder, +{ + async fn build_relay_with_batch( + build: &OfaBuilderWrapper, + src_client_id: &ClientIdB, + dst_client_id: &ClientIdA, + src_chain: OfaChainWrapper>, + dst_chain: OfaChainWrapper>, + src_batch_sender: MessageBatchSender, RelayError>, + dst_batch_sender: MessageBatchSender, RelayError>, + ) -> Result>, Build::Error> { + let relay_b_to_a = OfaBuilder::build_relay_b_to_a( + build.builder.as_ref(), + src_client_id, + dst_client_id, + src_chain, + dst_chain, + src_batch_sender, + dst_batch_sender, + ) + .await?; + + Ok(OfaRelayWrapper::new(relay_b_to_a)) + } +} diff --git a/crates/relayer-all-in-one/src/one_for_all/impls/builder/runtime.rs b/crates/relayer-all-in-one/src/one_for_all/impls/builder/runtime.rs new file mode 100644 index 0000000000..d3504eb023 --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/impls/builder/runtime.rs @@ -0,0 +1,20 @@ +use ibc_relayer_components::core::traits::error::HasErrorType; +use ibc_relayer_components::runtime::traits::runtime::HasRuntime; + +use crate::one_for_all::traits::builder::OfaBuilder; +use crate::one_for_all::types::builder::OfaBuilderWrapper; + +impl HasRuntime for OfaBuilderWrapper +where + Builder: OfaBuilder, +{ + type Runtime = Builder::Runtime; + + fn runtime(&self) -> &Self::Runtime { + self.builder.runtime() + } + + fn runtime_error(e: ::Error) -> Builder::Error { + Builder::runtime_error(e) + } +} diff --git a/crates/relayer-all-in-one/src/one_for_all/impls/builder/types.rs b/crates/relayer-all-in-one/src/one_for_all/impls/builder/types.rs new file mode 100644 index 0000000000..b599a46253 --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/impls/builder/types.rs @@ -0,0 +1,17 @@ +use ibc_relayer_components::build::traits::birelay::HasBiRelayType; + +use crate::one_for_all::traits::birelay::OfaBiRelay; +use crate::one_for_all::traits::builder::OfaBuilder; +use crate::one_for_all::types::birelay::OfaBiRelayWrapper; +use crate::one_for_all::types::builder::OfaBuilderWrapper; + +impl HasBiRelayType for OfaBuilderWrapper +where + Build: OfaBuilder, +{ + type BiRelay = OfaBiRelayWrapper; + + fn birelay_error(e: ::Error) -> Self::Error { + Build::birelay_error(e) + } +} diff --git a/crates/relayer-all-in-one/src/one_for_all/impls/chain/channel.rs b/crates/relayer-all-in-one/src/one_for_all/impls/chain/channel.rs new file mode 100644 index 0000000000..5c664df3dc --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/impls/chain/channel.rs @@ -0,0 +1,185 @@ +use async_trait::async_trait; + +use ibc_relayer_components::chain::traits::message_builders::channel::{ + CanBuildChannelHandshakeMessages, CanBuildChannelHandshakePayloads, +}; +use ibc_relayer_components::chain::traits::types::channel::{ + HasChannelHandshakePayloads, HasInitChannelOptionsType, +}; +use ibc_relayer_components::chain::traits::types::ibc_events::channel::{ + HasChannelOpenInitEvent, HasChannelOpenTryEvent, +}; + +use crate::one_for_all::traits::chain::{OfaChainTypes, OfaIbcChain}; +use crate::one_for_all::types::chain::OfaChainWrapper; +use crate::std_prelude::*; + +impl HasChannelHandshakePayloads> + for OfaChainWrapper +where + Chain: OfaChainTypes, + Counterparty: OfaChainTypes, +{ + type ChannelOpenTryPayload = Chain::ChannelOpenTryPayload; + + type ChannelOpenAckPayload = Chain::ChannelOpenAckPayload; + + type ChannelOpenConfirmPayload = Chain::ChannelOpenConfirmPayload; +} + +impl HasInitChannelOptionsType> + for OfaChainWrapper +where + Chain: OfaChainTypes, + Counterparty: OfaChainTypes, +{ + type InitChannelOptions = Chain::InitChannelOptions; +} + +impl HasChannelOpenInitEvent> + for OfaChainWrapper +where + Chain: OfaIbcChain, + Counterparty: OfaChainTypes, +{ + type ChannelOpenInitEvent = Chain::ChannelOpenInitEvent; + + fn try_extract_channel_open_init_event( + event: Chain::Event, + ) -> Option { + Chain::try_extract_channel_open_init_event(event) + } + + fn channel_open_init_event_channel_id( + event: &Chain::ChannelOpenInitEvent, + ) -> &Chain::ChannelId { + Chain::channel_open_init_event_channel_id(event) + } +} + +impl HasChannelOpenTryEvent> + for OfaChainWrapper +where + Chain: OfaIbcChain, + Counterparty: OfaChainTypes, +{ + type ChannelOpenTryEvent = Chain::ChannelOpenTryEvent; + + fn try_extract_channel_open_try_event( + event: Chain::Event, + ) -> Option { + Chain::try_extract_channel_open_try_event(event) + } + + fn channel_open_try_event_channel_id(event: &Chain::ChannelOpenTryEvent) -> &Chain::ChannelId { + Chain::channel_open_try_event_channel_id(event) + } +} + +#[async_trait] +impl CanBuildChannelHandshakePayloads> + for OfaChainWrapper +where + Chain: OfaIbcChain, + Counterparty: OfaChainTypes, +{ + async fn build_channel_open_try_payload( + &self, + client_state: &Self::ClientState, + height: &Self::Height, + port_id: &Self::PortId, + channel_id: &Self::ChannelId, + ) -> Result { + self.chain + .build_channel_open_try_payload(client_state, height, port_id, channel_id) + .await + } + + async fn build_channel_open_ack_payload( + &self, + client_state: &Self::ClientState, + height: &Self::Height, + port_id: &Self::PortId, + channel_id: &Self::ChannelId, + ) -> Result { + self.chain + .build_channel_open_ack_payload(client_state, height, port_id, channel_id) + .await + } + + async fn build_channel_open_confirm_payload( + &self, + client_state: &Self::ClientState, + height: &Self::Height, + port_id: &Self::PortId, + channel_id: &Self::ChannelId, + ) -> Result { + self.chain + .build_channel_open_confirm_payload(client_state, height, port_id, channel_id) + .await + } +} + +#[async_trait] +impl CanBuildChannelHandshakeMessages> + for OfaChainWrapper +where + Chain: OfaIbcChain, + Counterparty: OfaChainTypes, +{ + async fn build_channel_open_init_message( + &self, + port_id: &Self::PortId, + counterparty_port_id: &Counterparty::PortId, + init_channel_options: &Self::InitChannelOptions, + ) -> Result { + self.chain + .build_channel_open_init_message(port_id, counterparty_port_id, init_channel_options) + .await + } + + async fn build_channel_open_try_message( + &self, + dst_port_id: &Self::PortId, + src_port_id: &Counterparty::PortId, + src_channel_id: &Counterparty::ChannelId, + counterparty_payload: Counterparty::ChannelOpenTryPayload, + ) -> Result { + self.chain + .build_channel_open_try_message( + dst_port_id, + src_port_id, + src_channel_id, + counterparty_payload, + ) + .await + } + + async fn build_channel_open_ack_message( + &self, + port_id: &Self::PortId, + channel_id: &Self::ChannelId, + counterparty_channel_id: &Counterparty::ChannelId, + counterparty_payload: Counterparty::ChannelOpenAckPayload, + ) -> Result { + self.chain + .build_channel_open_ack_message( + port_id, + channel_id, + counterparty_channel_id, + counterparty_payload, + ) + .await + } + + async fn build_channel_open_confirm_message( + &self, + port_id: &Self::PortId, + channel_id: &Self::ChannelId, + counterparty_payload: Counterparty::ChannelOpenConfirmPayload, + ) -> Result { + self.chain + .build_channel_open_confirm_message(port_id, channel_id, counterparty_payload) + .await + } +} diff --git a/crates/relayer-all-in-one/src/one_for_all/impls/chain/client.rs b/crates/relayer-all-in-one/src/one_for_all/impls/chain/client.rs new file mode 100644 index 0000000000..1e6af4db51 --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/impls/chain/client.rs @@ -0,0 +1,187 @@ +use async_trait::async_trait; +use ibc_relayer_components::chain::traits::client::client_state::CanQueryClientState; +use ibc_relayer_components::chain::traits::client::consensus_state::CanFindConsensusStateHeight; +use ibc_relayer_components::chain::traits::client::create::{ + CanBuildCreateClientMessage, CanBuildCreateClientPayload, HasCreateClientEvent, + HasCreateClientOptions, HasCreateClientPayload, +}; +use ibc_relayer_components::chain::traits::client::update::{ + CanBuildUpdateClientMessage, CanBuildUpdateClientPayload, HasUpdateClientPayload, +}; +use ibc_relayer_components::chain::traits::types::client_state::{ + HasClientStateFields, HasClientStateType, +}; + +use crate::one_for_all::traits::chain::{OfaChainTypes, OfaIbcChain}; +use crate::one_for_all::types::chain::OfaChainWrapper; +use crate::std_prelude::*; + +impl HasCreateClientOptions> + for OfaChainWrapper +where + Chain: OfaChainTypes, + Counterparty: OfaChainTypes, +{ + type CreateClientPayloadOptions = Chain::CreateClientPayloadOptions; +} + +impl HasCreateClientPayload> + for OfaChainWrapper +where + Chain: OfaChainTypes, + Counterparty: OfaChainTypes, +{ + type CreateClientPayload = Chain::CreateClientPayload; +} + +impl HasCreateClientEvent> + for OfaChainWrapper +where + Chain: OfaIbcChain, + Counterparty: OfaChainTypes, +{ + type CreateClientEvent = Chain::CreateClientEvent; + + fn try_extract_create_client_event(event: Self::Event) -> Option { + Chain::try_extract_create_client_event(event) + } + + fn create_client_event_client_id(event: &Self::CreateClientEvent) -> &Self::ClientId { + Chain::create_client_event_client_id(event) + } +} + +#[async_trait] +impl CanBuildCreateClientPayload> + for OfaChainWrapper +where + Chain: OfaIbcChain, + Counterparty: OfaChainTypes, +{ + async fn build_create_client_payload( + &self, + create_client_options: &Self::CreateClientPayloadOptions, + ) -> Result { + self.chain + .build_create_client_payload(create_client_options) + .await + } +} + +#[async_trait] +impl CanBuildCreateClientMessage> + for OfaChainWrapper +where + Chain: OfaIbcChain, + Counterparty: OfaChainTypes, +{ + async fn build_create_client_message( + &self, + counterparty_payload: Counterparty::CreateClientPayload, + ) -> Result { + self.chain + .build_create_client_message(counterparty_payload) + .await + } +} + +#[async_trait] +impl HasClientStateType> + for OfaChainWrapper +where + Chain: OfaChainTypes, + Counterparty: OfaChainTypes, +{ + type ClientState = Chain::ClientState; +} + +#[async_trait] +impl HasUpdateClientPayload> + for OfaChainWrapper +where + Chain: OfaChainTypes, + Counterparty: OfaChainTypes, +{ + type UpdateClientPayload = Chain::UpdateClientPayload; +} + +#[async_trait] +impl CanBuildUpdateClientPayload> + for OfaChainWrapper +where + Chain: OfaIbcChain, + Counterparty: OfaChainTypes, +{ + async fn build_update_client_payload( + &self, + trusted_height: &Self::Height, + target_height: &Self::Height, + client_state: Self::ClientState, + ) -> Result { + self.chain + .build_update_client_payload(trusted_height, target_height, client_state) + .await + } +} + +#[async_trait] +impl CanBuildUpdateClientMessage> + for OfaChainWrapper +where + Chain: OfaIbcChain, + Counterparty: OfaChainTypes, +{ + async fn build_update_client_message( + &self, + client_id: &Self::ClientId, + payload: Counterparty::UpdateClientPayload, + ) -> Result, Self::Error> { + self.chain + .build_update_client_message(client_id, payload) + .await + } +} + +#[async_trait] +impl CanFindConsensusStateHeight> + for OfaChainWrapper +where + Chain: OfaIbcChain, + Counterparty: OfaChainTypes, +{ + async fn find_consensus_state_height_before( + &self, + client_id: &Self::ClientId, + target_height: &Counterparty::Height, + ) -> Result { + self.chain + .find_consensus_state_height_before(client_id, target_height) + .await + } +} + +impl HasClientStateFields> + for OfaChainWrapper +where + Chain: OfaIbcChain, + Counterparty: OfaChainTypes, +{ + fn client_state_latest_height(client_state: &Self::ClientState) -> &Self::Height { + Chain::client_state_latest_height(client_state) + } +} + +#[async_trait] +impl CanQueryClientState> + for OfaChainWrapper +where + Chain: OfaIbcChain, + Counterparty: OfaChainTypes, +{ + async fn query_client_state( + &self, + client_id: &Self::ClientId, + ) -> Result { + self.chain.query_client_state(client_id).await + } +} diff --git a/crates/relayer-all-in-one/src/one_for_all/impls/chain/components.rs b/crates/relayer-all-in-one/src/one_for_all/impls/chain/components.rs new file mode 100644 index 0000000000..85d9295b64 --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/impls/chain/components.rs @@ -0,0 +1,13 @@ +use ibc_relayer_components::core::traits::component::HasComponents; +use ibc_relayer_components::core::traits::sync::Async; +use ibc_relayer_components_extra::components::extra::chain::ExtraChainComponents; + +use crate::one_for_all::types::chain::OfaChainWrapper; +use crate::one_for_all::types::component::OfaComponents; + +impl HasComponents for OfaChainWrapper +where + Chain: Async, +{ + type Components = ExtraChainComponents; +} diff --git a/crates/relayer-all-in-one/src/one_for_all/impls/chain/connection.rs b/crates/relayer-all-in-one/src/one_for_all/impls/chain/connection.rs new file mode 100644 index 0000000000..c71299d332 --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/impls/chain/connection.rs @@ -0,0 +1,200 @@ +use async_trait::async_trait; +use ibc_relayer_components::chain::traits::message_builders::connection::{ + CanBuildConnectionHandshakeMessages, CanBuildConnectionHandshakePayloads, +}; +use ibc_relayer_components::chain::traits::types::connection::{ + HasConnectionHandshakePayloads, HasInitConnectionOptionsType, +}; +use ibc_relayer_components::chain::traits::types::ibc_events::connection::{ + HasConnectionOpenInitEvent, HasConnectionOpenTryEvent, +}; + +use crate::one_for_all::traits::chain::{OfaChainTypes, OfaIbcChain}; +use crate::one_for_all::types::chain::OfaChainWrapper; +use crate::std_prelude::*; + +impl HasConnectionHandshakePayloads> + for OfaChainWrapper +where + Chain: OfaChainTypes, + Counterparty: OfaChainTypes, +{ + type ConnectionOpenInitPayload = Chain::ConnectionOpenInitPayload; + + type ConnectionOpenTryPayload = Chain::ConnectionOpenTryPayload; + + type ConnectionOpenAckPayload = Chain::ConnectionOpenAckPayload; + + type ConnectionOpenConfirmPayload = Chain::ConnectionOpenConfirmPayload; +} + +impl HasInitConnectionOptionsType> + for OfaChainWrapper +where + Chain: OfaChainTypes, + Counterparty: OfaChainTypes, +{ + type InitConnectionOptions = Chain::InitConnectionOptions; +} + +impl HasConnectionOpenInitEvent> + for OfaChainWrapper +where + Chain: OfaIbcChain, + Counterparty: OfaChainTypes, +{ + type ConnectionOpenInitEvent = Chain::ConnectionOpenInitEvent; + + fn try_extract_connection_open_init_event( + event: Chain::Event, + ) -> Option { + Chain::try_extract_connection_open_init_event(event) + } + + fn connection_open_init_event_connection_id( + event: &Chain::ConnectionOpenInitEvent, + ) -> &Chain::ConnectionId { + Chain::connection_open_init_event_connection_id(event) + } +} + +impl HasConnectionOpenTryEvent> + for OfaChainWrapper +where + Chain: OfaIbcChain, + Counterparty: OfaChainTypes, +{ + type ConnectionOpenTryEvent = Chain::ConnectionOpenTryEvent; + + fn try_extract_connection_open_try_event( + event: Chain::Event, + ) -> Option { + Chain::try_extract_connection_open_try_event(event) + } + + fn connection_open_try_event_connection_id( + event: &Chain::ConnectionOpenTryEvent, + ) -> &Chain::ConnectionId { + Chain::connection_open_try_event_connection_id(event) + } +} + +#[async_trait] +impl CanBuildConnectionHandshakePayloads> + for OfaChainWrapper +where + Chain: OfaIbcChain, + Counterparty: OfaChainTypes, +{ + async fn build_connection_open_init_payload( + &self, + client_state: &Self::ClientState, + ) -> Result { + self.chain + .build_connection_open_init_payload(client_state) + .await + } + + async fn build_connection_open_try_payload( + &self, + client_state: &Self::ClientState, + height: &Self::Height, + client_id: &Self::ClientId, + connection_id: &Self::ConnectionId, + ) -> Result { + self.chain + .build_connection_open_try_payload(client_state, height, client_id, connection_id) + .await + } + + async fn build_connection_open_ack_payload( + &self, + client_state: &Self::ClientState, + height: &Self::Height, + client_id: &Self::ClientId, + connection_id: &Self::ConnectionId, + ) -> Result { + self.chain + .build_connection_open_ack_payload(client_state, height, client_id, connection_id) + .await + } + + async fn build_connection_open_confirm_payload( + &self, + client_state: &Self::ClientState, + height: &Self::Height, + client_id: &Self::ClientId, + connection_id: &Self::ConnectionId, + ) -> Result { + self.chain + .build_connection_open_confirm_payload(client_state, height, client_id, connection_id) + .await + } +} + +#[async_trait] +impl CanBuildConnectionHandshakeMessages> + for OfaChainWrapper +where + Chain: OfaIbcChain, + Counterparty: OfaChainTypes, +{ + async fn build_connection_open_init_message( + &self, + client_id: &Self::ClientId, + counterparty_client_id: &Counterparty::ClientId, + init_connection_options: &Self::InitConnectionOptions, + counterparty_payload: Counterparty::ConnectionOpenInitPayload, + ) -> Result { + self.chain + .build_connection_open_init_message( + client_id, + counterparty_client_id, + init_connection_options, + counterparty_payload, + ) + .await + } + + async fn build_connection_open_try_message( + &self, + client_id: &Self::ClientId, + counterparty_client_id: &Counterparty::ClientId, + counterparty_connection_id: &Counterparty::ConnectionId, + counterparty_payload: Counterparty::ConnectionOpenTryPayload, + ) -> Result { + self.chain + .build_connection_open_try_message( + client_id, + counterparty_client_id, + counterparty_connection_id, + counterparty_payload, + ) + .await + } + + async fn build_connection_open_ack_message( + &self, + connection_id: &Self::ConnectionId, + counterparty_connection_id: &Counterparty::ConnectionId, + counterparty_payload: Counterparty::ConnectionOpenAckPayload, + ) -> Result { + self.chain + .build_connection_open_ack_message( + connection_id, + counterparty_connection_id, + counterparty_payload, + ) + .await + } + + async fn build_connection_open_confirm_message( + &self, + connection_id: &Self::ConnectionId, + counterparty_payload: Counterparty::ConnectionOpenConfirmPayload, + ) -> Result { + self.chain + .build_connection_open_confirm_message(connection_id, counterparty_payload) + .await + } +} diff --git a/crates/relayer-all-in-one/src/one_for_all/impls/chain/error.rs b/crates/relayer-all-in-one/src/one_for_all/impls/chain/error.rs new file mode 100644 index 0000000000..3a1998187a --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/impls/chain/error.rs @@ -0,0 +1,11 @@ +use ibc_relayer_components::core::traits::error::HasErrorType; + +use crate::one_for_all::traits::chain::OfaChain; +use crate::one_for_all::types::chain::OfaChainWrapper; + +impl HasErrorType for OfaChainWrapper +where + Chain: OfaChain, +{ + type Error = Chain::Error; +} diff --git a/crates/relayer-all-in-one/src/one_for_all/impls/chain/event_subscription.rs b/crates/relayer-all-in-one/src/one_for_all/impls/chain/event_subscription.rs new file mode 100644 index 0000000000..de7256184d --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/impls/chain/event_subscription.rs @@ -0,0 +1,16 @@ +use alloc::sync::Arc; + +use ibc_relayer_components::chain::traits::event_subscription::HasEventSubscription; +use ibc_relayer_components::runtime::traits::subscription::Subscription; + +use crate::one_for_all::traits::chain::OfaChain; +use crate::one_for_all::types::chain::OfaChainWrapper; + +impl HasEventSubscription for OfaChainWrapper +where + Chain: OfaChain, +{ + fn event_subscription(&self) -> &Arc> { + self.chain.event_subscription() + } +} diff --git a/crates/relayer-all-in-one/src/one_for_all/impls/chain/logger.rs b/crates/relayer-all-in-one/src/one_for_all/impls/chain/logger.rs new file mode 100644 index 0000000000..d36d7f6284 --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/impls/chain/logger.rs @@ -0,0 +1,51 @@ +use ibc_relayer_components::chain::traits::logs::event::CanLogChainEvent; +use ibc_relayer_components::chain::traits::logs::packet::CanLogChainPacket; +use ibc_relayer_components::logger::traits::has_logger::{HasLogger, HasLoggerType}; +use ibc_relayer_components::logger::traits::logger::BaseLogger; + +use crate::one_for_all::traits::chain::{OfaChain, OfaChainTypes, OfaIbcChain}; +use crate::one_for_all::types::chain::OfaChainWrapper; + +impl HasLoggerType for OfaChainWrapper +where + Chain: OfaChain, +{ + type Logger = Chain::Logger; +} + +impl HasLogger for OfaChainWrapper +where + Chain: OfaChain, +{ + fn logger(&self) -> &Self::Logger { + self.chain.logger() + } +} + +impl CanLogChainEvent for OfaChainWrapper +where + Chain: OfaChain, +{ + fn log_event<'a>(event: &'a Chain::Event) -> ::LogValue<'a> { + Chain::log_event(event) + } +} + +impl CanLogChainPacket> + for OfaChainWrapper +where + Chain: OfaIbcChain, + Counterparty: OfaChainTypes, +{ + fn log_incoming_packet<'a>( + packet: &'a Self::IncomingPacket, + ) -> ::LogValue<'a> { + Chain::log_incoming_packet(packet) + } + + fn log_outgoing_packet<'a>( + packet: &'a Self::OutgoingPacket, + ) -> ::LogValue<'a> { + Chain::log_outgoing_packet(packet) + } +} diff --git a/crates/relayer-all-in-one/src/one_for_all/impls/chain/message/ack_packet.rs b/crates/relayer-all-in-one/src/one_for_all/impls/chain/message/ack_packet.rs new file mode 100644 index 0000000000..8f09f91ef3 --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/impls/chain/message/ack_packet.rs @@ -0,0 +1,55 @@ +use async_trait::async_trait; +use ibc_relayer_components::chain::traits::message_builders::ack_packet::{ + CanBuildAckPacketMessage, CanBuildAckPacketPayload, +}; +use ibc_relayer_components::chain::traits::types::packets::ack::HasAckPacketPayload; + +use crate::one_for_all::traits::chain::{OfaChainTypes, OfaIbcChain}; +use crate::one_for_all::types::chain::OfaChainWrapper; +use crate::std_prelude::*; + +#[async_trait] +impl HasAckPacketPayload> + for OfaChainWrapper +where + Chain: OfaChainTypes, + Counterparty: OfaChainTypes, +{ + type AckPacketPayload = Chain::AckPacketPayload; +} + +#[async_trait] +impl CanBuildAckPacketPayload> + for OfaChainWrapper +where + Chain: OfaIbcChain, + Counterparty: OfaChainTypes, +{ + async fn build_ack_packet_payload( + &self, + client_state: &Self::ClientState, + height: &Self::Height, + packet: &Self::IncomingPacket, + ack: &Self::WriteAcknowledgementEvent, + ) -> Result { + self.chain + .build_ack_packet_payload(client_state, height, packet, ack) + .await + } +} + +#[async_trait] +impl CanBuildAckPacketMessage> + for OfaChainWrapper +where + Chain: OfaIbcChain, + Counterparty: OfaChainTypes, +{ + async fn build_ack_packet_message( + &self, + packet: &Chain::OutgoingPacket, + payload: Counterparty::AckPacketPayload, + ) -> Result { + self.chain.build_ack_packet_message(packet, payload).await + } +} diff --git a/crates/relayer-all-in-one/src/one_for_all/impls/chain/message/mod.rs b/crates/relayer-all-in-one/src/one_for_all/impls/chain/message/mod.rs new file mode 100644 index 0000000000..999ab81f0c --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/impls/chain/message/mod.rs @@ -0,0 +1,3 @@ +pub mod ack_packet; +pub mod receive_packet; +pub mod timeout_unordered_packet; diff --git a/crates/relayer-all-in-one/src/one_for_all/impls/chain/message/receive_packet.rs b/crates/relayer-all-in-one/src/one_for_all/impls/chain/message/receive_packet.rs new file mode 100644 index 0000000000..c20809e6fe --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/impls/chain/message/receive_packet.rs @@ -0,0 +1,56 @@ +use async_trait::async_trait; +use ibc_relayer_components::chain::traits::message_builders::receive_packet::{ + CanBuildReceivePacketMessage, CanBuildReceivePacketPayload, +}; +use ibc_relayer_components::chain::traits::types::packets::receive::HasReceivePacketPayload; + +use crate::one_for_all::traits::chain::{OfaChainTypes, OfaIbcChain}; +use crate::one_for_all::types::chain::OfaChainWrapper; +use crate::std_prelude::*; + +#[async_trait] +impl HasReceivePacketPayload> + for OfaChainWrapper +where + Chain: OfaChainTypes, + Counterparty: OfaChainTypes, +{ + type ReceivePacketPayload = Chain::ReceivePacketPayload; +} + +#[async_trait] +impl CanBuildReceivePacketPayload> + for OfaChainWrapper +where + Chain: OfaIbcChain, + Counterparty: OfaChainTypes, +{ + async fn build_receive_packet_payload( + &self, + client_state: &Self::ClientState, + height: &Self::Height, + packet: &Self::OutgoingPacket, + ) -> Result { + self.chain + .build_receive_packet_payload(client_state, height, packet) + .await + } +} + +#[async_trait] +impl CanBuildReceivePacketMessage> + for OfaChainWrapper +where + Chain: OfaIbcChain, + Counterparty: OfaChainTypes, +{ + async fn build_receive_packet_message( + &self, + packet: &Chain::IncomingPacket, + payload: Counterparty::ReceivePacketPayload, + ) -> Result { + self.chain + .build_receive_packet_message(packet, payload) + .await + } +} diff --git a/crates/relayer-all-in-one/src/one_for_all/impls/chain/message/timeout_unordered_packet.rs b/crates/relayer-all-in-one/src/one_for_all/impls/chain/message/timeout_unordered_packet.rs new file mode 100644 index 0000000000..11462f9197 --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/impls/chain/message/timeout_unordered_packet.rs @@ -0,0 +1,55 @@ +use async_trait::async_trait; +use ibc_relayer_components::chain::traits::message_builders::timeout_unordered_packet::{ + CanBuildTimeoutUnorderedPacketMessage, CanBuildTimeoutUnorderedPacketPayload, +}; +use ibc_relayer_components::chain::traits::types::packets::timeout::HasTimeoutUnorderedPacketPayload; + +use crate::one_for_all::traits::chain::{OfaChainTypes, OfaIbcChain}; +use crate::one_for_all::types::chain::OfaChainWrapper; +use crate::std_prelude::*; + +impl HasTimeoutUnorderedPacketPayload> + for OfaChainWrapper +where + Chain: OfaChainTypes, + Counterparty: OfaChainTypes, +{ + type TimeoutUnorderedPacketPayload = Chain::TimeoutUnorderedPacketPayload; +} + +#[async_trait] +impl CanBuildTimeoutUnorderedPacketPayload> + for OfaChainWrapper +where + Chain: OfaIbcChain, + Counterparty: OfaChainTypes, +{ + async fn build_timeout_unordered_packet_payload( + &self, + client_state: &Self::ClientState, + height: &Self::Height, + packet: &Self::IncomingPacket, + ) -> Result { + self.chain + .build_timeout_unordered_packet_payload(client_state, height, packet) + .await + } +} + +#[async_trait] +impl CanBuildTimeoutUnorderedPacketMessage> + for OfaChainWrapper +where + Chain: OfaIbcChain, + Counterparty: OfaChainTypes, +{ + async fn build_timeout_unordered_packet_message( + &self, + packet: &Chain::OutgoingPacket, + payload: Counterparty::TimeoutUnorderedPacketPayload, + ) -> Result { + self.chain + .build_timeout_unordered_packet_message(packet, payload) + .await + } +} diff --git a/crates/relayer-all-in-one/src/one_for_all/impls/chain/message_sender.rs b/crates/relayer-all-in-one/src/one_for_all/impls/chain/message_sender.rs new file mode 100644 index 0000000000..72d112cb35 --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/impls/chain/message_sender.rs @@ -0,0 +1,21 @@ +use async_trait::async_trait; +use ibc_relayer_components::chain::traits::components::message_sender::MessageSender; + +use crate::one_for_all::traits::chain::OfaChain; +use crate::one_for_all::types::chain::OfaChainWrapper; +use crate::one_for_all::types::component::OfaComponents; +use crate::std_prelude::*; + +#[async_trait] +impl MessageSender> for OfaComponents +where + Chain: OfaChain, +{ + async fn send_messages( + chain: &OfaChainWrapper, + messages: Vec, + ) -> Result>, Chain::Error> { + let events = chain.chain.send_messages(messages).await?; + Ok(events) + } +} diff --git a/crates/relayer-all-in-one/src/one_for_all/impls/chain/mod.rs b/crates/relayer-all-in-one/src/one_for_all/impls/chain/mod.rs new file mode 100644 index 0000000000..70c8f93571 --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/impls/chain/mod.rs @@ -0,0 +1,12 @@ +pub mod channel; +pub mod client; +pub mod components; +pub mod connection; +pub mod error; +pub mod event_subscription; +pub mod logger; +pub mod message; +pub mod message_sender; +pub mod queries; +pub mod telemetry; +pub mod types; diff --git a/crates/relayer-all-in-one/src/one_for_all/impls/chain/queries/channel.rs b/crates/relayer-all-in-one/src/one_for_all/impls/chain/queries/channel.rs new file mode 100644 index 0000000000..f4ff54c6d1 --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/impls/chain/queries/channel.rs @@ -0,0 +1,24 @@ +use async_trait::async_trait; +use ibc_relayer_components::chain::traits::queries::channel::CanQueryCounterpartyChainIdFromChannel; + +use crate::one_for_all::traits::chain::{OfaChainTypes, OfaIbcChain}; +use crate::one_for_all::types::chain::OfaChainWrapper; +use crate::std_prelude::*; + +#[async_trait] +impl CanQueryCounterpartyChainIdFromChannel> + for OfaChainWrapper +where + Chain: OfaIbcChain, + Counterparty: OfaChainTypes, +{ + async fn query_chain_id_from_channel_id( + &self, + channel_id: &Self::ChannelId, + port_id: &Self::PortId, + ) -> Result { + self.chain + .query_chain_id_from_channel_id(channel_id, port_id) + .await + } +} diff --git a/crates/relayer-all-in-one/src/one_for_all/impls/chain/queries/consensus_state.rs b/crates/relayer-all-in-one/src/one_for_all/impls/chain/queries/consensus_state.rs new file mode 100644 index 0000000000..835afbe0b8 --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/impls/chain/queries/consensus_state.rs @@ -0,0 +1,35 @@ +use async_trait::async_trait; +use ibc_relayer_components::chain::traits::components::consensus_state_querier::ConsensusStateQuerier; +use ibc_relayer_components::chain::traits::types::consensus_state::HasConsensusStateType; + +use crate::one_for_all::traits::chain::{OfaChainTypes, OfaIbcChain}; +use crate::one_for_all::types::chain::OfaChainWrapper; +use crate::one_for_all::types::component::OfaComponents; +use crate::std_prelude::*; + +impl HasConsensusStateType> + for OfaChainWrapper +where + Chain: OfaChainTypes, + Counterparty: OfaChainTypes, +{ + type ConsensusState = Chain::ConsensusState; +} + +#[async_trait] +impl + ConsensusStateQuerier, OfaChainWrapper> for OfaComponents +where + Chain: OfaIbcChain, + Counterparty: OfaChainTypes, +{ + async fn query_consensus_state( + chain: &OfaChainWrapper, + client_id: &Chain::ClientId, + height: &Counterparty::Height, + ) -> Result { + let consensus_state = chain.chain.query_consensus_state(client_id, height).await?; + + Ok(consensus_state) + } +} diff --git a/crates/relayer-all-in-one/src/one_for_all/impls/chain/queries/mod.rs b/crates/relayer-all-in-one/src/one_for_all/impls/chain/queries/mod.rs new file mode 100644 index 0000000000..40caf4ac14 --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/impls/chain/queries/mod.rs @@ -0,0 +1,8 @@ +pub mod channel; +pub mod consensus_state; +pub mod packet_commitments; +pub mod received_packet; +pub mod send_packet; +pub mod status; +pub mod unreceived_packets; +pub mod write_ack; diff --git a/crates/relayer-all-in-one/src/one_for_all/impls/chain/queries/packet_commitments.rs b/crates/relayer-all-in-one/src/one_for_all/impls/chain/queries/packet_commitments.rs new file mode 100644 index 0000000000..aa58391a58 --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/impls/chain/queries/packet_commitments.rs @@ -0,0 +1,31 @@ +use async_trait::async_trait; + +use ibc_relayer_components::chain::traits::queries::packet_commitments::CanQueryPacketCommitments; + +use crate::one_for_all::traits::chain::OfaIbcChain; +use crate::one_for_all::types::chain::OfaChainWrapper; +use crate::std_prelude::*; + +#[async_trait] +impl CanQueryPacketCommitments> + for OfaChainWrapper +where + Chain: OfaIbcChain, + Counterparty: OfaIbcChain, +{ + /// Query the sequences of the packets that the chain has committed to be + /// sent to the counterparty chain, of which the full packet relaying is not + /// yet completed. Once the chain receives the ack from the counterparty + /// chain, a given sequence should be removed from the packet commitment list. + async fn query_packet_commitments( + &self, + channel_id: &Self::ChannelId, + port_id: &Self::PortId, + ) -> Result<(Vec, Self::Height), Self::Error> { + let (sequences, height) = self + .chain + .query_packet_commitments(channel_id, port_id) + .await?; + Ok((sequences, height)) + } +} diff --git a/crates/relayer-all-in-one/src/one_for_all/impls/chain/queries/received_packet.rs b/crates/relayer-all-in-one/src/one_for_all/impls/chain/queries/received_packet.rs new file mode 100644 index 0000000000..de9ae955a6 --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/impls/chain/queries/received_packet.rs @@ -0,0 +1,28 @@ +use async_trait::async_trait; +use ibc_relayer_components::chain::traits::queries::received_packet::CanQueryReceivedPacket; + +use crate::one_for_all::traits::chain::{OfaChainTypes, OfaIbcChain}; +use crate::one_for_all::types::chain::OfaChainWrapper; +use crate::std_prelude::*; + +#[async_trait] +impl CanQueryReceivedPacket> + for OfaChainWrapper +where + Chain: OfaIbcChain, + Counterparty: OfaChainTypes, +{ + async fn query_is_packet_received( + &self, + port_id: &Self::PortId, + channel_id: &Self::ChannelId, + sequence: &Counterparty::Sequence, + ) -> Result { + let is_received = self + .chain + .query_is_packet_received(port_id, channel_id, sequence) + .await?; + + Ok(is_received) + } +} diff --git a/crates/relayer-all-in-one/src/one_for_all/impls/chain/queries/send_packet.rs b/crates/relayer-all-in-one/src/one_for_all/impls/chain/queries/send_packet.rs new file mode 100644 index 0000000000..482ac80c54 --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/impls/chain/queries/send_packet.rs @@ -0,0 +1,43 @@ +use async_trait::async_trait; + +use ibc_relayer_components::chain::traits::queries::send_packet::CanQuerySendPacketsFromSequences; + +use crate::one_for_all::traits::chain::{OfaChainTypes, OfaIbcChain}; +use crate::one_for_all::types::chain::OfaChainWrapper; +use crate::std_prelude::*; + +#[async_trait] +impl CanQuerySendPacketsFromSequences> + for OfaChainWrapper +where + Chain: OfaIbcChain + OfaChainTypes, + Counterparty: OfaIbcChain, +{ + /// Given a list of sequences, a channel and port will query a list of outgoing + /// packets which have not been relayed. + async fn query_send_packets_from_sequences( + &self, + channel_id: &Self::ChannelId, + port_id: &Self::PortId, + counterparty_channel_id: &Counterparty::ChannelId, + counterparty_port_id: &Counterparty::PortId, + sequences: &[Self::Sequence], + // The height is given to query the packets from a specific height. + // This height should be the same as the query height from the + // `CanQueryPacketCommitments` made on the same chain. + height: &Self::Height, + ) -> Result, Self::Error> { + let send_packets = self + .chain + .query_send_packets_from_sequences( + channel_id, + port_id, + counterparty_channel_id, + counterparty_port_id, + sequences, + height, + ) + .await?; + Ok(send_packets) + } +} diff --git a/crates/relayer-all-in-one/src/one_for_all/impls/chain/queries/status.rs b/crates/relayer-all-in-one/src/one_for_all/impls/chain/queries/status.rs new file mode 100644 index 0000000000..b6d896abb9 --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/impls/chain/queries/status.rs @@ -0,0 +1,37 @@ +use async_trait::async_trait; +use ibc_relayer_components::chain::traits::components::chain_status_querier::ChainStatusQuerier; +use ibc_relayer_components::chain::traits::types::status::HasChainStatusType; + +use crate::one_for_all::traits::chain::OfaChain; +use crate::one_for_all::types::chain::OfaChainWrapper; +use crate::one_for_all::types::component::OfaComponents; +use crate::std_prelude::*; + +impl HasChainStatusType for OfaChainWrapper +where + Chain: OfaChain, +{ + type ChainStatus = Chain::ChainStatus; + + fn chain_status_height(status: &Chain::ChainStatus) -> &Chain::Height { + Chain::chain_status_height(status) + } + + fn chain_status_timestamp(status: &Chain::ChainStatus) -> &Chain::Timestamp { + Chain::chain_status_timestamp(status) + } +} + +#[async_trait] +impl ChainStatusQuerier> for OfaComponents +where + Chain: OfaChain, +{ + async fn query_chain_status( + context: &OfaChainWrapper, + ) -> Result { + let status = context.chain.query_chain_status().await?; + + Ok(status) + } +} diff --git a/crates/relayer-all-in-one/src/one_for_all/impls/chain/queries/unreceived_packets.rs b/crates/relayer-all-in-one/src/one_for_all/impls/chain/queries/unreceived_packets.rs new file mode 100644 index 0000000000..4f1ccdd108 --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/impls/chain/queries/unreceived_packets.rs @@ -0,0 +1,32 @@ +use async_trait::async_trait; + +use ibc_relayer_components::chain::traits::queries::unreceived_packets::CanQueryUnreceivedPacketSequences; + +use crate::one_for_all::traits::chain::{OfaChainTypes, OfaIbcChain}; +use crate::one_for_all::types::chain::OfaChainWrapper; +use crate::std_prelude::*; + +#[async_trait] +impl CanQueryUnreceivedPacketSequences> + for OfaChainWrapper +where + Chain: OfaIbcChain + OfaChainTypes, + Counterparty: OfaIbcChain, +{ + /// Given a list of counterparty commitment sequences, + /// return a filtered list of sequences which the chain + /// has not received the packet from the counterparty chain. + async fn query_unreceived_packet_sequences( + &self, + channel_id: &Chain::ChannelId, + port_id: &Chain::PortId, + sequences: &[Counterparty::Sequence], + ) -> Result, Self::Error> { + let unreceived_packet_sequences = self + .chain + .query_unreceived_packet_sequences(channel_id, port_id, sequences) + .await?; + + Ok(unreceived_packet_sequences) + } +} diff --git a/crates/relayer-all-in-one/src/one_for_all/impls/chain/queries/write_ack.rs b/crates/relayer-all-in-one/src/one_for_all/impls/chain/queries/write_ack.rs new file mode 100644 index 0000000000..28e8f87a6a --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/impls/chain/queries/write_ack.rs @@ -0,0 +1,21 @@ +use async_trait::async_trait; +use ibc_relayer_components::chain::traits::queries::write_ack::CanQueryWriteAcknowledgement; + +use crate::one_for_all::traits::chain::{OfaChainTypes, OfaIbcChain}; +use crate::one_for_all::types::chain::OfaChainWrapper; +use crate::std_prelude::*; + +#[async_trait] +impl CanQueryWriteAcknowledgement> + for OfaChainWrapper +where + Chain: OfaIbcChain, + Counterparty: OfaChainTypes, +{ + async fn query_write_acknowledgement_event( + &self, + packet: &Self::IncomingPacket, + ) -> Result, Self::Error> { + self.chain.query_write_acknowledgement_event(packet).await + } +} diff --git a/crates/relayer-all-in-one/src/one_for_all/impls/chain/telemetry.rs b/crates/relayer-all-in-one/src/one_for_all/impls/chain/telemetry.rs new file mode 100644 index 0000000000..7b80fc32ae --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/impls/chain/telemetry.rs @@ -0,0 +1,15 @@ +use ibc_relayer_components_extra::telemetry::traits::telemetry::HasTelemetry; + +use crate::one_for_all::traits::chain::OfaChain; +use crate::one_for_all::types::chain::OfaChainWrapper; + +impl HasTelemetry for OfaChainWrapper +where + Chain: OfaChain, +{ + type Telemetry = Chain::Telemetry; + + fn telemetry(&self) -> &Self::Telemetry { + self.chain.telemetry() + } +} diff --git a/crates/relayer-all-in-one/src/one_for_all/impls/chain/types.rs b/crates/relayer-all-in-one/src/one_for_all/impls/chain/types.rs new file mode 100644 index 0000000000..53bb0be88a --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/impls/chain/types.rs @@ -0,0 +1,227 @@ +use ibc_relayer_components::chain::traits::components::packet_fields_reader::PacketFieldsReader; +use ibc_relayer_components::chain::traits::types::chain_id::{HasChainId, HasChainIdType}; +use ibc_relayer_components::chain::traits::types::event::HasEventType; +use ibc_relayer_components::chain::traits::types::height::{CanIncrementHeight, HasHeightType}; +use ibc_relayer_components::chain::traits::types::ibc::{ + HasCounterpartyMessageHeight, HasIbcChainTypes, +}; +use ibc_relayer_components::chain::traits::types::ibc_events::send_packet::HasSendPacketEvent; +use ibc_relayer_components::chain::traits::types::ibc_events::write_ack::{ + CanBuildPacketFromWriteAckEvent, HasWriteAcknowledgementEvent, +}; +use ibc_relayer_components::chain::traits::types::message::{ + CanEstimateMessageSize, HasMessageType, +}; +use ibc_relayer_components::chain::traits::types::packet::HasIbcPacketTypes; +use ibc_relayer_components::chain::traits::types::timestamp::HasTimestampType; +use ibc_relayer_components::core::traits::error::HasErrorType; +use ibc_relayer_components::runtime::traits::runtime::HasRuntime; + +use crate::one_for_all::traits::chain::{OfaChain, OfaChainTypes, OfaIbcChain}; +use crate::one_for_all::types::chain::OfaChainWrapper; +use crate::one_for_all::types::component::OfaComponents; +use crate::std_prelude::*; + +impl HasRuntime for OfaChainWrapper { + type Runtime = Chain::Runtime; + + fn runtime(&self) -> &Self::Runtime { + self.chain.runtime() + } + + fn runtime_error(e: ::Error) -> Chain::Error { + Chain::runtime_error(e) + } +} + +impl HasMessageType for OfaChainWrapper { + type Message = Chain::Message; +} + +impl CanEstimateMessageSize for OfaChainWrapper { + fn estimate_message_size(message: &Self::Message) -> Result { + Chain::estimate_message_size(message) + } +} + +impl HasEventType for OfaChainWrapper { + type Event = Chain::Event; +} + +impl HasHeightType for OfaChainWrapper { + type Height = Chain::Height; +} + +impl CanIncrementHeight for OfaChainWrapper { + fn increment_height(height: &Self::Height) -> Result { + Chain::increment_height(height) + } +} + +impl HasChainIdType for OfaChainWrapper { + type ChainId = Chain::ChainId; +} + +impl HasTimestampType for OfaChainWrapper { + type Timestamp = Chain::Timestamp; +} + +impl HasChainId for OfaChainWrapper { + fn chain_id(&self) -> &Self::ChainId { + self.chain.chain_id() + } +} + +impl HasIbcChainTypes> for OfaChainWrapper +where + Chain: OfaChainTypes, + Counterparty: OfaChainTypes, +{ + type ClientId = Chain::ClientId; + + type ConnectionId = Chain::ConnectionId; + + type ChannelId = Chain::ChannelId; + + type PortId = Chain::PortId; + + type Sequence = Chain::Sequence; +} + +impl HasCounterpartyMessageHeight> + for OfaChainWrapper +where + Chain: OfaIbcChain, + Counterparty: OfaChainTypes, +{ + fn counterparty_message_height_for_update_client( + message: &Self::Message, + ) -> Option { + Chain::counterparty_message_height_for_update_client(message) + } +} + +impl HasIbcPacketTypes> + for OfaChainWrapper +where + Chain: OfaChainTypes, + Counterparty: OfaChainTypes, +{ + type IncomingPacket = Chain::IncomingPacket; + + type OutgoingPacket = Chain::OutgoingPacket; +} + +impl PacketFieldsReader, OfaChainWrapper> + for OfaComponents +where + Chain: OfaIbcChain, + Counterparty: OfaChainTypes, +{ + fn incoming_packet_src_channel_id(packet: &Chain::IncomingPacket) -> &Counterparty::ChannelId { + Chain::incoming_packet_src_channel_id(packet) + } + + fn incoming_packet_dst_channel_id(packet: &Chain::IncomingPacket) -> &Chain::ChannelId { + Chain::incoming_packet_dst_channel_id(packet) + } + + fn incoming_packet_src_port(packet: &Chain::IncomingPacket) -> &Counterparty::PortId { + Chain::incoming_packet_src_port(packet) + } + + fn incoming_packet_dst_port(packet: &Chain::IncomingPacket) -> &Chain::PortId { + Chain::incoming_packet_dst_port(packet) + } + + fn incoming_packet_sequence(packet: &Chain::IncomingPacket) -> &Counterparty::Sequence { + Chain::incoming_packet_sequence(packet) + } + + fn incoming_packet_timeout_height(packet: &Chain::IncomingPacket) -> Option<&Chain::Height> { + Chain::incoming_packet_timeout_height(packet) + } + + fn incoming_packet_timeout_timestamp(packet: &Chain::IncomingPacket) -> &Chain::Timestamp { + Chain::incoming_packet_timeout_timestamp(packet) + } + + fn outgoing_packet_src_channel_id(packet: &Chain::OutgoingPacket) -> &Chain::ChannelId { + Chain::outgoing_packet_src_channel_id(packet) + } + + fn outgoing_packet_dst_channel_id(packet: &Chain::OutgoingPacket) -> &Counterparty::ChannelId { + Chain::outgoing_packet_dst_channel_id(packet) + } + + fn outgoing_packet_src_port(packet: &Chain::OutgoingPacket) -> &Chain::PortId { + Chain::outgoing_packet_src_port(packet) + } + + fn outgoing_packet_dst_port(packet: &Chain::OutgoingPacket) -> &Counterparty::PortId { + Chain::outgoing_packet_dst_port(packet) + } + + fn outgoing_packet_sequence(packet: &Chain::OutgoingPacket) -> &Chain::Sequence { + Chain::outgoing_packet_sequence(packet) + } + + fn outgoing_packet_timeout_height( + packet: &Chain::OutgoingPacket, + ) -> Option<&Counterparty::Height> { + Chain::outgoing_packet_timeout_height(packet) + } + + fn outgoing_packet_timeout_timestamp( + packet: &Chain::OutgoingPacket, + ) -> &Counterparty::Timestamp { + Chain::outgoing_packet_timeout_timestamp(packet) + } +} + +impl HasWriteAcknowledgementEvent> + for OfaChainWrapper +where + Chain: OfaIbcChain, + Counterparty: OfaChainTypes, +{ + type WriteAcknowledgementEvent = Chain::WriteAcknowledgementEvent; + + fn try_extract_write_acknowledgement_event( + event: &Self::Event, + ) -> Option { + Chain::try_extract_write_acknowledgement_event(event) + } +} + +impl CanBuildPacketFromWriteAckEvent> + for OfaChainWrapper +where + Chain: OfaIbcChain, + Counterparty: OfaChainTypes, +{ + fn build_packet_from_write_acknowledgement_event( + ack: &Self::WriteAcknowledgementEvent, + ) -> &Self::IncomingPacket { + Chain::extract_packet_from_write_acknowledgement_event(ack) + } +} + +impl HasSendPacketEvent> + for OfaChainWrapper +where + Chain: OfaIbcChain, + Counterparty: OfaChainTypes, +{ + type SendPacketEvent = Chain::SendPacketEvent; + + fn try_extract_send_packet_event(event: &Self::Event) -> Option { + Chain::try_extract_send_packet_event(event) + } + + fn extract_packet_from_send_packet_event( + event: &Self::SendPacketEvent, + ) -> Self::OutgoingPacket { + Chain::extract_packet_from_send_packet_event(event) + } +} diff --git a/crates/relayer-all-in-one/src/one_for_all/impls/mod.rs b/crates/relayer-all-in-one/src/one_for_all/impls/mod.rs new file mode 100644 index 0000000000..1f0c0fb3c0 --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/impls/mod.rs @@ -0,0 +1,4 @@ +pub mod birelay; +pub mod builder; +pub mod chain; +pub mod relay; diff --git a/crates/relayer-all-in-one/src/one_for_all/impls/relay/batch.rs b/crates/relayer-all-in-one/src/one_for_all/impls/relay/batch.rs new file mode 100644 index 0000000000..852ad6fdcf --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/impls/relay/batch.rs @@ -0,0 +1,24 @@ +use ibc_relayer_components::relay::traits::target::{DestinationTarget, SourceTarget}; +use ibc_relayer_components_extra::batch::traits::channel::HasMessageBatchSender; +use ibc_relayer_components_extra::batch::types::aliases::MessageBatchSender; + +use crate::one_for_all::traits::relay::OfaRelay; +use crate::one_for_all::types::relay::OfaRelayWrapper; + +impl HasMessageBatchSender for OfaRelayWrapper +where + Relay: OfaRelay, +{ + fn get_batch_sender(&self) -> &MessageBatchSender { + self.relay.src_chain_message_batch_sender() + } +} + +impl HasMessageBatchSender for OfaRelayWrapper +where + Relay: OfaRelay, +{ + fn get_batch_sender(&self) -> &MessageBatchSender { + self.relay.dst_chain_message_batch_sender() + } +} diff --git a/crates/relayer-all-in-one/src/one_for_all/impls/relay/channel.rs b/crates/relayer-all-in-one/src/one_for_all/impls/relay/channel.rs new file mode 100644 index 0000000000..6743922a70 --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/impls/relay/channel.rs @@ -0,0 +1,149 @@ +use async_trait::async_trait; + +use ibc_relayer_components::relay::impls::channel::open_ack::RelayChannelOpenAck; +use ibc_relayer_components::relay::impls::channel::open_confirm::RelayChannelOpenConfirm; +use ibc_relayer_components::relay::impls::channel::open_handshake::RelayChannelOpenHandshake; +use ibc_relayer_components::relay::impls::channel::open_init::{ + InitializeChannel, InjectMissingChannelInitEventError, +}; +use ibc_relayer_components::relay::impls::channel::open_try::{ + InjectMissingChannelTryEventError, RelayChannelOpenTry, +}; +use ibc_relayer_components::relay::traits::channel::open_ack::{ + CanRelayChannelOpenAck, ChannelOpenAckRelayer, +}; +use ibc_relayer_components::relay::traits::channel::open_confirm::{ + CanRelayChannelOpenConfirm, ChannelOpenConfirmRelayer, +}; +use ibc_relayer_components::relay::traits::channel::open_handshake::{ + CanRelayChannelOpenHandshake, ChannelOpenHandshakeRelayer, +}; +use ibc_relayer_components::relay::traits::channel::open_init::{ + CanInitChannel, ChannelInitializer, +}; +use ibc_relayer_components::relay::traits::channel::open_try::{ + CanRelayChannelOpenTry, ChannelOpenTryRelayer, +}; + +use crate::one_for_all::traits::chain::OfaChainTypes; +use crate::one_for_all::{traits::relay::OfaRelay, types::relay::OfaRelayWrapper}; +use crate::std_prelude::*; + +impl InjectMissingChannelInitEventError for OfaRelayWrapper +where + Relay: OfaRelay, +{ + fn missing_channel_init_event_error(&self) -> Relay::Error { + self.relay.missing_channel_init_event_error() + } +} + +impl InjectMissingChannelTryEventError for OfaRelayWrapper +where + Relay: OfaRelay, +{ + fn missing_channel_try_event_error( + &self, + src_channel_id: &::ChannelId, + ) -> Relay::Error { + self.relay.missing_channel_try_event_error(src_channel_id) + } +} + +#[async_trait] +impl CanInitChannel for OfaRelayWrapper +where + Relay: OfaRelay, +{ + async fn init_channel( + &self, + src_port_id: &::PortId, + dst_port_id: &::PortId, + init_channel_options: &::InitChannelOptions, + ) -> Result<::ChannelId, Self::Error> { + InitializeChannel::init_channel(self, src_port_id, dst_port_id, init_channel_options).await + } +} + +#[async_trait] +impl CanRelayChannelOpenTry for OfaRelayWrapper +where + Relay: OfaRelay, +{ + async fn relay_channel_open_try( + &self, + dst_port_id: &::PortId, + src_port_id: &::PortId, + src_channel_id: &::ChannelId, + ) -> Result<::ChannelId, Self::Error> { + RelayChannelOpenTry::relay_channel_open_try(self, dst_port_id, src_port_id, src_channel_id) + .await + } +} + +#[async_trait] +impl CanRelayChannelOpenAck for OfaRelayWrapper +where + Relay: OfaRelay, +{ + async fn relay_channel_open_ack( + &self, + src_port_id: &::PortId, + src_channel_id: &::ChannelId, + dst_port_id: &::PortId, + dst_channel_id: &::ChannelId, + ) -> Result<(), Self::Error> { + RelayChannelOpenAck::relay_channel_open_ack( + self, + src_port_id, + src_channel_id, + dst_port_id, + dst_channel_id, + ) + .await + } +} + +#[async_trait] +impl CanRelayChannelOpenConfirm for OfaRelayWrapper +where + Relay: OfaRelay, +{ + async fn relay_channel_open_confirm( + &self, + dst_port_id: &::PortId, + dst_channel_id: &::ChannelId, + src_port_id: &::PortId, + src_channel_id: &::ChannelId, + ) -> Result<(), Self::Error> { + RelayChannelOpenConfirm::relay_channel_open_confirm( + self, + dst_port_id, + dst_channel_id, + src_port_id, + src_channel_id, + ) + .await + } +} + +#[async_trait] +impl CanRelayChannelOpenHandshake for OfaRelayWrapper +where + Relay: OfaRelay, +{ + async fn relay_channel_open_handshake( + &self, + src_channel_id: &::ChannelId, + src_port_id: &::PortId, + dst_port_id: &::PortId, + ) -> Result<::ChannelId, Self::Error> { + RelayChannelOpenHandshake::relay_channel_open_handshake( + self, + src_channel_id, + src_port_id, + dst_port_id, + ) + .await + } +} diff --git a/crates/relayer-all-in-one/src/one_for_all/impls/relay/client.rs b/crates/relayer-all-in-one/src/one_for_all/impls/relay/client.rs new file mode 100644 index 0000000000..47e9cc6abc --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/impls/relay/client.rs @@ -0,0 +1,30 @@ +use ibc_relayer_components::relay::components::create_client::InjectMissingCreateClientEventError; +use ibc_relayer_components::relay::traits::target::{DestinationTarget, SourceTarget}; + +use crate::one_for_all::traits::relay::OfaRelay; +use crate::one_for_all::types::chain::OfaChainWrapper; +use crate::one_for_all::types::relay::OfaRelayWrapper; + +impl InjectMissingCreateClientEventError for OfaRelayWrapper +where + Relay: OfaRelay, +{ + fn missing_create_client_event_error( + src_chain: &OfaChainWrapper, + dst_chain: &OfaChainWrapper, + ) -> Self::Error { + Relay::missing_src_create_client_event_error(&src_chain.chain, &dst_chain.chain) + } +} + +impl InjectMissingCreateClientEventError for OfaRelayWrapper +where + Relay: OfaRelay, +{ + fn missing_create_client_event_error( + dst_chain: &OfaChainWrapper, + src_chain: &OfaChainWrapper, + ) -> Self::Error { + Relay::missing_dst_create_client_event_error(&dst_chain.chain, &src_chain.chain) + } +} diff --git a/crates/relayer-all-in-one/src/one_for_all/impls/relay/components.rs b/crates/relayer-all-in-one/src/one_for_all/impls/relay/components.rs new file mode 100644 index 0000000000..9da4c870a0 --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/impls/relay/components.rs @@ -0,0 +1,17 @@ +use ibc_relayer_components::core::traits::component::HasComponents; +use ibc_relayer_components::core::traits::sync::Async; +use ibc_relayer_components_extra::components::extra::closures::relay::components::CanUseExtraRelayComponents; +use ibc_relayer_components_extra::components::extra::relay::ExtraRelayComponents; + +use crate::one_for_all::traits::relay::OfaRelay; +use crate::one_for_all::types::component::OfaComponents; +use crate::one_for_all::types::relay::OfaRelayWrapper; + +impl HasComponents for OfaRelayWrapper +where + Relay: Async, +{ + type Components = ExtraRelayComponents; +} + +impl CanUseExtraRelayComponents for OfaRelayWrapper where Relay: OfaRelay {} diff --git a/crates/relayer-all-in-one/src/one_for_all/impls/relay/connection.rs b/crates/relayer-all-in-one/src/one_for_all/impls/relay/connection.rs new file mode 100644 index 0000000000..ebceec6a88 --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/impls/relay/connection.rs @@ -0,0 +1,130 @@ +use async_trait::async_trait; + +use ibc_relayer_components::relay::impls::connection::open_ack::RelayConnectionOpenAck; +use ibc_relayer_components::relay::impls::connection::open_confirm::RelayConnectionOpenConfirm; +use ibc_relayer_components::relay::impls::connection::open_handshake::RelayConnectionOpenHandshake; +use ibc_relayer_components::relay::impls::connection::open_init::{ + InitializeConnection, InjectMissingConnectionInitEventError, +}; +use ibc_relayer_components::relay::impls::connection::open_try::{ + InjectMissingConnectionTryEventError, RelayConnectionOpenTry, +}; +use ibc_relayer_components::relay::traits::connection::open_ack::{ + CanRelayConnectionOpenAck, ConnectionOpenAckRelayer, +}; +use ibc_relayer_components::relay::traits::connection::open_confirm::{ + CanRelayConnectionOpenConfirm, ConnectionOpenConfirmRelayer, +}; +use ibc_relayer_components::relay::traits::connection::open_handshake::{ + CanRelayConnectionOpenHandshake, ConnectionOpenHandshakeRelayer, +}; +use ibc_relayer_components::relay::traits::connection::open_init::{ + CanInitConnection, ConnectionInitializer, +}; +use ibc_relayer_components::relay::traits::connection::open_try::{ + CanRelayConnectionOpenTry, ConnectionOpenTryRelayer, +}; + +use crate::one_for_all::traits::chain::OfaChainTypes; +use crate::one_for_all::traits::relay::OfaRelay; +use crate::one_for_all::types::relay::OfaRelayWrapper; +use crate::std_prelude::*; + +impl InjectMissingConnectionInitEventError for OfaRelayWrapper +where + Relay: OfaRelay, +{ + fn missing_connection_init_event_error(&self) -> Relay::Error { + self.relay.missing_connection_init_event_error() + } +} + +impl InjectMissingConnectionTryEventError for OfaRelayWrapper +where + Relay: OfaRelay, +{ + fn missing_connection_try_event_error( + &self, + src_connection_id: &::ConnectionId, + ) -> Relay::Error { + self.relay + .missing_connection_try_event_error(src_connection_id) + } +} + +#[async_trait] +impl CanInitConnection for OfaRelayWrapper +where + Relay: OfaRelay, +{ + async fn init_connection( + &self, + init_connection_options: &::InitConnectionOptions, + ) -> Result<::ConnectionId, Self::Error> { + InitializeConnection::init_connection(self, init_connection_options).await + } +} + +#[async_trait] +impl CanRelayConnectionOpenTry for OfaRelayWrapper +where + Relay: OfaRelay, +{ + async fn relay_connection_open_try( + &self, + src_connection_id: &::ConnectionId, + ) -> Result<::ConnectionId, Self::Error> { + RelayConnectionOpenTry::relay_connection_open_try(self, src_connection_id).await + } +} + +#[async_trait] +impl CanRelayConnectionOpenAck for OfaRelayWrapper +where + Relay: OfaRelay, +{ + async fn relay_connection_open_ack( + &self, + src_connection_id: &::ConnectionId, + dst_connection_id: &::ConnectionId, + ) -> Result<(), Self::Error> { + RelayConnectionOpenAck::relay_connection_open_ack( + self, + src_connection_id, + dst_connection_id, + ) + .await + } +} + +#[async_trait] +impl CanRelayConnectionOpenConfirm for OfaRelayWrapper +where + Relay: OfaRelay, +{ + async fn relay_connection_open_confirm( + &self, + src_connection_id: &::ConnectionId, + dst_connection_id: &::ConnectionId, + ) -> Result<(), Self::Error> { + RelayConnectionOpenConfirm::relay_connection_open_confirm( + self, + src_connection_id, + dst_connection_id, + ) + .await + } +} + +#[async_trait] +impl CanRelayConnectionOpenHandshake for OfaRelayWrapper +where + Relay: OfaRelay, +{ + async fn relay_connection_open_handshake( + &self, + src_connection_id: &::ConnectionId, + ) -> Result<::ConnectionId, Self::Error> { + RelayConnectionOpenHandshake::relay_connection_open_handshake(self, src_connection_id).await + } +} diff --git a/crates/relayer-all-in-one/src/one_for_all/impls/relay/error.rs b/crates/relayer-all-in-one/src/one_for_all/impls/relay/error.rs new file mode 100644 index 0000000000..de6e80cd0d --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/impls/relay/error.rs @@ -0,0 +1,19 @@ +use ibc_relayer_components_extra::relay::components::packet_relayers::retry::SupportsPacketRetry; + +use crate::one_for_all::traits::relay::OfaRelay; +use crate::one_for_all::types::relay::OfaRelayWrapper; + +impl SupportsPacketRetry for OfaRelayWrapper +where + Relay: OfaRelay, +{ + const MAX_RETRY: usize = 3; + + fn is_retryable_error(e: &Relay::Error) -> bool { + Relay::is_retryable_error(e) + } + + fn max_retry_exceeded_error(e: Relay::Error) -> Relay::Error { + Relay::max_retry_exceeded_error(e) + } +} diff --git a/crates/relayer-all-in-one/src/one_for_all/impls/relay/logger.rs b/crates/relayer-all-in-one/src/one_for_all/impls/relay/logger.rs new file mode 100644 index 0000000000..763ee2be4c --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/impls/relay/logger.rs @@ -0,0 +1,20 @@ +use ibc_relayer_components::logger::traits::has_logger::{HasLogger, HasLoggerType}; + +use crate::one_for_all::traits::relay::OfaRelay; +use crate::one_for_all::types::relay::OfaRelayWrapper; + +impl HasLoggerType for OfaRelayWrapper +where + Relay: OfaRelay, +{ + type Logger = Relay::Logger; +} + +impl HasLogger for OfaRelayWrapper +where + Relay: OfaRelay, +{ + fn logger(&self) -> &Self::Logger { + self.relay.logger() + } +} diff --git a/crates/relayer-all-in-one/src/one_for_all/impls/relay/mod.rs b/crates/relayer-all-in-one/src/one_for_all/impls/relay/mod.rs new file mode 100644 index 0000000000..cecb7f8efd --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/impls/relay/mod.rs @@ -0,0 +1,10 @@ +pub mod batch; +pub mod channel; +pub mod client; +pub mod components; +pub mod connection; +pub mod error; +pub mod logger; +pub mod packet_filter; +pub mod packet_relayers; +pub mod types; diff --git a/crates/relayer-all-in-one/src/one_for_all/impls/relay/packet_filter.rs b/crates/relayer-all-in-one/src/one_for_all/impls/relay/packet_filter.rs new file mode 100644 index 0000000000..93de4100e1 --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/impls/relay/packet_filter.rs @@ -0,0 +1,20 @@ +use async_trait::async_trait; +use ibc_relayer_components::relay::traits::components::packet_filter::PacketFilter; + +use crate::one_for_all::traits::relay::OfaRelay; +use crate::one_for_all::types::component::OfaComponents; +use crate::one_for_all::types::relay::OfaRelayWrapper; +use crate::std_prelude::*; + +#[async_trait] +impl PacketFilter> for OfaComponents +where + Relay: OfaRelay, +{ + async fn should_relay_packet( + relay: &OfaRelayWrapper, + packet: &Relay::Packet, + ) -> Result { + relay.relay.should_relay_packet(packet).await + } +} diff --git a/crates/relayer-all-in-one/src/one_for_all/impls/relay/packet_relayers/lock.rs b/crates/relayer-all-in-one/src/one_for_all/impls/relay/packet_relayers/lock.rs new file mode 100644 index 0000000000..26026a0ac7 --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/impls/relay/packet_relayers/lock.rs @@ -0,0 +1,21 @@ +use async_trait::async_trait; +use ibc_relayer_components::relay::traits::packet_lock::HasPacketLock; + +use crate::one_for_all::traits::relay::OfaRelay; +use crate::one_for_all::types::relay::OfaRelayWrapper; +use crate::std_prelude::*; + +#[async_trait] +impl HasPacketLock for OfaRelayWrapper +where + Relay: OfaRelay, +{ + type PacketLock<'a> = Relay::PacketLock<'a>; + + async fn try_acquire_packet_lock<'a>( + &'a self, + packet: &'a Self::Packet, + ) -> Option> { + self.relay.try_acquire_packet_lock(packet).await + } +} diff --git a/crates/relayer-all-in-one/src/one_for_all/impls/relay/packet_relayers/mod.rs b/crates/relayer-all-in-one/src/one_for_all/impls/relay/packet_relayers/mod.rs new file mode 100644 index 0000000000..e949e6a93d --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/impls/relay/packet_relayers/mod.rs @@ -0,0 +1 @@ +pub mod lock; diff --git a/crates/relayer-all-in-one/src/one_for_all/impls/relay/types.rs b/crates/relayer-all-in-one/src/one_for_all/impls/relay/types.rs new file mode 100644 index 0000000000..a06a232ff0 --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/impls/relay/types.rs @@ -0,0 +1,65 @@ +use ibc_relayer_components::core::traits::error::HasErrorType; +use ibc_relayer_components::relay::traits::chains::HasRelayChains; +use ibc_relayer_components::runtime::traits::runtime::HasRuntime; + +use crate::one_for_all::traits::chain::OfaChainTypes; +use crate::one_for_all::traits::relay::OfaRelay; +use crate::one_for_all::types::chain::OfaChainWrapper; +use crate::one_for_all::types::relay::OfaRelayWrapper; + +impl HasErrorType for OfaRelayWrapper +where + Relay: OfaRelay, +{ + type Error = Relay::Error; +} + +impl HasRuntime for OfaRelayWrapper +where + Relay: OfaRelay, +{ + type Runtime = Relay::Runtime; + + fn runtime(&self) -> &Self::Runtime { + self.relay.runtime() + } + + fn runtime_error(e: ::Error) -> Relay::Error { + Relay::runtime_error(e) + } +} + +impl HasRelayChains for OfaRelayWrapper +where + Relay: OfaRelay, +{ + type Packet = ::OutgoingPacket; + + type SrcChain = OfaChainWrapper; + + type DstChain = OfaChainWrapper; + + fn src_chain_error(e: ::Error) -> Self::Error { + Relay::src_chain_error(e) + } + + fn dst_chain_error(e: ::Error) -> Self::Error { + Relay::dst_chain_error(e) + } + + fn src_chain(&self) -> &Self::SrcChain { + self.relay.src_chain() + } + + fn dst_chain(&self) -> &Self::DstChain { + self.relay.dst_chain() + } + + fn src_client_id(&self) -> &::ClientId { + self.relay.src_client_id() + } + + fn dst_client_id(&self) -> &::ClientId { + self.relay.dst_client_id() + } +} diff --git a/crates/relayer-all-in-one/src/one_for_all/mod.rs b/crates/relayer-all-in-one/src/one_for_all/mod.rs new file mode 100644 index 0000000000..bb294d36dc --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/mod.rs @@ -0,0 +1,22 @@ +/*! + The one-for-all provider constructs for the minimal relayer. +*/ + +//! An abstract trait which stipulates all of dependencies that an application +//! needs to provide in order to make use of the functionality the framework +//! exposes. +//! +//! There is a duality between the `one_for_all` trait and the `all_for_one` +//! trait such that types that implement the `one_for_all` trait gain access to +//! the APIs exposed by the `all_for_one` trait. The `one_for_all` trait +//! encapsulates what functionality applications need to provide. The +//! `all_for_one` trait encapsulates what functionality applications have +//! access to and can use once the `one_for_all` trait is implemented. +//! +//! This is trait is meant to be the main trait that users of the framework +//! implement in order to use the APIs provided by the framework, which are +//! encapsulated in the `all_for_one` trait. + +pub mod impls; +pub mod traits; +pub mod types; diff --git a/crates/relayer-all-in-one/src/one_for_all/traits/birelay.rs b/crates/relayer-all-in-one/src/one_for_all/traits/birelay.rs new file mode 100644 index 0000000000..fc39f449be --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/traits/birelay.rs @@ -0,0 +1,52 @@ +use core::fmt::Debug; + +use ibc_relayer_components::core::traits::error::HasErrorType; +use ibc_relayer_components::core::traits::sync::Async; +use ibc_relayer_components::logger::traits::level::HasBaseLogLevels; + +use crate::all_for_one::runtime::AfoRuntime; +use crate::one_for_all::traits::relay::{OfaHomogeneousRelay, OfaRelay}; +use crate::one_for_all::types::relay::OfaRelayWrapper; + +pub trait OfaBiRelay: Async { + type Error: Debug + Async; + + type Runtime: AfoRuntime; + + type Logger: HasBaseLogLevels; + + type RelayAToB: OfaRelay; + + type RelayBToA: OfaRelay< + SrcChain = ::DstChain, + DstChain = ::SrcChain, + Error = ::Error, + Logger = ::Logger, + >; + + fn runtime(&self) -> &Self::Runtime; + + fn runtime_error(e: ::Error) -> Self::Error; + + fn logger(&self) -> &Self::Logger; + + fn relay_a_to_b(&self) -> &OfaRelayWrapper; + + fn relay_b_to_a(&self) -> &OfaRelayWrapper; + + fn relay_error(e: ::Error) -> Self::Error; +} + +pub trait OfaHomogeneousBiRelay: + OfaBiRelay +{ + type Relay: OfaHomogeneousRelay; +} + +impl OfaHomogeneousBiRelay for BiRelay +where + BiRelay: OfaBiRelay, + Relay: OfaHomogeneousRelay, +{ + type Relay = Relay; +} diff --git a/crates/relayer-all-in-one/src/one_for_all/traits/builder.rs b/crates/relayer-all-in-one/src/one_for_all/traits/builder.rs new file mode 100644 index 0000000000..af90cf5f19 --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/traits/builder.rs @@ -0,0 +1,156 @@ +use alloc::collections::BTreeMap; +use alloc::sync::Arc; +use core::fmt::Debug; +use ibc_relayer_components::core::traits::error::HasErrorType; +use ibc_relayer_components::logger::traits::level::HasBaseLogLevels; +use ibc_relayer_components::runtime::traits::mutex::HasMutex; +use ibc_relayer_components_extra::batch::types::config::BatchConfig; + +use async_trait::async_trait; +use ibc_relayer_components::core::traits::sync::Async; + +use crate::all_for_one::runtime::AfoRuntime; +use crate::one_for_all::traits::birelay::OfaBiRelay; +use crate::one_for_all::traits::chain::OfaChainTypes; +use crate::one_for_all::traits::relay::OfaRelay; +use crate::one_for_all::types::batch::aliases::MessageBatchSender; +use crate::one_for_all::types::chain::OfaChainWrapper; +use crate::one_for_all::types::relay::OfaRelayWrapper; +use crate::std_prelude::*; + +#[async_trait] +pub trait OfaBuilder: Async { + type Error: Debug + Async; + + type Runtime: AfoRuntime; + + type Logger: HasBaseLogLevels; + + type BiRelay: OfaBiRelay; + + fn runtime(&self) -> &Self::Runtime; + + fn runtime_error(e: ::Error) -> Self::Error; + + fn birelay_error(e: ::Error) -> Self::Error; + + fn logger(&self) -> &Self::Logger; + + fn batch_config(&self) -> &BatchConfig; + + async fn build_chain_a(&self, chain_id: &ChainIdA) -> Result, Self::Error>; + + async fn build_chain_b(&self, chain_id: &ChainIdB) -> Result, Self::Error>; + + async fn build_relay_a_to_b( + &self, + src_client_id: &ClientIdA, + dst_client_id: &ClientIdB, + src_chain: OfaChainWrapper>, + dst_chain: OfaChainWrapper>, + src_batch_sender: MessageBatchSender, RelayError>, + dst_batch_sender: MessageBatchSender, RelayError>, + ) -> Result, Self::Error>; + + async fn build_relay_b_to_a( + &self, + src_client_id: &ClientIdB, + dst_client_id: &ClientIdA, + src_chain: OfaChainWrapper>, + dst_chain: OfaChainWrapper>, + src_batch_sender: MessageBatchSender, RelayError>, + dst_batch_sender: MessageBatchSender, RelayError>, + ) -> Result, Self::Error>; + + async fn build_birelay( + &self, + relay_a_to_b: OfaRelayWrapper>, + relay_b_to_a: OfaRelayWrapper>, + ) -> Result; +} + +pub type BiRelay = ::BiRelay; + +pub type RelayAToB = as OfaBiRelay>::RelayAToB; + +pub type RelayBToA = as OfaBiRelay>::RelayBToA; + +pub type ChainA = as OfaRelay>::SrcChain; + +pub type RelayError = as OfaRelay>::Error; + +pub type ChainB = as OfaRelay>::DstChain; + +pub type ChainIdA = as OfaChainTypes>::ChainId; + +pub type ChainIdB = as OfaChainTypes>::ChainId; + +pub type ClientIdA = as OfaChainTypes>::ClientId; + +pub type ClientIdB = as OfaChainTypes>::ClientId; + +pub type Runtime = ::Runtime; + +pub type Mutex = as HasMutex>::Mutex; + +pub type ChainACache = + Arc, OfaChainWrapper>>>>; + +pub type ChainBCache = + Arc, OfaChainWrapper>>>>; + +pub type RelayAToBCache = Arc< + Mutex< + Builder, + BTreeMap< + ( + ChainIdA, + ChainIdB, + ClientIdA, + ClientIdB, + ), + OfaRelayWrapper>, + >, + >, +>; + +pub type RelayBToACache = Arc< + Mutex< + Builder, + BTreeMap< + ( + ChainIdB, + ChainIdA, + ClientIdB, + ClientIdA, + ), + OfaRelayWrapper>, + >, + >, +>; + +pub type BatchSenderCacheA = Mutex< + Build, + BTreeMap< + ( + ChainIdA, + ChainIdB, + ClientIdA, + ClientIdB, + ), + MessageBatchSender, RelayError>, + >, +>; + +pub type BatchSenderCacheB = Mutex< + Build, + BTreeMap< + ( + ChainIdB, + ChainIdA, + ClientIdB, + ClientIdA, + ), + MessageBatchSender, RelayError>, + >, +>; diff --git a/crates/relayer-all-in-one/src/one_for_all/traits/chain.rs b/crates/relayer-all-in-one/src/one_for_all/traits/chain.rs new file mode 100644 index 0000000000..000012482a --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/traits/chain.rs @@ -0,0 +1,545 @@ +//! The `OfaChainWrapper` trait specifies what a chain context needs to provide +//! in order to gain access to the APIs provided by the `AfoBaseChain` +//! trait. + +use alloc::sync::Arc; +use core::fmt::{Debug, Display}; +use ibc_relayer_components::core::traits::error::HasErrorType; +use ibc_relayer_components::logger::traits::level::HasBaseLogLevels; +use ibc_relayer_components::logger::traits::logger::BaseLogger; +use ibc_relayer_components_extra::telemetry::traits::metrics::HasBasicMetrics; + +use async_trait::async_trait; +use ibc_relayer_components::core::traits::sync::Async; +use ibc_relayer_components::runtime::traits::subscription::Subscription; + +use crate::all_for_one::runtime::AfoRuntime; +use crate::std_prelude::*; + +#[async_trait] +pub trait OfaChainTypes: Async { + /** + Corresponds to + [`HasErrorType::Error`](ibc_relayer_components::core::traits::error::HasErrorType::Error). + */ + type Error: Async + Debug; + + /** + Corresponds to + [`HasRuntime::Runtime`](ibc_relayer_components::runtime::traits::runtime::HasRuntime::Runtime). + */ + type Runtime: AfoRuntime; + + type Logger: HasBaseLogLevels; + + type Telemetry: HasBasicMetrics; + + /** + Corresponds to + [`HasMessageType::Message`](ibc_relayer_components::chain::traits::types::message::HasMessageType::Message). + */ + type Message: Async; + + /** + Corresponds to + [`HasEventType::Event`](ibc_relayer_components::chain::traits::types::event::HasEventType::Event). + */ + type Event: Async; + + type ClientState: Async; + + /** + Corresponds to + [`HasConsensusStateType::ConsensusState`](ibc_relayer_components::chain::traits::types::consensus_state::HasConsensusStateType::ConsensusState). + */ + type ConsensusState: Async; + + /** + Corresponds to + [`HasChainTypes::Height`](ibc_relayer_components::chain::traits::types::height::HasHeightType::Height). + */ + type Height: Clone + Ord + Display + Async; + + /** + Corresponds to + [`HasChainTypes::Timestamp`](ibc_relayer_components::chain::traits::types::timestamp::HasTimestampType::Timestamp). + */ + type Timestamp: Ord + Display + Async; + + /** + Corresponds to + [`HasChainIdType::ChainId`](ibc_relayer_components::chain::traits::types::chain_id::HasChainIdType::ChainId). + */ + type ChainId: Eq + Ord + Display + Clone + Async; + + /** + Corresponds to + [`HasIbcChainTypes::ClientId`](ibc_relayer_components::chain::traits::types::ibc::HasIbcChainTypes::ClientId). + */ + type ClientId: Ord + Display + Clone + Async; + + /** + Corresponds to + [`HasIbcChainTypes::ConnectionId`](ibc_relayer_components::chain::traits::types::ibc::HasIbcChainTypes::ConnectionId). + */ + type ConnectionId: Display + Clone + Async; + + /** + Corresponds to + [`HasIbcChainTypes::ChannelId`](ibc_relayer_components::chain::traits::types::ibc::HasIbcChainTypes::ChannelId). + */ + type ChannelId: Display + Clone + Async; + + /** + Corresponds to + [`HasIbcChainTypes::PortId`](ibc_relayer_components::chain::traits::types::ibc::HasIbcChainTypes::PortId). + */ + type PortId: Display + Clone + Async; + + /** + Corresponds to + [`HasIbcChainTypes::Sequence`](ibc_relayer_components::chain::traits::types::ibc::HasIbcChainTypes::Sequence). + */ + type Sequence: Display + Clone + Async; + + /** + Corresponds to + [`HasChainStatusType::ChainStatus`](ibc_relayer_components::chain::traits::types::status::HasChainStatusType::ChainStatus). + */ + type ChainStatus: Async; + + /** + Corresponds to + [`HasIbcPacketTypes::IncomingPacket`](ibc_relayer_components::chain::traits::types::packet::HasIbcPacketTypes::IncomingPacket) + */ + type IncomingPacket: Async; + + /** + Corresponds to + [`HasIbcPacketTypes::OutgoingPacket`](ibc_relayer_components::chain::traits::types::packet::HasIbcPacketTypes::OutgoingPacket) + */ + type OutgoingPacket: Async; + + type CreateClientPayloadOptions: Async; + + type InitConnectionOptions: Async; + + type InitChannelOptions: Async; + + type CreateClientPayload: Async; + + type UpdateClientPayload: Async; + + type ConnectionOpenInitPayload: Async; + + type ConnectionOpenTryPayload: Async; + + type ConnectionOpenAckPayload: Async; + + type ConnectionOpenConfirmPayload: Async; + + type ChannelOpenTryPayload: Async; + + type ChannelOpenAckPayload: Async; + + type ChannelOpenConfirmPayload: Async; + + type ReceivePacketPayload: Async; + + type AckPacketPayload: Async; + + type TimeoutUnorderedPacketPayload: Async; + + type CreateClientEvent: Async; + + type ConnectionOpenInitEvent: Async; + + type ConnectionOpenTryEvent: Async; + + type ChannelOpenInitEvent: Async; + + type ChannelOpenTryEvent: Async; + + type SendPacketEvent: Async; + + /** + Corresponds to + [`HasWriteAcknowledgementEvent::WriteAcknowledgementEvent`](ibc_relayer_components::chain::traits::types::ibc_events::write_ack::HasWriteAcknowledgementEvent::WriteAcknowledgementEvent). + */ + type WriteAcknowledgementEvent: Async; +} + +#[async_trait] +pub trait OfaChain: OfaChainTypes { + fn runtime(&self) -> &Self::Runtime; + + fn runtime_error(e: ::Error) -> Self::Error; + + fn logger(&self) -> &Self::Logger; + + fn telemetry(&self) -> &Self::Telemetry; + + fn log_event<'a>(event: &'a Self::Event) -> ::LogValue<'a>; + + fn log_incoming_packet<'a>( + event: &'a Self::IncomingPacket, + ) -> ::LogValue<'a>; + + fn log_outgoing_packet<'a>( + event: &'a Self::OutgoingPacket, + ) -> ::LogValue<'a>; + + fn increment_height(height: &Self::Height) -> Result; + + fn estimate_message_size(message: &Self::Message) -> Result; + + fn chain_status_height(status: &Self::ChainStatus) -> &Self::Height; + + fn chain_status_timestamp(status: &Self::ChainStatus) -> &Self::Timestamp; + + fn try_extract_write_acknowledgement_event( + event: &Self::Event, + ) -> Option; + + fn try_extract_send_packet_event(event: &Self::Event) -> Option; + + fn extract_packet_from_send_packet_event(event: &Self::SendPacketEvent) + -> Self::OutgoingPacket; + + fn extract_packet_from_write_acknowledgement_event( + ack: &Self::WriteAcknowledgementEvent, + ) -> &Self::IncomingPacket; + + fn try_extract_create_client_event(event: Self::Event) -> Option; + + fn create_client_event_client_id(event: &Self::CreateClientEvent) -> &Self::ClientId; + + fn try_extract_connection_open_init_event( + event: Self::Event, + ) -> Option; + + fn client_state_latest_height(client_state: &Self::ClientState) -> &Self::Height; + + fn connection_open_init_event_connection_id( + event: &Self::ConnectionOpenInitEvent, + ) -> &Self::ConnectionId; + + fn try_extract_connection_open_try_event( + event: Self::Event, + ) -> Option; + + fn connection_open_try_event_connection_id( + event: &Self::ConnectionOpenTryEvent, + ) -> &Self::ConnectionId; + + fn try_extract_channel_open_init_event( + event: Self::Event, + ) -> Option; + + fn channel_open_init_event_channel_id(event: &Self::ChannelOpenInitEvent) -> &Self::ChannelId; + + fn try_extract_channel_open_try_event(event: Self::Event) -> Option; + + fn channel_open_try_event_channel_id(event: &Self::ChannelOpenTryEvent) -> &Self::ChannelId; + + /** + Corresponds to + [`CanSendMessages::send_messages`](ibc_relayer_components::chain::traits::message_sender::CanSendMessages::send_messages) + */ + async fn send_messages( + &self, + messages: Vec, + ) -> Result>, Self::Error>; + + fn chain_id(&self) -> &Self::ChainId; + + async fn query_chain_status(&self) -> Result; + + fn event_subscription(&self) -> &Arc>; + + async fn query_write_acknowledgement_event( + &self, + packet: &Self::IncomingPacket, + ) -> Result, Self::Error>; + + async fn build_create_client_payload( + &self, + create_client_options: &Self::CreateClientPayloadOptions, + ) -> Result; + + async fn build_update_client_payload( + &self, + trusted_height: &Self::Height, + target_height: &Self::Height, + client_state: Self::ClientState, + ) -> Result; + + async fn build_connection_open_init_payload( + &self, + client_state: &Self::ClientState, + ) -> Result; + + async fn build_connection_open_try_payload( + &self, + client_state: &Self::ClientState, + height: &Self::Height, + client_id: &Self::ClientId, + connection_id: &Self::ConnectionId, + ) -> Result; + + async fn build_connection_open_ack_payload( + &self, + client_state: &Self::ClientState, + height: &Self::Height, + client_id: &Self::ClientId, + connection_id: &Self::ConnectionId, + ) -> Result; + + async fn build_connection_open_confirm_payload( + &self, + client_state: &Self::ClientState, + height: &Self::Height, + client_id: &Self::ClientId, + connection_id: &Self::ConnectionId, + ) -> Result; + + async fn build_channel_open_try_payload( + &self, + client_state: &Self::ClientState, + height: &Self::Height, + port_id: &Self::PortId, + channel_id: &Self::ChannelId, + ) -> Result; + + async fn build_channel_open_ack_payload( + &self, + client_state: &Self::ClientState, + height: &Self::Height, + port_id: &Self::PortId, + channel_id: &Self::ChannelId, + ) -> Result; + + async fn build_channel_open_confirm_payload( + &self, + client_state: &Self::ClientState, + height: &Self::Height, + port_id: &Self::PortId, + channel_id: &Self::ChannelId, + ) -> Result; + + async fn build_receive_packet_payload( + &self, + client_state: &Self::ClientState, + height: &Self::Height, + packet: &Self::OutgoingPacket, + ) -> Result; + + async fn build_ack_packet_payload( + &self, + client_state: &Self::ClientState, + height: &Self::Height, + packet: &Self::IncomingPacket, + ack: &Self::WriteAcknowledgementEvent, + ) -> Result; + + async fn build_timeout_unordered_packet_payload( + &self, + client_state: &Self::ClientState, + height: &Self::Height, + packet: &Self::IncomingPacket, + ) -> Result; +} + +#[async_trait] +pub trait OfaIbcChain: OfaChain +where + Counterparty: OfaChainTypes, +{ + fn incoming_packet_src_channel_id(packet: &Self::IncomingPacket) -> &Counterparty::ChannelId; + + fn incoming_packet_dst_channel_id(packet: &Self::IncomingPacket) -> &Self::ChannelId; + + fn incoming_packet_src_port(packet: &Self::IncomingPacket) -> &Counterparty::PortId; + + fn incoming_packet_dst_port(packet: &Self::IncomingPacket) -> &Self::PortId; + + fn incoming_packet_sequence(packet: &Self::IncomingPacket) -> &Counterparty::Sequence; + + fn incoming_packet_timeout_height(packet: &Self::IncomingPacket) -> Option<&Self::Height>; + + fn incoming_packet_timeout_timestamp(packet: &Self::IncomingPacket) -> &Self::Timestamp; + + fn outgoing_packet_src_channel_id(packet: &Self::OutgoingPacket) -> &Self::ChannelId; + + fn outgoing_packet_dst_channel_id(packet: &Self::OutgoingPacket) -> &Counterparty::ChannelId; + + fn outgoing_packet_src_port(packet: &Self::OutgoingPacket) -> &Self::PortId; + + fn outgoing_packet_dst_port(packet: &Self::OutgoingPacket) -> &Counterparty::PortId; + + fn outgoing_packet_sequence(packet: &Self::OutgoingPacket) -> &Self::Sequence; + + fn outgoing_packet_timeout_height( + packet: &Self::OutgoingPacket, + ) -> Option<&Counterparty::Height>; + + fn outgoing_packet_timeout_timestamp(packet: &Self::OutgoingPacket) + -> &Counterparty::Timestamp; + + fn counterparty_message_height_for_update_client( + message: &Self::Message, + ) -> Option; + + async fn query_chain_id_from_channel_id( + &self, + channel_id: &Self::ChannelId, + port_id: &Self::PortId, + ) -> Result; + + async fn query_client_state( + &self, + client_id: &Self::ClientId, + ) -> Result; + + async fn query_consensus_state( + &self, + client_id: &Self::ClientId, + height: &Counterparty::Height, + ) -> Result; + + async fn find_consensus_state_height_before( + &self, + client_id: &Self::ClientId, + target_height: &Counterparty::Height, + ) -> Result; + + async fn query_is_packet_received( + &self, + port_id: &Self::PortId, + channel_id: &Self::ChannelId, + sequence: &Counterparty::Sequence, + ) -> Result; + + /// Query the sequences of the packets that the chain has committed to be + /// sent to the counterparty chain, of which the full packet relaying is not + /// yet completed. Once the chain receives the ack from the counterparty + /// chain, a given sequence should be removed from the packet commitment list. + async fn query_packet_commitments( + &self, + channel_id: &Self::ChannelId, + port_id: &Self::PortId, + ) -> Result<(Vec, Self::Height), Self::Error>; + + /// Given a list of counterparty commitment sequences, + /// return a filtered list of sequences which the chain + /// has not received the packet from the counterparty chain. + async fn query_unreceived_packet_sequences( + &self, + channel_id: &Self::ChannelId, + port_id: &Self::PortId, + sequences: &[Counterparty::Sequence], + ) -> Result, Self::Error>; + + /// Given a list of sequences, a channel and port will query a list of outgoing + /// packets which have not been relayed. + async fn query_send_packets_from_sequences( + &self, + channel_id: &Self::ChannelId, + port_id: &Self::PortId, + counterparty_channel_id: &Counterparty::ChannelId, + counterparty_port_id: &Counterparty::PortId, + sequences: &[Self::Sequence], + // The height is given to query the packets from a specific height. + // This height should be the same as the query height from the + // `CanQueryPacketCommitments` made on the same chain. + height: &Self::Height, + ) -> Result, Self::Error>; + + async fn build_receive_packet_message( + &self, + packet: &Self::IncomingPacket, + payload: Counterparty::ReceivePacketPayload, + ) -> Result; + + async fn build_ack_packet_message( + &self, + packet: &Self::OutgoingPacket, + payload: Counterparty::AckPacketPayload, + ) -> Result; + + async fn build_timeout_unordered_packet_message( + &self, + packet: &Self::OutgoingPacket, + payload: Counterparty::TimeoutUnorderedPacketPayload, + ) -> Result; + + async fn build_create_client_message( + &self, + counterparty_payload: Counterparty::CreateClientPayload, + ) -> Result; + + async fn build_update_client_message( + &self, + client_id: &Self::ClientId, + payload: Counterparty::UpdateClientPayload, + ) -> Result, Self::Error>; + + async fn build_connection_open_init_message( + &self, + client_id: &Self::ClientId, + counterparty_client_id: &Counterparty::ClientId, + init_connection_options: &Self::InitConnectionOptions, + counterparty_payload: Counterparty::ConnectionOpenInitPayload, + ) -> Result; + + async fn build_connection_open_try_message( + &self, + client_id: &Self::ClientId, + counterparty_client_id: &Counterparty::ClientId, + counterparty_connection_id: &Counterparty::ConnectionId, + counterparty_payload: Counterparty::ConnectionOpenTryPayload, + ) -> Result; + + async fn build_connection_open_ack_message( + &self, + connection_id: &Self::ConnectionId, + counterparty_connection_id: &Counterparty::ConnectionId, + counterparty_payload: Counterparty::ConnectionOpenAckPayload, + ) -> Result; + + async fn build_connection_open_confirm_message( + &self, + connection_id: &Self::ConnectionId, + counterparty_payload: Counterparty::ConnectionOpenConfirmPayload, + ) -> Result; + + async fn build_channel_open_init_message( + &self, + port_id: &Self::PortId, + counterparty_port_id: &Counterparty::PortId, + init_channel_options: &Self::InitChannelOptions, + ) -> Result; + + async fn build_channel_open_try_message( + &self, + port_id: &Self::PortId, + counterparty_port_id: &Counterparty::PortId, + counterparty_channel_id: &Counterparty::ChannelId, + counterparty_payload: Counterparty::ChannelOpenTryPayload, + ) -> Result; + + async fn build_channel_open_ack_message( + &self, + port_id: &Self::PortId, + channel_id: &Self::ChannelId, + counterparty_channel_id: &Counterparty::ChannelId, + counterparty_payload: Counterparty::ChannelOpenAckPayload, + ) -> Result; + + async fn build_channel_open_confirm_message( + &self, + port_id: &Self::PortId, + channel_id: &Self::ChannelId, + counterparty_payload: Counterparty::ChannelOpenConfirmPayload, + ) -> Result; +} diff --git a/crates/relayer-all-in-one/src/one_for_all/traits/mod.rs b/crates/relayer-all-in-one/src/one_for_all/traits/mod.rs new file mode 100644 index 0000000000..cdcf68ace5 --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/traits/mod.rs @@ -0,0 +1,5 @@ +pub mod birelay; +pub mod builder; +pub mod chain; +pub mod relay; +pub mod runtime; diff --git a/crates/relayer-all-in-one/src/one_for_all/traits/relay.rs b/crates/relayer-all-in-one/src/one_for_all/traits/relay.rs new file mode 100644 index 0000000000..059eeb0176 --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/traits/relay.rs @@ -0,0 +1,114 @@ +//! The `OfaRelayWrapper` trait specifies what a relay context needs to provide +//! in order to gain access to the APIs provided by the `AfoBaseRelay` +//! trait. + +use core::fmt::Debug; + +use async_trait::async_trait; +use ibc_relayer_components::core::traits::error::HasErrorType; +use ibc_relayer_components::core::traits::sync::Async; +use ibc_relayer_components::logger::traits::level::HasBaseLogLevels; + +use crate::all_for_one::runtime::AfoRuntime; +use crate::one_for_all::traits::chain::{OfaChainTypes, OfaIbcChain}; +use crate::one_for_all::types::batch::aliases::MessageBatchSender; +use crate::one_for_all::types::chain::OfaChainWrapper; +use crate::std_prelude::*; + +#[async_trait] +pub trait OfaRelay: Async { + /** + Corresponds to [`HasErrorType::Error`](ibc_relayer_components::core::traits::error::HasErrorType). + */ + type Error: Debug + Clone + Async; + + type Runtime: AfoRuntime; + + type Logger: HasBaseLogLevels; + + type Packet: Async; + + type SrcChain: OfaIbcChain; + + type DstChain: OfaIbcChain< + Self::SrcChain, + Logger = Self::Logger, + IncomingPacket = Self::Packet, + OutgoingPacket = ::IncomingPacket, + >; + + type PacketLock<'a>: Send; + + fn runtime_error(e: ::Error) -> Self::Error; + + fn src_chain_error(e: ::Error) -> Self::Error; + + fn dst_chain_error(e: ::Error) -> Self::Error; + + fn is_retryable_error(e: &Self::Error) -> bool; + + fn max_retry_exceeded_error(e: Self::Error) -> Self::Error; + + fn missing_src_create_client_event_error( + src_chain: &Self::SrcChain, + dst_chain: &Self::DstChain, + ) -> Self::Error; + + fn missing_dst_create_client_event_error( + dst_chain: &Self::DstChain, + src_chain: &Self::SrcChain, + ) -> Self::Error; + + fn missing_connection_init_event_error(&self) -> Self::Error; + + fn missing_connection_try_event_error( + &self, + src_connection_id: &::ConnectionId, + ) -> Self::Error; + + fn missing_channel_init_event_error(&self) -> Self::Error; + + fn missing_channel_try_event_error( + &self, + src_channel_id: &::ChannelId, + ) -> Self::Error; + + fn runtime(&self) -> &Self::Runtime; + + fn logger(&self) -> &Self::Logger; + + fn src_client_id(&self) -> &::ClientId; + + fn dst_client_id(&self) -> &::ClientId; + + fn src_chain(&self) -> &OfaChainWrapper; + + fn dst_chain(&self) -> &OfaChainWrapper; + + async fn try_acquire_packet_lock<'a>( + &'a self, + packet: &'a Self::Packet, + ) -> Option>; + + async fn should_relay_packet(&self, packet: &Self::Packet) -> Result; + + fn src_chain_message_batch_sender(&self) -> &MessageBatchSender; + + fn dst_chain_message_batch_sender(&self) -> &MessageBatchSender; +} + +pub trait OfaHomogeneousRelay: OfaRelay { + type Chain: OfaIbcChain< + Self::Chain, + IncomingPacket = Self::Packet, + OutgoingPacket = Self::Packet, + >; +} + +impl OfaHomogeneousRelay for Relay +where + Relay: OfaRelay, + Chain: OfaIbcChain, +{ + type Chain = Chain; +} diff --git a/crates/relayer-all-in-one/src/one_for_all/traits/runtime.rs b/crates/relayer-all-in-one/src/one_for_all/traits/runtime.rs new file mode 100644 index 0000000000..8ee550264e --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/traits/runtime.rs @@ -0,0 +1,87 @@ +use core::fmt::Debug; +use core::ops::DerefMut; +use core::pin::Pin; +use core::time::Duration; + +use async_trait::async_trait; +use futures_core::{Future, Stream}; +use ibc_relayer_components::core::traits::sync::Async; +use ibc_relayer_components_extra::runtime::traits::spawn::TaskHandle; + +use crate::std_prelude::*; + +#[async_trait] +pub trait OfaRuntime: Async { + type Error: Async + Debug; + + type Time: Async; + + type Mutex: Async; + + type MutexGuard<'a, T: Async>: 'a + Send + Sync + DerefMut; + + type Sender: Clone + Async + where + T: Async; + + type Receiver: Async + where + T: Async; + + type SenderOnce: Async + where + T: Async; + + type ReceiverOnce: Async + where + T: Async; + + async fn sleep(&self, duration: Duration); + + fn now(&self) -> Self::Time; + + fn duration_since(time: &Self::Time, other: &Self::Time) -> Duration; + + fn new_mutex(item: T) -> Self::Mutex; + + async fn acquire_mutex<'a, T: Async>(mutex: &'a Self::Mutex) -> Self::MutexGuard<'a, T>; + + fn spawn(&self, task: F) -> Box + where + F: Future + Send + 'static, + F::Output: Send + 'static; + + fn new_channel() -> (Self::Sender, Self::Receiver) + where + T: Async; + + fn send(sender: &Self::Sender, value: T) -> Result<(), Self::Error> + where + T: Async; + + async fn receive(receiver: &mut Self::Receiver) -> Result + where + T: Async; + + fn try_receive(receiver: &mut Self::Receiver) -> Result, Self::Error> + where + T: Async; + + fn receiver_to_stream( + receiver: Self::Receiver, + ) -> Pin + Send + 'static>> + where + T: Async; + + fn new_channel_once() -> (Self::SenderOnce, Self::ReceiverOnce) + where + T: Async; + + fn send_once(sender: Self::SenderOnce, value: T) -> Result<(), Self::Error> + where + T: Async; + + async fn receive_once(receiver: Self::ReceiverOnce) -> Result + where + T: Async; +} diff --git a/crates/relayer-all-in-one/src/one_for_all/types/batch/aliases.rs b/crates/relayer-all-in-one/src/one_for_all/types/batch/aliases.rs new file mode 100644 index 0000000000..b869d1494b --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/types/batch/aliases.rs @@ -0,0 +1,34 @@ +use ibc_relayer_components::runtime::traits::mutex::HasMutex; +use ibc_relayer_components_extra::runtime::traits::channel::HasChannelTypes; +use ibc_relayer_components_extra::runtime::traits::channel_once::HasChannelOnceTypes; + +use crate::one_for_all::traits::chain::OfaChainTypes; +use crate::std_prelude::*; + +pub type Runtime = ::Runtime; + +pub type Mutex = as HasMutex>::Mutex; + +pub type Sender = as HasChannelTypes>::Sender; + +pub type Receiver = as HasChannelTypes>::Receiver; + +pub type SenderOnce = as HasChannelOnceTypes>::SenderOnce; + +pub type ReceiverOnce = + as HasChannelOnceTypes>::ReceiverOnce; + +pub type EventResult = Result::Event>>, Error>; + +pub type EventResultSender = SenderOnce>; + +pub type EventResultReceiver = ReceiverOnce>; + +pub type BatchSubmission = ( + Vec<::Message>, + EventResultSender, +); + +pub type MessageBatchSender = Sender>; + +pub type MessageBatchReceiver = Receiver>; diff --git a/crates/relayer-all-in-one/src/one_for_all/types/batch/mod.rs b/crates/relayer-all-in-one/src/one_for_all/types/batch/mod.rs new file mode 100644 index 0000000000..d293a01b6f --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/types/batch/mod.rs @@ -0,0 +1 @@ +pub mod aliases; diff --git a/crates/relayer-all-in-one/src/one_for_all/types/birelay.rs b/crates/relayer-all-in-one/src/one_for_all/types/birelay.rs new file mode 100644 index 0000000000..11d5e01849 --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/types/birelay.rs @@ -0,0 +1,21 @@ +use alloc::sync::Arc; + +pub struct OfaBiRelayWrapper { + pub birelay: Arc, +} + +impl OfaBiRelayWrapper { + pub fn new(birelay: BiRelay) -> Self { + Self { + birelay: Arc::new(birelay), + } + } +} + +impl Clone for OfaBiRelayWrapper { + fn clone(&self) -> Self { + Self { + birelay: self.birelay.clone(), + } + } +} diff --git a/crates/relayer-all-in-one/src/one_for_all/types/builder.rs b/crates/relayer-all-in-one/src/one_for_all/types/builder.rs new file mode 100644 index 0000000000..05ca60ef27 --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/types/builder.rs @@ -0,0 +1,92 @@ +use alloc::collections::BTreeMap; +use alloc::sync::Arc; +use ibc_relayer_components::runtime::traits::mutex::HasMutex; + +use crate::one_for_all::traits::birelay::OfaHomogeneousBiRelay; +use crate::one_for_all::traits::builder::{ + BatchSenderCacheA, BatchSenderCacheB, ChainACache, ChainBCache, OfaBuilder, RelayAToBCache, + RelayBToACache, +}; + +pub struct OfaBuilderWrapper +where + Build: OfaBuilder, +{ + pub builder: Arc, + pub chain_a_cache: ChainACache, + pub chain_b_cache: ChainBCache, + pub relay_a_to_b_cache: RelayAToBCache, + pub relay_b_to_a_cache: RelayBToACache, + pub batch_sender_cache_a: Arc>, + pub batch_sender_cache_b: Arc>, +} + +impl OfaBuilderWrapper +where + Builder: OfaBuilder, +{ + pub fn new_with_heterogenous_cache(builder: Builder) -> Self { + let chain_a_cache = Arc::new(Builder::Runtime::new_mutex(BTreeMap::new())); + + let chain_b_cache = Arc::new(Builder::Runtime::new_mutex(BTreeMap::new())); + + let relay_a_to_b_cache = Arc::new(Builder::Runtime::new_mutex(BTreeMap::new())); + + let relay_b_to_a_cache = Arc::new(Builder::Runtime::new_mutex(BTreeMap::new())); + + let batch_sender_cache_a = Arc::new(Builder::Runtime::new_mutex(BTreeMap::new())); + + let batch_sender_cache_b = Arc::new(Builder::Runtime::new_mutex(BTreeMap::new())); + + Self { + builder: Arc::new(builder), + chain_a_cache, + chain_b_cache, + relay_a_to_b_cache, + relay_b_to_a_cache, + batch_sender_cache_a, + batch_sender_cache_b, + } + } +} + +impl OfaBuilderWrapper +where + Builder: OfaBuilder, + Builder::BiRelay: OfaHomogeneousBiRelay, +{ + pub fn new_with_homogenous_cache(builder: Builder) -> Self { + let chain_cache = Arc::new(Builder::Runtime::new_mutex(BTreeMap::new())); + + let relay_cache = Arc::new(Builder::Runtime::new_mutex(BTreeMap::new())); + + let batch_sender_cache = Arc::new(Builder::Runtime::new_mutex(BTreeMap::new())); + + Self { + builder: Arc::new(builder), + chain_a_cache: chain_cache.clone(), + chain_b_cache: chain_cache, + relay_a_to_b_cache: relay_cache.clone(), + relay_b_to_a_cache: relay_cache, + batch_sender_cache_a: batch_sender_cache.clone(), + batch_sender_cache_b: batch_sender_cache, + } + } +} + +impl Clone for OfaBuilderWrapper +where + Builder: OfaBuilder, +{ + fn clone(&self) -> Self { + Self { + builder: self.builder.clone(), + chain_a_cache: self.chain_a_cache.clone(), + chain_b_cache: self.chain_b_cache.clone(), + relay_a_to_b_cache: self.relay_a_to_b_cache.clone(), + relay_b_to_a_cache: self.relay_b_to_a_cache.clone(), + batch_sender_cache_a: self.batch_sender_cache_a.clone(), + batch_sender_cache_b: self.batch_sender_cache_b.clone(), + } + } +} diff --git a/crates/relayer-all-in-one/src/one_for_all/types/chain.rs b/crates/relayer-all-in-one/src/one_for_all/types/chain.rs new file mode 100644 index 0000000000..4c74dcd50f --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/types/chain.rs @@ -0,0 +1,21 @@ +use alloc::sync::Arc; + +pub struct OfaChainWrapper { + pub chain: Arc, +} + +impl OfaChainWrapper { + pub fn new(chain: Chain) -> Self { + Self { + chain: Arc::new(chain), + } + } +} + +impl Clone for OfaChainWrapper { + fn clone(&self) -> Self { + Self { + chain: self.chain.clone(), + } + } +} diff --git a/crates/relayer-all-in-one/src/one_for_all/types/component.rs b/crates/relayer-all-in-one/src/one_for_all/types/component.rs new file mode 100644 index 0000000000..cbb2187e7d --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/types/component.rs @@ -0,0 +1 @@ +pub struct OfaComponents; diff --git a/crates/relayer-all-in-one/src/one_for_all/types/mod.rs b/crates/relayer-all-in-one/src/one_for_all/types/mod.rs new file mode 100644 index 0000000000..3de19630d5 --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/types/mod.rs @@ -0,0 +1,6 @@ +pub mod batch; +pub mod birelay; +pub mod builder; +pub mod chain; +pub mod component; +pub mod relay; diff --git a/crates/relayer-all-in-one/src/one_for_all/types/relay.rs b/crates/relayer-all-in-one/src/one_for_all/types/relay.rs new file mode 100644 index 0000000000..7919287749 --- /dev/null +++ b/crates/relayer-all-in-one/src/one_for_all/types/relay.rs @@ -0,0 +1,21 @@ +use alloc::sync::Arc; + +pub struct OfaRelayWrapper { + pub relay: Arc, +} + +impl OfaRelayWrapper { + pub fn new(relay: Relay) -> Self { + Self { + relay: Arc::new(relay), + } + } +} + +impl Clone for OfaRelayWrapper { + fn clone(&self) -> Self { + Self { + relay: self.relay.clone(), + } + } +} diff --git a/crates/relayer-all-in-one/src/std_prelude.rs b/crates/relayer-all-in-one/src/std_prelude.rs new file mode 100644 index 0000000000..ac791e1a54 --- /dev/null +++ b/crates/relayer-all-in-one/src/std_prelude.rs @@ -0,0 +1,12 @@ +// Re-export according to alloc::prelude::v1 because it is not yet stabilized +// https://doc.rust-lang.org/src/alloc/prelude/v1.rs.html +pub use alloc::borrow::ToOwned; +pub use alloc::boxed::Box; +pub use alloc::format; +pub use alloc::string::{String, ToString}; +pub use alloc::vec; +pub use alloc::vec::Vec; +// Those are exported by default in the std prelude in Rust 2021 +pub use core::convert::{TryFrom, TryInto}; +pub use core::iter::FromIterator; +pub use core::prelude::v1::*; diff --git a/crates/relayer-cli/Cargo.toml b/crates/relayer-cli/Cargo.toml index 4bf394526d..ad570357da 100644 --- a/crates/relayer-cli/Cargo.toml +++ b/crates/relayer-cli/Cargo.toml @@ -23,14 +23,21 @@ std = ["flex-error/std"] eyre_tracer = ["flex-error/eyre_tracer"] telemetry = ["ibc-relayer/telemetry", "ibc-telemetry"] rest-server = ["ibc-relayer-rest"] +experimental = ["ibc-relayer-all-in-one", "ibc-relayer-cosmos", "ibc-relayer-runtime"] [dependencies] -ibc-relayer-types = { version = "0.25.0", path = "../relayer-types" } -ibc-relayer = { version = "0.25.0", path = "../relayer" } -ibc-telemetry = { version = "0.25.0", path = "../telemetry", optional = true } -ibc-relayer-rest = { version = "0.25.0", path = "../relayer-rest", optional = true } -ibc-chain-registry = { version = "0.25.0" , path = "../chain-registry" } +ibc-relayer-types = { version = "0.25.0", path = "../relayer-types" } +ibc-relayer = { version = "0.25.0", path = "../relayer" } +ibc-telemetry = { version = "0.25.0", path = "../telemetry", optional = true } +ibc-relayer-rest = { version = "0.25.0", path = "../relayer-rest", optional = true } +ibc-chain-registry = { version = "0.25.0" , path = "../chain-registry" } +ibc-relayer-cosmos = { version = "0.25.0", path = "../relayer-cosmos", optional = true } +ibc-relayer-all-in-one = { version = "0.1.0", path = "../relayer-all-in-one", optional = true } +ibc-relayer-components = { version = "0.1.0", path = "../relayer-components" } +ibc-relayer-runtime = { version = "0.1.0" , path = "../relayer-runtime", optional = true } +async-trait = "0.1.56" +chrono = "0.4.24" clap = { version = "3.2", features = ["cargo"] } clap_complete = "3.2" color-eyre = "0.6" @@ -47,6 +54,7 @@ humantime = "2.1" itertools = "0.10.5" oneline-eyre = "0.1" regex = "1.9.5" +opentelemetry = { version = "0.17.0", features = ["metrics"] } serde = { version = "1.0", features = ["serde_derive"] } serde_json = "1" signal-hook = "0.3.17" @@ -55,6 +63,7 @@ tokio = { version = "1.0", features = ["full"] } tracing = "0.1.36" tracing-subscriber = { version = "0.3.14", features = ["fmt", "env-filter", "json"]} time = "0.3" + [dependencies.tendermint] version = "0.33.0" features = ["secp256k1"] diff --git a/crates/relayer-cli/src/cli_utils.rs b/crates/relayer-cli/src/cli_utils.rs index 5a2aae7fc0..24c74cc847 100644 --- a/crates/relayer-cli/src/cli_utils.rs +++ b/crates/relayer-cli/src/cli_utils.rs @@ -5,17 +5,12 @@ use eyre::eyre; use tokio::runtime::Runtime as TokioRuntime; use tracing::debug; +use ibc_relayer::chain::counterparty::{channel_connection_client, ChannelConnectionClient}; +use ibc_relayer::chain::handle::{BaseChainHandle, ChainHandle}; use ibc_relayer::chain::requests::{ IncludeProof, QueryChannelRequest, QueryClientStateRequest, QueryConnectionRequest, QueryHeight, }; -use ibc_relayer::{ - chain::{ - counterparty::{channel_connection_client, ChannelConnectionClient}, - handle::{BaseChainHandle, ChainHandle}, - }, - config::Config, - spawn, -}; +use ibc_relayer::{config::Config, spawn}; use ibc_relayer_types::core::ics02_client::client_state::ClientState; use ibc_relayer_types::core::ics24_host::identifier::{ChainId, ChannelId, PortId}; diff --git a/crates/relayer-cli/src/commands.rs b/crates/relayer-cli/src/commands.rs index 51e7022bd6..b8eef41024 100644 --- a/crates/relayer-cli/src/commands.rs +++ b/crates/relayer-cli/src/commands.rs @@ -102,6 +102,11 @@ pub enum CliCmd { /// Generate auto-complete scripts for different shells. #[clap(display_order = 1000)] Completions(CompletionsCmd), + + /// Relay packets using the new experimental relayer architecture. + #[clap(subcommand)] + #[cfg(feature = "experimental")] + NewRelay(crate::next::commands::new_relay::NewRelayCmds), } /// This trait allows you to define how application configuration is loaded. diff --git a/crates/relayer-cli/src/error.rs b/crates/relayer-cli/src/error.rs index a87c15a5d4..1368ea34a7 100644 --- a/crates/relayer-cli/src/error.rs +++ b/crates/relayer-cli/src/error.rs @@ -1,6 +1,7 @@ //! All errors which can be raised from a command. -use flex_error::{define_error, DisplayError}; +use eyre::Report; +use flex_error::{define_error, DisplayError, TraceError}; use std::io::Error as IoError; use tendermint::Error as TendermintError; @@ -24,6 +25,10 @@ use ibc_relayer::upgrade_chain::UpgradeChainError; define_error! { /// An error raised within the relayer CLI Error { + Generic + [ TraceError ] + | _ | { "generic error" }, + Config |_| { "config error" }, @@ -119,5 +124,15 @@ define_error! { KeyRing [ KeyRingError ] |_| { "keyring error" }, + + Secp256k1KeyPair + { chain_id: ChainId } + | e | { + format_args!("no Secp256k1 key pair for chain {}", e.chain_id) + } } } + +pub fn handle_generic_error(e: impl Into) -> Error { + Error::generic(e.into()) +} diff --git a/crates/relayer-cli/src/lib.rs b/crates/relayer-cli/src/lib.rs index ff28651be7..8ad2d23d71 100644 --- a/crates/relayer-cli/src/lib.rs +++ b/crates/relayer-cli/src/lib.rs @@ -35,5 +35,8 @@ pub mod error; pub mod prelude; pub mod tracing_handle; +#[cfg(feature = "experimental")] +pub mod next; + /// The path to the default configuration file, relative to the home directory. pub const DEFAULT_CONFIG_PATH: &str = ".hermes/config.toml"; diff --git a/crates/relayer-cli/src/next/commands/mod.rs b/crates/relayer-cli/src/next/commands/mod.rs new file mode 100644 index 0000000000..fa1d833be4 --- /dev/null +++ b/crates/relayer-cli/src/next/commands/mod.rs @@ -0,0 +1 @@ +pub mod new_relay; diff --git a/crates/relayer-cli/src/next/commands/new_relay.rs b/crates/relayer-cli/src/next/commands/new_relay.rs new file mode 100644 index 0000000000..4acc1089e1 --- /dev/null +++ b/crates/relayer-cli/src/next/commands/new_relay.rs @@ -0,0 +1,156 @@ +use abscissa_core::clap::Parser; +use abscissa_core::{Command, Runnable}; +use alloc::sync::Arc; +use tokio::runtime::Runtime as TokioRuntime; + +use ibc_relayer_all_in_one::all_for_one::builder::CanBuildAfoBiRelay; +use ibc_relayer_components::relay::traits::components::auto_relayer::CanAutoRelay; +use ibc_relayer_cosmos::contexts::builder::CosmosBuilder; +use ibc_relayer_types::core::ics24_host::identifier::{ChainId, ClientId}; + +use crate::conclude::Output; +use crate::error::{handle_generic_error, Error}; +use crate::prelude::*; + +/// `relay` subcommands which utilize the experimental relayer architecture. +#[derive(Command, Debug, Parser, Runnable)] +pub enum NewRelayCmds { + /// Relay all packets between two chains using the experimental + /// relayer architecture. + Packets(NewRelayPacketsCmd), +} + +/// Encodes the CLI parameters of the experimental `relay packet` command +/// which utilizes the experimental relayer architecture. +#[derive(Debug, Parser, Command, PartialEq, Eq)] +pub struct NewRelayPacketsCmd { + #[clap( + long = "chain-a", + required = true, + value_name = "CHAIN_A_ID", + help_heading = "REQUIRED", + help = "Identifier of chain A" + )] + chain_a_id: ChainId, + + #[clap( + long = "client-a", + required = true, + value_name = "CLIENT_A_ID", + help_heading = "REQUIRED", + help = "Identifier of the client associated with chain A" + )] + client_a_id: ClientId, + + #[clap( + long = "chain-b", + required = true, + value_name = "CHAIN_B_ID", + help_heading = "REQUIRED", + help = "Identifier of chain B" + )] + chain_b_id: ChainId, + + #[clap( + long = "client-b", + required = true, + value_name = "CLIENT_B_ID", + help_heading = "REQUIRED", + help = "Identifier of the client associated with chain B" + )] + client_b_id: ClientId, +} + +impl Runnable for NewRelayPacketsCmd { + fn run(&self) { + let config = app_config(); + + let runtime = Arc::new(TokioRuntime::new().unwrap()); + + let builder = CosmosBuilder::new_wrapped( + (*config).clone(), + runtime.clone(), + Default::default(), + Default::default(), + Default::default(), + Default::default(), + ); + + let res: Result<(), Error> = runtime.block_on(async { + let birelay = builder + .build_afo_birelay( + &self.chain_a_id, + &self.chain_b_id, + &self.client_a_id, + &self.client_b_id, + ) + .await + .map_err(handle_generic_error)?; + + birelay.auto_relay().await.map_err(handle_generic_error)?; + + Ok(()) + }); + + if let Err(e) = res { + Output::error(e).exit(); + } + } +} + +#[cfg(test)] +mod tests { + use super::NewRelayPacketsCmd; + + use std::str::FromStr; + + use abscissa_core::clap::Parser; + use ibc_relayer_types::core::ics24_host::identifier::{ChainId, ClientId}; + + #[test] + fn test_new_relay_packets_required_only() { + assert_eq!( + NewRelayPacketsCmd { + chain_a_id: ChainId::from_string("chain_a_id"), + chain_b_id: ChainId::from_string("chain_b_id"), + client_a_id: ClientId::from_str("client_a_id").unwrap(), + client_b_id: ClientId::from_str("client_b_id").unwrap(), + }, + NewRelayPacketsCmd::parse_from([ + "test", + "--chain-a", + "chain_a_id", + "--client-a", + "client_a_id", + "--chain-b", + "chain_b_id", + "--client-b", + "client_b_id", + ]) + ) + } + + #[test] + fn test_new_relay_packets_no_chain_id() { + assert!(NewRelayPacketsCmd::try_parse_from([ + "test", + "--client-a", + "client_a_id", + "--client-b", + "client_b_id" + ]) + .is_err()) + } + + #[test] + fn test_new_relay_packets_no_client_id() { + assert!(NewRelayPacketsCmd::try_parse_from([ + "test", + "--chain-a", + "chain_a_id", + "--chain-b", + "chain_b_id" + ]) + .is_err()) + } +} diff --git a/crates/relayer-cli/src/next/mod.rs b/crates/relayer-cli/src/next/mod.rs new file mode 100644 index 0000000000..82b6da3c0a --- /dev/null +++ b/crates/relayer-cli/src/next/mod.rs @@ -0,0 +1 @@ +pub mod commands; diff --git a/crates/relayer-components-extra/Cargo.toml b/crates/relayer-components-extra/Cargo.toml new file mode 100644 index 0000000000..4f2081d3db --- /dev/null +++ b/crates/relayer-components-extra/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "ibc-relayer-components-extra" +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" +readme = "README.md" +keywords = ["blockchain", "consensus", "cosmos", "ibc", "tendermint"] +repository = "https://github.com/informalsystems/ibc-rs" +authors = ["Informal Systems "] +rust-version = "1.68" +description = """ + Implementation of an IBC Relayer in Rust, as a library +""" + +[package.metadata.docs.rs] +all-features = true + +[features] + +[dependencies] +async-trait = "0.1.56" +futures-core = { version = "0.3", default-features = false, features = ["alloc"] } +futures-util = { version = "0.3", default-features = false, features = ["alloc"] } +ibc-relayer-components = { version = "0.1.0", path = "../relayer-components" } diff --git a/crates/relayer-components-extra/src/batch/components/message_sender.rs b/crates/relayer-components-extra/src/batch/components/message_sender.rs new file mode 100644 index 0000000000..306d25b431 --- /dev/null +++ b/crates/relayer-components-extra/src/batch/components/message_sender.rs @@ -0,0 +1,49 @@ +use async_trait::async_trait; +use ibc_relayer_components::chain::traits::types::ibc::HasIbcChainTypes; +use ibc_relayer_components::relay::traits::chains::HasRelayChains; +use ibc_relayer_components::relay::traits::components::ibc_message_sender::{ + CanSendIbcMessages, IbcMessageSender, +}; +use ibc_relayer_components::relay::traits::target::ChainTarget; +use ibc_relayer_components::runtime::traits::runtime::HasRuntime; + +use crate::batch::traits::channel::HasMessageBatchSender; +use crate::batch::types::sink::BatchWorkerSink; +use crate::runtime::traits::channel::CanUseChannels; +use crate::runtime::traits::channel_once::{CanCreateChannelsOnce, CanUseChannelsOnce}; +use crate::std_prelude::*; + +pub struct SendMessagesToBatchWorker; + +#[async_trait] +impl IbcMessageSender + for SendMessagesToBatchWorker +where + Relay: HasRelayChains, + Relay: CanSendIbcMessages, + Target: ChainTarget, + TargetChain: HasIbcChainTypes, + TargetChain: HasRuntime, + Runtime: CanCreateChannelsOnce + CanUseChannels + CanUseChannelsOnce, + Relay: HasMessageBatchSender, +{ + async fn send_messages( + context: &Relay, + messages: Vec, + ) -> Result>, Relay::Error> { + let (result_sender, result_receiver) = Runtime::new_channel_once(); + + let message_sender = context.get_batch_sender(); + + Runtime::send(message_sender, (messages, result_sender)) + .map_err(TargetChain::runtime_error) + .map_err(Target::target_chain_error)?; + + let events = Runtime::receive_once(result_receiver) + .await + .map_err(TargetChain::runtime_error) + .map_err(Target::target_chain_error)??; + + Ok(events) + } +} diff --git a/crates/relayer-components-extra/src/batch/components/mod.rs b/crates/relayer-components-extra/src/batch/components/mod.rs new file mode 100644 index 0000000000..79f0f8ecf3 --- /dev/null +++ b/crates/relayer-components-extra/src/batch/components/mod.rs @@ -0,0 +1 @@ +pub mod message_sender; diff --git a/crates/relayer-components-extra/src/batch/mod.rs b/crates/relayer-components-extra/src/batch/mod.rs new file mode 100644 index 0000000000..fbfd97c2d0 --- /dev/null +++ b/crates/relayer-components-extra/src/batch/mod.rs @@ -0,0 +1,4 @@ +pub mod components; +pub mod traits; +pub mod types; +pub mod worker; diff --git a/crates/relayer-components-extra/src/batch/traits/channel.rs b/crates/relayer-components-extra/src/batch/traits/channel.rs new file mode 100644 index 0000000000..3305b9ac7c --- /dev/null +++ b/crates/relayer-components-extra/src/batch/traits/channel.rs @@ -0,0 +1,52 @@ +use ibc_relayer_components::chain::traits::types::chain::HasChainTypes; +use ibc_relayer_components::core::traits::sync::Async; +use ibc_relayer_components::relay::traits::chains::HasRelayChains; +use ibc_relayer_components::relay::traits::target::ChainTarget; +use ibc_relayer_components::runtime::traits::runtime::HasRuntime; + +use crate::batch::types::aliases::MessageBatchSender; +use crate::runtime::traits::channel::HasChannelTypes; +use crate::runtime::traits::channel_once::HasChannelOnceTypes; + +pub trait HasMessageBatchSender: HasRelayChains +where + Target: ChainTarget, + Target::TargetChain: HasRuntime, + ::Runtime: HasChannelTypes + HasChannelOnceTypes, +{ + fn get_batch_sender(&self) -> &MessageBatchSender; +} + +pub trait HasMessageBatchSenderType: Async { + type MessageBatchSender: Async; +} + +impl HasMessageBatchSenderType for Chain +where + Error: Async, + Chain: HasChainTypes + HasRuntime, + Chain::Runtime: HasChannelTypes + HasChannelOnceTypes, +{ + type MessageBatchSender = MessageBatchSender; +} + +pub trait HasMessageBatchSenderTypes: Async { + type SrcMessageBatchSender: Async; + + type DstMessageBatchSender: Async; +} + +impl HasMessageBatchSenderTypes for Relay +where + SrcMessageBatchSender: Async, + DstMessageBatchSender: Async, + Relay: HasRelayChains, + Relay::SrcChain: + HasMessageBatchSenderType, + Relay::DstChain: + HasMessageBatchSenderType, +{ + type SrcMessageBatchSender = SrcMessageBatchSender; + + type DstMessageBatchSender = DstMessageBatchSender; +} diff --git a/crates/relayer-components-extra/src/batch/traits/config.rs b/crates/relayer-components-extra/src/batch/traits/config.rs new file mode 100644 index 0000000000..d81f13444d --- /dev/null +++ b/crates/relayer-components-extra/src/batch/traits/config.rs @@ -0,0 +1,7 @@ +use ibc_relayer_components::core::traits::sync::Async; + +use crate::batch::types::config::BatchConfig; + +pub trait HasBatchConfig: Async { + fn batch_config(&self) -> &BatchConfig; +} diff --git a/crates/relayer-components-extra/src/batch/traits/mod.rs b/crates/relayer-components-extra/src/batch/traits/mod.rs new file mode 100644 index 0000000000..81ad882b9e --- /dev/null +++ b/crates/relayer-components-extra/src/batch/traits/mod.rs @@ -0,0 +1,2 @@ +pub mod channel; +pub mod config; diff --git a/crates/relayer-components-extra/src/batch/types/aliases.rs b/crates/relayer-components-extra/src/batch/types/aliases.rs new file mode 100644 index 0000000000..31336e29f7 --- /dev/null +++ b/crates/relayer-components-extra/src/batch/types/aliases.rs @@ -0,0 +1,31 @@ +use ibc_relayer_components::chain::traits::types::event::HasEventType; +use ibc_relayer_components::chain::traits::types::message::HasMessageType; +use ibc_relayer_components::runtime::types::aliases::Runtime; + +use crate::runtime::traits::channel::HasChannelTypes; +use crate::runtime::traits::channel_once::HasChannelOnceTypes; +use crate::std_prelude::*; + +pub type Sender = as HasChannelTypes>::Sender; + +pub type Receiver = as HasChannelTypes>::Receiver; + +pub type SenderOnce = as HasChannelOnceTypes>::SenderOnce; + +pub type ReceiverOnce = + as HasChannelOnceTypes>::ReceiverOnce; + +pub type EventResult = Result::Event>>, Error>; + +pub type EventResultSender = SenderOnce>; + +pub type EventResultReceiver = ReceiverOnce>; + +pub type BatchSubmission = ( + Vec<::Message>, + EventResultSender, +); + +pub type MessageBatchSender = Sender>; + +pub type MessageBatchReceiver = Receiver>; diff --git a/crates/relayer-components-extra/src/batch/types/config.rs b/crates/relayer-components-extra/src/batch/types/config.rs new file mode 100644 index 0000000000..65c947c2e7 --- /dev/null +++ b/crates/relayer-components-extra/src/batch/types/config.rs @@ -0,0 +1,22 @@ +use core::time::Duration; + +#[derive(Debug, Clone)] +pub struct BatchConfig { + pub max_message_count: usize, + pub max_tx_size: usize, + pub buffer_size: usize, + pub max_delay: Duration, + pub sleep_time: Duration, +} + +impl Default for BatchConfig { + fn default() -> Self { + Self { + max_message_count: 10, + max_tx_size: 1000, + buffer_size: 1000, + max_delay: Duration::from_secs(1), + sleep_time: Duration::from_millis(50), + } + } +} diff --git a/crates/relayer-components-extra/src/batch/types/mod.rs b/crates/relayer-components-extra/src/batch/types/mod.rs new file mode 100644 index 0000000000..c8d3625875 --- /dev/null +++ b/crates/relayer-components-extra/src/batch/types/mod.rs @@ -0,0 +1,3 @@ +pub mod aliases; +pub mod config; +pub mod sink; diff --git a/crates/relayer-components-extra/src/batch/types/sink.rs b/crates/relayer-components-extra/src/batch/types/sink.rs new file mode 100644 index 0000000000..99195c2488 --- /dev/null +++ b/crates/relayer-components-extra/src/batch/types/sink.rs @@ -0,0 +1 @@ +pub struct BatchWorkerSink; diff --git a/crates/relayer-components-extra/src/batch/worker.rs b/crates/relayer-components-extra/src/batch/worker.rs new file mode 100644 index 0000000000..0bd9d0495a --- /dev/null +++ b/crates/relayer-components-extra/src/batch/worker.rs @@ -0,0 +1,389 @@ +use alloc::collections::VecDeque; +use core::mem; +use ibc_relayer_components::relay::traits::components::ibc_message_sender::CanSendIbcMessages; +use ibc_relayer_components::relay::traits::logs::logger::CanLogRelayTarget; + +use async_trait::async_trait; +use ibc_relayer_components::chain::traits::types::chain::HasChainTypes; +use ibc_relayer_components::chain::traits::types::message::{ + CanEstimateMessageSize, HasMessageType, +}; +use ibc_relayer_components::core::traits::sync::Async; +use ibc_relayer_components::logger::traits::level::HasBaseLogLevels; +use ibc_relayer_components::relay::traits::chains::HasRelayChains; +use ibc_relayer_components::relay::traits::target::ChainTarget; +use ibc_relayer_components::runtime::traits::mutex::HasMutex; +use ibc_relayer_components::runtime::traits::runtime::HasRuntime; +use ibc_relayer_components::runtime::traits::sleep::CanSleep; +use ibc_relayer_components::runtime::traits::time::HasTime; +use ibc_relayer_components::runtime::types::aliases::Runtime; + +use crate::batch::types::aliases::{BatchSubmission, EventResultSender, MessageBatchReceiver}; +use crate::batch::types::config::BatchConfig; +use crate::batch::types::sink::BatchWorkerSink; +use crate::runtime::traits::channel::{CanUseChannels, HasChannelTypes}; +use crate::runtime::traits::channel_once::{CanUseChannelsOnce, HasChannelOnceTypes}; +use crate::runtime::traits::spawn::{HasSpawner, Spawner, TaskHandle}; +use crate::std_prelude::*; + +#[async_trait] +pub trait CanSpawnBatchMessageWorker: HasRelayChains +where + Target: ChainTarget, + Target::TargetChain: HasRuntime, + Runtime: HasChannelTypes + HasChannelOnceTypes, +{ + fn spawn_batch_message_worker( + self, + target: Target, + config: BatchConfig, + receiver: MessageBatchReceiver, + ) -> Box; +} + +impl CanSpawnBatchMessageWorker for Relay +where + Relay: CanRunLoop, + Target: ChainTarget, + Target::TargetChain: HasRuntime, + Runtime: HasSpawner + HasChannelTypes + HasChannelOnceTypes, +{ + fn spawn_batch_message_worker( + self, + _target: Target, + config: BatchConfig, + receiver: MessageBatchReceiver, + ) -> Box { + let spawner = Target::target_chain(&self).runtime().spawner(); + + spawner.spawn(async move { + self.run_loop(&config, receiver).await; + }) + } +} + +#[async_trait] +trait CanRunLoop: HasRelayChains +where + Target: ChainTarget, + Target::TargetChain: HasRuntime, + Runtime: HasChannelTypes + HasChannelOnceTypes, +{ + async fn run_loop( + &self, + config: &BatchConfig, + receiver: MessageBatchReceiver, + ); +} + +#[async_trait] +impl CanRunLoop for Relay +where + Relay: CanLogRelayTarget + CanProcessMessageBatches, + Target: ChainTarget, + Target::TargetChain: HasRuntime, + Runtime: HasTime + HasMutex + CanSleep + CanUseChannels + HasChannelOnceTypes, +{ + async fn run_loop( + &self, + config: &BatchConfig, + mut receiver: MessageBatchReceiver, + ) { + let runtime = Target::target_chain(self).runtime(); + let mut pending_batches: VecDeque> = + VecDeque::new(); + + let mut last_sent_time = runtime.now(); + + loop { + let payload = Runtime::try_receive(&mut receiver); + + match payload { + Ok(m_batch) => { + if let Some(batch) = m_batch { + let batch_size = batch.0.len(); + self.log_relay_target( + Relay::Logger::LEVEL_TRACE, + "received message batch", + |log| { + log.display("batch_size", &batch_size); + }, + ); + + pending_batches.push_back(batch); + } + + let current_batch_size = pending_batches.len(); + let now = runtime.now(); + + self.process_message_batches( + config, + &mut pending_batches, + now, + &mut last_sent_time, + ) + .await; + + if pending_batches.len() == current_batch_size { + runtime.sleep(config.sleep_time).await; + } + } + Err(e) => { + self.log_relay_target( + Relay::Logger::LEVEL_ERROR, + "error in try_receive, terminating worker", + |log| { + log.debug("error", &e); + }, + ); + + return; + } + } + } + } +} + +#[async_trait] +pub trait CanProcessMessageBatches: HasRelayChains +where + Target: ChainTarget, + Target::TargetChain: HasRuntime, + Runtime: HasTime + HasChannelTypes + HasChannelOnceTypes, +{ + async fn process_message_batches( + &self, + config: &BatchConfig, + pending_batches: &mut VecDeque>, + now: as HasTime>::Time, + last_sent_time: &mut as HasTime>::Time, + ); +} + +#[async_trait] +impl CanProcessMessageBatches for Relay +where + Relay: CanLogRelayTarget + CanSendReadyBatches + Clone, + Target: ChainTarget, + Target::TargetChain: HasRuntime, + Target::TargetChain: CanPartitionMessageBatches, + Runtime: HasTime + HasSpawner + HasChannelTypes + HasChannelOnceTypes, +{ + async fn process_message_batches( + &self, + config: &BatchConfig, + pending_batches: &mut VecDeque>, + now: Runtime::Time, + last_sent_time: &mut Runtime::Time, + ) { + let ready_batches = Target::TargetChain::partition_message_batches(config, pending_batches); + + if ready_batches.is_empty() { + // If there is nothing to send, return the remaining batches which should also be empty + } else if pending_batches.is_empty() + && Runtime::duration_since(&now, last_sent_time) < config.max_delay + { + // If the current batch is not full and there is still some time until max delay, + // return everything and wait until the next batch is full + *pending_batches = ready_batches; + } else { + let batch_size = ready_batches.len(); + self.log_relay_target(Relay::Logger::LEVEL_TRACE, "sending ready batches", |log| { + log.display("batch_size", &batch_size); + }); + + let spawner = Target::target_chain(self).runtime().spawner(); + let relay = self.clone(); + + spawner.spawn(async move { + relay.send_ready_batches(ready_batches).await; + }); + + *last_sent_time = now; + } + } +} + +pub trait CanPartitionMessageBatches: HasChainTypes + HasRuntime +where + Error: Async, + Self::Runtime: HasChannelTypes + HasChannelOnceTypes, +{ + fn partition_message_batches( + config: &BatchConfig, + pending_batches: &mut VecDeque>, + ) -> VecDeque<(Vec, EventResultSender)>; +} + +impl CanPartitionMessageBatches for Chain +where + Error: Async, + Chain: HasChainTypes + HasRuntime, + Chain: CanEstimateBatchSize, + Runtime: HasChannelTypes + HasChannelOnceTypes, +{ + fn partition_message_batches( + config: &BatchConfig, + pending_batches: &mut VecDeque>, + ) -> VecDeque<(Vec, EventResultSender)> { + let batches = mem::take(pending_batches); + + let mut total_message_count: usize = 0; + let mut total_batch_size: usize = 0; + + let (mut ready_batches, mut remaining_batches): (VecDeque<_>, _) = batches + .into_iter() + .partition(|(current_messages, _sender)| { + if total_message_count > config.max_message_count + || total_batch_size > config.max_tx_size + { + false + } else { + let current_message_count = current_messages.len(); + let current_batch_size = Chain::estimate_batch_size(current_messages); + + if total_message_count + current_message_count > config.max_message_count + || total_batch_size + current_batch_size > config.max_tx_size + { + false + } else { + total_message_count += current_message_count; + total_batch_size += current_batch_size; + + true + } + } + }); + + // If for some reason ready batch is empty but remaining batches is not, + // it means there are single batch that are too big to fit in. + // In that case put the first remaining batch as ready. + if ready_batches.is_empty() && !remaining_batches.is_empty() { + if let Some(batch) = remaining_batches.pop_front() { + ready_batches.push_back(batch); + } + } + + *pending_batches = remaining_batches; + + ready_batches + } +} + +#[async_trait] +pub trait CanSendReadyBatches: HasRelayChains +where + Target: ChainTarget, + Target::TargetChain: HasRuntime, + Runtime: HasChannelTypes + HasChannelOnceTypes, +{ + async fn send_ready_batches( + &self, + ready_batches: VecDeque>, + ); +} + +#[async_trait] +impl CanSendReadyBatches for Relay +where + Relay: CanLogRelayTarget + CanSendIbcMessages, + Target: ChainTarget, + Target::TargetChain: HasRuntime, + Runtime: CanUseChannelsOnce + CanUseChannels, + Relay::Error: Clone, +{ + async fn send_ready_batches( + &self, + ready_batches: VecDeque>, + ) { + let (messages, senders): (Vec<_>, Vec<_>) = ready_batches + .into_iter() + .map(|(messages, result_sender)| { + let message_count = messages.len(); + (messages, (message_count, result_sender)) + }) + .unzip(); + + let in_messages = messages.into_iter().flatten().collect::>(); + + let message_count = in_messages.len(); + + self.log_relay_target( + Relay::Logger::LEVEL_TRACE, + "sending batched messages to inner sender", + |log| { + log.display("message_count", &message_count); + }, + ); + + let send_result = self.send_messages(Target::default(), in_messages).await; + + match send_result { + Err(e) => { + self.log_relay_target( + Relay::Logger::LEVEL_TRACE, + "inner sender returned error result, sending error back to caller", + |log| { + log.debug("error", &e); + }, + ); + + for (_, sender) in senders.into_iter() { + let _ = Runtime::send_once(sender, Err(e.clone())); + } + } + Ok(all_events) => { + let events_count = all_events.len(); + let mut all_events = all_events.into_iter(); + + self.log_relay_target( + Relay::Logger::LEVEL_TRACE, + "inner sender returned result events, sending events back to caller", + |log| { + log.display("events_count", &events_count); + }, + ); + + for (message_count, sender) in senders.into_iter() { + let events = take(&mut all_events, message_count); + let _ = Runtime::send_once(sender, Ok(events)); + } + } + } + } +} + +trait CanEstimateBatchSize: HasMessageType { + fn estimate_batch_size(messages: &[Self::Message]) -> usize; +} + +impl CanEstimateBatchSize for Chain +where + Chain: CanEstimateMessageSize, +{ + fn estimate_batch_size(messages: &[Self::Message]) -> usize { + messages + .iter() + .map(|message| { + // return 0 on encoding error, as we don't want + // the batching operation to error out. + Chain::estimate_message_size(message).unwrap_or(0) + }) + .sum() + } +} + +fn take>(it: &mut I, count: usize) -> Vec { + let mut res = Vec::new(); + for _ in 0..count { + match it.next() { + Some(x) => { + res.push(x); + } + None => { + return res; + } + } + } + res +} diff --git a/crates/relayer-components-extra/src/build/components/mod.rs b/crates/relayer-components-extra/src/build/components/mod.rs new file mode 100644 index 0000000000..6193dd91b7 --- /dev/null +++ b/crates/relayer-components-extra/src/build/components/mod.rs @@ -0,0 +1 @@ +pub mod relay; diff --git a/crates/relayer-components-extra/src/build/components/relay/batch.rs b/crates/relayer-components-extra/src/build/components/relay/batch.rs new file mode 100644 index 0000000000..32a1b14d44 --- /dev/null +++ b/crates/relayer-components-extra/src/build/components/relay/batch.rs @@ -0,0 +1,185 @@ +use async_trait::async_trait; +use ibc_relayer_components::build::traits::birelay::HasBiRelayType; +use ibc_relayer_components::build::traits::components::relay_from_chains_builder::RelayFromChainsBuilder; +use ibc_relayer_components::build::traits::target::chain::ChainBuildTarget; +use ibc_relayer_components::build::traits::target::relay::RelayBuildTarget; +use ibc_relayer_components::build::types::aliases::{ + CounterpartyChainId, CounterpartyClientId, RelayError, TargetChain, TargetChainId, + TargetChainRuntime, TargetClientId, +}; +use ibc_relayer_components::chain::traits::types::chain_id::HasChainId; +use ibc_relayer_components::chain::traits::types::ibc::HasIbcChainTypes; +use ibc_relayer_components::core::traits::error::HasErrorType; +use ibc_relayer_components::relay::traits::chains::HasRelayChains; +use ibc_relayer_components::relay::traits::target::{DestinationTarget, SourceTarget}; +use ibc_relayer_components::runtime::traits::mutex::{HasMutex, HasRuntimeWithMutex}; +use ibc_relayer_components::runtime::traits::runtime::HasRuntime; + +use crate::batch::traits::config::HasBatchConfig; +use crate::batch::types::aliases::{MessageBatchReceiver, MessageBatchSender}; +use crate::batch::worker::CanSpawnBatchMessageWorker; +use crate::build::traits::cache::HasBatchSenderCache; +use crate::build::traits::components::relay_with_batch_builder::CanBuildRelayWithBatch; +use crate::runtime::traits::channel::{CanCloneSender, CanCreateChannels, HasChannelTypes}; +use crate::runtime::traits::channel_once::HasChannelOnceTypes; +use crate::std_prelude::*; + +pub struct BuildRelayWithBatchWorker; + +#[async_trait] +impl + RelayFromChainsBuilder for BuildRelayWithBatchWorker +where + Build: HasBiRelayType + + HasRuntime + + HasBatchConfig + + HasErrorType + + CanBuildRelayWithBatch, + Build: + CanBuildBatchChannel + CanBuildBatchChannel, + Target: RelayBuildTarget, + Relay: HasRelayChains>, + Relay: Clone + + CanSpawnBatchMessageWorker + + CanSpawnBatchMessageWorker, + SrcChain: HasIbcChainTypes, + DstChain: HasIbcChainTypes, + SrcChain: HasRuntime + HasChainId, + DstChain: HasRuntime + HasChainId, + SrcRuntime: HasChannelTypes + HasChannelOnceTypes, + DstRuntime: HasChannelTypes + HasChannelOnceTypes, + Build::Runtime: HasMutex, +{ + async fn build_relay_from_chains( + build: &Build, + src_client_id: &SrcChain::ClientId, + dst_client_id: &DstChain::ClientId, + src_chain: SrcChain, + dst_chain: DstChain, + ) -> Result { + let src_chain_id = src_chain.chain_id(); + let dst_chain_id = dst_chain.chain_id(); + + let (src_sender, m_src_receiver) = build + .build_batch_channel( + Target::SrcChainTarget::default(), + src_chain_id, + dst_chain_id, + src_client_id, + dst_client_id, + ) + .await?; + + let (dst_sender, m_dst_receiver) = build + .build_batch_channel( + Target::DstChainTarget::default(), + dst_chain_id, + src_chain_id, + dst_client_id, + src_client_id, + ) + .await?; + + let relay = build + .build_relay_with_batch( + Target::default(), + src_client_id, + dst_client_id, + src_chain, + dst_chain, + src_sender, + dst_sender, + ) + .await?; + + if let Some(src_receiver) = m_src_receiver { + relay.clone().spawn_batch_message_worker( + SourceTarget, + build.batch_config().clone(), + src_receiver, + ); + } + + if let Some(dst_receiver) = m_dst_receiver { + relay.clone().spawn_batch_message_worker( + DestinationTarget, + build.batch_config().clone(), + dst_receiver, + ); + } + + Ok(relay) + } +} + +#[async_trait] +pub trait CanBuildBatchChannel: HasBiRelayType + HasErrorType +where + Target: ChainBuildTarget, + TargetChain: HasRuntime, + TargetChainRuntime: HasChannelTypes + HasChannelOnceTypes, +{ + async fn build_batch_channel( + &self, + target: Target, + chain_id: &TargetChainId, + counterparty_chain_id: &CounterpartyChainId, + client_id: &TargetClientId, + counterparty_client_id: &CounterpartyClientId, + ) -> Result< + ( + MessageBatchSender, RelayError>, + Option, RelayError>>, + ), + Self::Error, + >; +} + +#[async_trait] +impl CanBuildBatchChannel for Build +where + Build: HasBiRelayType + HasRuntimeWithMutex + HasErrorType, + Target: ChainBuildTarget, + Chain: HasIbcChainTypes + HasRuntime, + Counterparty: HasIbcChainTypes, + Runtime: CanCreateChannels + HasChannelOnceTypes + CanCloneSender, + Build: HasBatchSenderCache>, + Chain::ChainId: Ord + Clone, + Counterparty::ChainId: Ord + Clone, + Chain::ClientId: Ord + Clone, + Counterparty::ClientId: Ord + Clone, +{ + async fn build_batch_channel( + &self, + target: Target, + chain_id: &Chain::ChainId, + counterparty_chain_id: &Counterparty::ChainId, + client_id: &Chain::ClientId, + counterparty_client_id: &Counterparty::ClientId, + ) -> Result< + ( + MessageBatchSender>, + Option>>, + ), + Self::Error, + > { + let mutex = self.batch_sender_cache(target); + + let mut sender_cache = Build::Runtime::acquire_mutex(mutex).await; + + let cache_key = ( + chain_id.clone(), + counterparty_chain_id.clone(), + client_id.clone(), + counterparty_client_id.clone(), + ); + + if let Some(sender) = sender_cache.get(&cache_key) { + Ok((Runtime::clone_sender(sender), None)) + } else { + let (sender, receiver) = Runtime::new_channel(); + sender_cache.insert(cache_key, Runtime::clone_sender(&sender)); + Ok((sender, Some(receiver))) + } + } +} diff --git a/crates/relayer-components-extra/src/build/components/relay/mod.rs b/crates/relayer-components-extra/src/build/components/relay/mod.rs new file mode 100644 index 0000000000..f02defef46 --- /dev/null +++ b/crates/relayer-components-extra/src/build/components/relay/mod.rs @@ -0,0 +1 @@ +pub mod batch; diff --git a/crates/relayer-components-extra/src/build/mod.rs b/crates/relayer-components-extra/src/build/mod.rs new file mode 100644 index 0000000000..1f44d235ab --- /dev/null +++ b/crates/relayer-components-extra/src/build/mod.rs @@ -0,0 +1,2 @@ +pub mod components; +pub mod traits; diff --git a/crates/relayer-components-extra/src/build/traits/cache.rs b/crates/relayer-components-extra/src/build/traits/cache.rs new file mode 100644 index 0000000000..59546a1c74 --- /dev/null +++ b/crates/relayer-components-extra/src/build/traits/cache.rs @@ -0,0 +1,44 @@ +use alloc::collections::BTreeMap; + +use ibc_relayer_components::build::traits::birelay::HasBiRelayType; +use ibc_relayer_components::build::traits::target::chain::ChainBuildTarget; +use ibc_relayer_components::build::types::aliases::{ + CounterpartyChainId, CounterpartyClientId, TargetChain, TargetChainId, TargetClientId, +}; +use ibc_relayer_components::core::traits::sync::Async; +use ibc_relayer_components::runtime::traits::mutex::HasRuntimeWithMutex; +use ibc_relayer_components::runtime::types::aliases::Mutex; + +use crate::batch::traits::channel::HasMessageBatchSenderType; + +pub trait HasBatchSenderCache: Async +where + Target: HasBatchSenderCacheType, +{ + fn batch_sender_cache(&self, target: Target) -> &Target::BatchSenderCache; +} + +pub trait HasBatchSenderCacheType: Async { + type BatchSenderCache: Async; +} + +impl HasBatchSenderCacheType for Target +where + Error: Async, + Build: HasBiRelayType + HasRuntimeWithMutex, + Target: ChainBuildTarget, + Target::TargetChain: HasMessageBatchSenderType, +{ + type BatchSenderCache = Mutex< + Build, + BTreeMap< + ( + TargetChainId, + CounterpartyChainId, + TargetClientId, + CounterpartyClientId, + ), + as HasMessageBatchSenderType>::MessageBatchSender, + >, + >; +} diff --git a/crates/relayer-components-extra/src/build/traits/components/mod.rs b/crates/relayer-components-extra/src/build/traits/components/mod.rs new file mode 100644 index 0000000000..f8cf1ea5bc --- /dev/null +++ b/crates/relayer-components-extra/src/build/traits/components/mod.rs @@ -0,0 +1 @@ +pub mod relay_with_batch_builder; diff --git a/crates/relayer-components-extra/src/build/traits/components/relay_with_batch_builder.rs b/crates/relayer-components-extra/src/build/traits/components/relay_with_batch_builder.rs new file mode 100644 index 0000000000..13aa123234 --- /dev/null +++ b/crates/relayer-components-extra/src/build/traits/components/relay_with_batch_builder.rs @@ -0,0 +1,113 @@ +use async_trait::async_trait; +use ibc_relayer_components::build::traits::birelay::HasBiRelayType; +use ibc_relayer_components::build::traits::target::relay::RelayBuildTarget; +use ibc_relayer_components::build::types::aliases::{ + TargetDstChain, TargetDstClientId, TargetRelay, TargetSrcChain, TargetSrcClientId, +}; +use ibc_relayer_components::core::traits::component::{DelegateComponent, HasComponents}; +use ibc_relayer_components::core::traits::error::HasErrorType; +use ibc_relayer_components::runtime::traits::mutex::HasRuntimeWithMutex; + +use crate::batch::traits::channel::HasMessageBatchSenderTypes; +use crate::std_prelude::*; + +pub struct RelayWithBatchBuilderComponent; + +#[async_trait] +pub trait RelayWithBatchBuilder +where + Build: HasBiRelayType + HasErrorType, + Target: RelayBuildTarget, + Target::TargetRelay: HasMessageBatchSenderTypes, +{ + async fn build_relay_with_batch( + build: &Build, + src_client_id: &TargetSrcClientId, + dst_client_id: &TargetDstClientId, + src_chain: TargetSrcChain, + dst_chain: TargetDstChain, + src_batch_sender: ::SrcMessageBatchSender, + dst_batch_sender: ::DstMessageBatchSender, + ) -> Result, Build::Error>; +} + +#[async_trait] +impl RelayWithBatchBuilder for Component +where + Build: HasBiRelayType + HasErrorType, + Target: RelayBuildTarget, + Target::TargetRelay: HasMessageBatchSenderTypes, + Component: DelegateComponent, + Component::Delegate: RelayWithBatchBuilder, +{ + async fn build_relay_with_batch( + build: &Build, + src_client_id: &TargetSrcClientId, + dst_client_id: &TargetDstClientId, + src_chain: TargetSrcChain, + dst_chain: TargetDstChain, + src_batch_sender: ::SrcMessageBatchSender, + dst_batch_sender: ::DstMessageBatchSender, + ) -> Result, Build::Error> { + Component::Delegate::build_relay_with_batch( + build, + src_client_id, + dst_client_id, + src_chain, + dst_chain, + src_batch_sender, + dst_batch_sender, + ) + .await + } +} + +#[async_trait] +pub trait CanBuildRelayWithBatch: + HasBiRelayType + HasRuntimeWithMutex + HasErrorType +where + Target: RelayBuildTarget, + Target::TargetRelay: HasMessageBatchSenderTypes, +{ + async fn build_relay_with_batch( + &self, + target: Target, + src_client_id: &TargetSrcClientId, + dst_client_id: &TargetDstClientId, + src_chain: TargetSrcChain, + dst_chain: TargetDstChain, + src_batch_sender: ::SrcMessageBatchSender, + dst_batch_sender: ::DstMessageBatchSender, + ) -> Result, Self::Error>; +} + +#[async_trait] +impl CanBuildRelayWithBatch for Build +where + Build: HasBiRelayType + HasRuntimeWithMutex + HasErrorType + HasComponents, + Target: RelayBuildTarget, + Target::TargetRelay: HasMessageBatchSenderTypes, + Build::Components: RelayWithBatchBuilder, +{ + async fn build_relay_with_batch( + &self, + _target: Target, + src_client_id: &TargetSrcClientId, + dst_client_id: &TargetDstClientId, + src_chain: TargetSrcChain, + dst_chain: TargetDstChain, + src_batch_sender: ::SrcMessageBatchSender, + dst_batch_sender: ::DstMessageBatchSender, + ) -> Result, Self::Error> { + Build::Components::build_relay_with_batch( + self, + src_client_id, + dst_client_id, + src_chain, + dst_chain, + src_batch_sender, + dst_batch_sender, + ) + .await + } +} diff --git a/crates/relayer-components-extra/src/build/traits/mod.rs b/crates/relayer-components-extra/src/build/traits/mod.rs new file mode 100644 index 0000000000..45d44f7e00 --- /dev/null +++ b/crates/relayer-components-extra/src/build/traits/mod.rs @@ -0,0 +1,2 @@ +pub mod cache; +pub mod components; diff --git a/crates/relayer-components-extra/src/clear_packet/mod.rs b/crates/relayer-components-extra/src/clear_packet/mod.rs new file mode 100644 index 0000000000..2c8b83993a --- /dev/null +++ b/crates/relayer-components-extra/src/clear_packet/mod.rs @@ -0,0 +1 @@ +pub mod worker; diff --git a/crates/relayer-components-extra/src/clear_packet/worker.rs b/crates/relayer-components-extra/src/clear_packet/worker.rs new file mode 100644 index 0000000000..1dcd325335 --- /dev/null +++ b/crates/relayer-components-extra/src/clear_packet/worker.rs @@ -0,0 +1,80 @@ +use async_trait::async_trait; +use core::time::Duration; + +use ibc_relayer_components::chain::types::aliases::{ChannelId, PortId}; +use ibc_relayer_components::relay::traits::chains::HasRelayChains; +use ibc_relayer_components::relay::traits::clear_interval::HasClearInterval; +use ibc_relayer_components::relay::traits::components::packet_clearer::CanClearPackets; +use ibc_relayer_components::runtime::traits::runtime::HasRuntime; +use ibc_relayer_components::runtime::traits::sleep::CanSleep; + +use crate::runtime::traits::spawn::{HasSpawner, Spawner, TaskHandle}; +use crate::std_prelude::*; + +#[async_trait] +pub trait CanSpawnPacketClearWorker: HasRelayChains { + fn spawn_packet_clear_worker( + self, + src_channel_id: ChannelId, + src_counterparty_port_id: PortId, + dst_channel_id: ChannelId, + dst_port_id: PortId, + ) -> Box; +} + +impl CanSpawnPacketClearWorker for Relay +where + Relay: CanRunLoop + HasRuntime, + Relay::Runtime: HasSpawner, +{ + fn spawn_packet_clear_worker( + self, + src_channel_id: ChannelId, + src_port_id: PortId, + dst_channel_id: ChannelId, + dst_port_id: PortId, + ) -> Box { + let spawner = self.runtime().spawner(); + + spawner.spawn(async move { + self.run_loop(&src_channel_id, &src_port_id, &dst_channel_id, &dst_port_id) + .await; + }) + } +} + +#[async_trait] +trait CanRunLoop: HasRelayChains { + async fn run_loop( + &self, + src_channel_id: &ChannelId, + src_port_id: &PortId, + dst_channel_id: &ChannelId, + dst_port_id: &PortId, + ); +} + +#[async_trait] +impl CanRunLoop for Relay +where + Relay: HasRuntime + CanClearPackets + HasClearInterval, + Relay::Runtime: CanSleep, +{ + async fn run_loop( + &self, + src_channel_id: &ChannelId, + src_port_id: &PortId, + dst_channel_id: &ChannelId, + dst_port_id: &PortId, + ) { + let runtime = self.runtime(); + let clear_interval = self.clear_interval().into(); + + loop { + let _ = self + .clear_packets(src_channel_id, src_port_id, dst_channel_id, dst_port_id) + .await; + runtime.sleep(Duration::from_secs(clear_interval)).await; + } + } +} diff --git a/crates/relayer-components-extra/src/components/extra/birelay.rs b/crates/relayer-components-extra/src/components/extra/birelay.rs new file mode 100644 index 0000000000..8ecbbe211d --- /dev/null +++ b/crates/relayer-components-extra/src/components/extra/birelay.rs @@ -0,0 +1,11 @@ +use core::marker::PhantomData; + +use crate::relay::components::auto_relayers::parallel_two_way::ParallelTwoWayAutoRelay; +use ibc_relayer_components::relay::traits::components::auto_relayer::AutoRelayerComponent; +pub struct ExtraBiRelayComponents(pub PhantomData); + +ibc_relayer_components::delegate_component!( + AutoRelayerComponent, + ExtraBiRelayComponents, + ParallelTwoWayAutoRelay, +); diff --git a/crates/relayer-components-extra/src/components/extra/build.rs b/crates/relayer-components-extra/src/components/extra/build.rs new file mode 100644 index 0000000000..31c4fffac5 --- /dev/null +++ b/crates/relayer-components-extra/src/components/extra/build.rs @@ -0,0 +1,36 @@ +use core::marker::PhantomData; + +use ibc_relayer_components::build::traits::components::birelay_builder::BiRelayBuilderComponent; +use ibc_relayer_components::build::traits::components::birelay_from_relay_builder::BiRelayFromRelayBuilderComponent; +use ibc_relayer_components::build::traits::components::chain_builder::ChainBuilderComponent; +use ibc_relayer_components::build::traits::components::relay_builder::RelayBuilderComponent; +use ibc_relayer_components::build::traits::components::relay_from_chains_builder::RelayFromChainsBuilderComponent; +use ibc_relayer_components::components::default::build::DefaultBuildComponents; + +use crate::build::components::relay::batch::BuildRelayWithBatchWorker; +use crate::build::traits::components::relay_with_batch_builder::RelayWithBatchBuilderComponent; + +pub struct ExtraBuildComponents(pub PhantomData); + +ibc_relayer_components::delegate_component!( + RelayFromChainsBuilderComponent, + ExtraBuildComponents, + BuildRelayWithBatchWorker, +); + +ibc_relayer_components::delegate_component!( + RelayWithBatchBuilderComponent, + ExtraBuildComponents, + BaseComponents, +); + +ibc_relayer_components::delegate_components!( + [ + ChainBuilderComponent, + RelayBuilderComponent, + BiRelayBuilderComponent, + BiRelayFromRelayBuilderComponent, + ], + ExtraBuildComponents, + DefaultBuildComponents, +); diff --git a/crates/relayer-components-extra/src/components/extra/chain.rs b/crates/relayer-components-extra/src/components/extra/chain.rs new file mode 100644 index 0000000000..581c4bd466 --- /dev/null +++ b/crates/relayer-components-extra/src/components/extra/chain.rs @@ -0,0 +1,29 @@ +use core::marker::PhantomData; +use ibc_relayer_components::chain::traits::components::chain_status_querier::ChainStatusQuerierComponent; +use ibc_relayer_components::chain::traits::components::consensus_state_querier::ConsensusStateQuerierComponent; +use ibc_relayer_components::chain::traits::components::message_sender::MessageSenderComponent; +use ibc_relayer_components::chain::traits::components::packet_fields_reader::PacketFieldsReaderComponent; +use ibc_relayer_components::components::default::chain::DefaultChainComponents; + +use crate::telemetry::components::consensus_state::ConsensusStateTelemetryQuerier; +use crate::telemetry::components::status::ChainStatusTelemetryQuerier; + +pub struct ExtraChainComponents(pub PhantomData); + +ibc_relayer_components::delegate_component!( + ChainStatusQuerierComponent, + ExtraChainComponents, + ChainStatusTelemetryQuerier, +); + +ibc_relayer_components::delegate_component!( + ConsensusStateQuerierComponent, + ExtraChainComponents, + ConsensusStateTelemetryQuerier, +); + +ibc_relayer_components::delegate_components!( + [MessageSenderComponent, PacketFieldsReaderComponent,], + ExtraChainComponents, + DefaultChainComponents, +); diff --git a/crates/relayer-components-extra/src/components/extra/closures/batch.rs b/crates/relayer-components-extra/src/components/extra/closures/batch.rs new file mode 100644 index 0000000000..1733e45a82 --- /dev/null +++ b/crates/relayer-components-extra/src/components/extra/closures/batch.rs @@ -0,0 +1,65 @@ +use ibc_relayer_components::chain::traits::types::chain_id::HasChainId; +use ibc_relayer_components::chain::traits::types::ibc::HasIbcChainTypes; +use ibc_relayer_components::chain::traits::types::message::CanEstimateMessageSize; +use ibc_relayer_components::logger::traits::has_logger::HasLogger; +use ibc_relayer_components::logger::traits::level::HasBaseLogLevels; +use ibc_relayer_components::relay::traits::chains::HasRelayChains; +use ibc_relayer_components::relay::traits::components::ibc_message_sender::CanSendIbcMessages; +use ibc_relayer_components::relay::traits::target::{DestinationTarget, SourceTarget}; +use ibc_relayer_components::runtime::traits::mutex::HasMutex; +use ibc_relayer_components::runtime::traits::runtime::HasRuntime; +use ibc_relayer_components::runtime::traits::sleep::CanSleep; +use ibc_relayer_components::runtime::traits::time::HasTime; + +use crate::batch::types::sink::BatchWorkerSink; +use crate::batch::worker::CanSpawnBatchMessageWorker; +use crate::runtime::traits::channel::{CanCloneSender, CanUseChannels, HasChannelTypes}; +use crate::runtime::traits::channel_once::{CanUseChannelsOnce, HasChannelOnceTypes}; +use crate::runtime::traits::spawn::HasSpawner; + +pub trait CanUseBatchMessageWorkerSpawner: UseBatchMessageWorkerSpawner +where + Self::SrcChain: HasRuntime, + Self::DstChain: HasRuntime, + ::Runtime: HasChannelTypes + HasChannelOnceTypes, + ::Runtime: HasChannelTypes + HasChannelOnceTypes, +{ +} + +pub trait UseBatchMessageWorkerSpawner: + CanSpawnBatchMessageWorker + CanSpawnBatchMessageWorker +where + Self::SrcChain: HasRuntime, + Self::DstChain: HasRuntime, + ::Runtime: HasChannelTypes + HasChannelOnceTypes, + ::Runtime: HasChannelTypes + HasChannelOnceTypes, +{ +} + +impl UseBatchMessageWorkerSpawner for Relay +where + Relay: Clone + + HasLogger + + HasRelayChains + + CanSendIbcMessages + + CanSendIbcMessages, + SrcChain: HasRuntime + HasChainId + CanEstimateMessageSize + HasIbcChainTypes, + DstChain: HasRuntime + HasChainId + CanEstimateMessageSize + HasIbcChainTypes, + SrcChain::Runtime: HasTime + + HasMutex + + CanSleep + + HasSpawner + + CanUseChannels + + CanUseChannelsOnce + + CanCloneSender, + DstChain::Runtime: HasTime + + HasMutex + + CanSleep + + HasSpawner + + CanUseChannels + + CanUseChannelsOnce + + CanCloneSender, + Relay::Error: Clone, + Relay::Logger: HasBaseLogLevels, +{ +} diff --git a/crates/relayer-components-extra/src/components/extra/closures/build.rs b/crates/relayer-components-extra/src/components/extra/closures/build.rs new file mode 100644 index 0000000000..f3998e0d2b --- /dev/null +++ b/crates/relayer-components-extra/src/components/extra/closures/build.rs @@ -0,0 +1,69 @@ +use ibc_relayer_components::build::traits::birelay::HasBiRelayType; +use ibc_relayer_components::build::traits::cache::{HasChainCache, HasRelayCache}; +use ibc_relayer_components::build::traits::components::birelay_builder::CanBuildBiRelay; +use ibc_relayer_components::build::traits::components::birelay_from_relay_builder::BiRelayFromRelayBuilder; +use ibc_relayer_components::build::traits::components::chain_builder::ChainBuilder; +use ibc_relayer_components::build::traits::target::chain::{ChainATarget, ChainBTarget}; +use ibc_relayer_components::build::traits::target::relay::{RelayAToBTarget, RelayBToATarget}; +use ibc_relayer_components::chain::traits::types::chain_id::HasChainId; +use ibc_relayer_components::chain::traits::types::ibc::HasIbcChainTypes; +use ibc_relayer_components::core::traits::component::HasComponents; +use ibc_relayer_components::core::traits::error::HasErrorType; +use ibc_relayer_components::core::traits::sync::Async; +use ibc_relayer_components::relay::traits::chains::HasRelayChains; +use ibc_relayer_components::relay::traits::two_way::HasTwoWayRelay; +use ibc_relayer_components::runtime::traits::mutex::HasMutex; +use ibc_relayer_components::runtime::traits::runtime::HasRuntime; + +use crate::batch::traits::config::HasBatchConfig; +use crate::build::traits::cache::HasBatchSenderCache; +use crate::build::traits::components::relay_with_batch_builder::RelayWithBatchBuilder; +use crate::components::extra::build::ExtraBuildComponents; +use crate::components::extra::closures::batch::UseBatchMessageWorkerSpawner; +use crate::runtime::traits::channel::{CanCloneSender, CanCreateChannels}; +use crate::runtime::traits::channel_once::CanUseChannelsOnce; + +pub trait CanUseExtraBuilderComponents: UseExtraBuilderComponents {} + +pub trait UseExtraBuilderComponents: CanBuildBiRelay {} + +impl + UseExtraBuilderComponents for Build +where + Build: HasErrorType + + HasRuntime + + HasBatchConfig + + HasBiRelayType + + HasRelayCache + + HasRelayCache + + HasChainCache + + HasChainCache + + HasBatchSenderCache + + HasBatchSenderCache + + HasComponents>, + BiRelay: HasTwoWayRelay, + RelayAToB: Clone + + HasErrorType + + HasRelayChains + + UseBatchMessageWorkerSpawner, + RelayBToA: Clone + + HasErrorType + + HasRelayChains + + UseBatchMessageWorkerSpawner, + ChainA: Clone + HasRuntime + HasChainId + HasIbcChainTypes, + ChainB: Clone + HasRuntime + HasChainId + HasIbcChainTypes, + Error: Async, + ChainA::ChainId: Ord + Clone, + ChainB::ChainId: Ord + Clone, + ChainA::ClientId: Ord + Clone, + ChainB::ClientId: Ord + Clone, + ChainA::Runtime: CanCreateChannels + CanUseChannelsOnce + CanCloneSender, + ChainB::Runtime: CanCreateChannels + CanUseChannelsOnce + CanCloneSender, + Build::Runtime: HasMutex, + BaseComponents: BiRelayFromRelayBuilder + + RelayWithBatchBuilder + + RelayWithBatchBuilder + + ChainBuilder + + ChainBuilder, +{ +} diff --git a/crates/relayer-components-extra/src/components/extra/closures/chain.rs b/crates/relayer-components-extra/src/components/extra/closures/chain.rs new file mode 100644 index 0000000000..9f7f8f2c88 --- /dev/null +++ b/crates/relayer-components-extra/src/components/extra/closures/chain.rs @@ -0,0 +1,41 @@ +use ibc_relayer_components::chain::traits::components::chain_status_querier::{ + CanQueryChainStatus, ChainStatusQuerier, +}; +use ibc_relayer_components::chain::traits::components::consensus_state_querier::{ + CanQueryConsensusState, ConsensusStateQuerier, +}; +use ibc_relayer_components::chain::traits::components::message_sender::{ + CanSendMessages, MessageSender, +}; +use ibc_relayer_components::chain::traits::types::consensus_state::HasConsensusStateType; +use ibc_relayer_components::chain::traits::types::height::HasHeightType; +use ibc_relayer_components::chain::traits::types::ibc::HasIbcChainTypes; +use ibc_relayer_components::chain::traits::types::status::HasChainStatusType; +use ibc_relayer_components::core::traits::component::HasComponents; +use ibc_relayer_components::core::traits::error::HasErrorType; + +use crate::components::extra::chain::ExtraChainComponents; +use crate::telemetry::traits::metrics::HasBasicMetrics; +use crate::telemetry::traits::telemetry::HasTelemetry; + +pub trait UseExtraChainComponents: + CanQueryChainStatus + CanQueryConsensusState + CanSendMessages +where + Counterparty: HasHeightType + HasConsensusStateType, +{ +} + +impl UseExtraChainComponents for Chain +where + Counterparty: HasHeightType + HasConsensusStateType, + Chain: HasErrorType + + HasTelemetry + + HasChainStatusType + + HasIbcChainTypes + + HasComponents>, + Chain::Telemetry: HasBasicMetrics, + BaseComponents: MessageSender + + ChainStatusQuerier + + ConsensusStateQuerier, +{ +} diff --git a/crates/relayer-components-extra/src/components/extra/closures/mod.rs b/crates/relayer-components-extra/src/components/extra/closures/mod.rs new file mode 100644 index 0000000000..2155acfb44 --- /dev/null +++ b/crates/relayer-components-extra/src/components/extra/closures/mod.rs @@ -0,0 +1,4 @@ +pub mod batch; +pub mod build; +pub mod chain; +pub mod relay; diff --git a/crates/relayer-components-extra/src/components/extra/closures/relay/ack_packet_relayer.rs b/crates/relayer-components-extra/src/components/extra/closures/relay/ack_packet_relayer.rs new file mode 100644 index 0000000000..911e3061ee --- /dev/null +++ b/crates/relayer-components-extra/src/components/extra/closures/relay/ack_packet_relayer.rs @@ -0,0 +1,82 @@ +use ibc_relayer_components::chain::traits::client::client_state::CanQueryClientState; +use ibc_relayer_components::chain::traits::client::consensus_state::CanFindConsensusStateHeight; +use ibc_relayer_components::chain::traits::client::update::{ + CanBuildUpdateClientMessage, CanBuildUpdateClientPayload, +}; +use ibc_relayer_components::chain::traits::components::chain_status_querier::CanQueryChainStatus; +use ibc_relayer_components::chain::traits::components::consensus_state_querier::CanQueryConsensusState; +use ibc_relayer_components::chain::traits::components::message_sender::CanSendMessages; +use ibc_relayer_components::chain::traits::components::packet_fields_reader::CanReadPacketFields; +use ibc_relayer_components::chain::traits::message_builders::ack_packet::{ + CanBuildAckPacketMessage, CanBuildAckPacketPayload, +}; +use ibc_relayer_components::chain::traits::types::chain_id::HasChainId; +use ibc_relayer_components::chain::traits::types::client_state::HasClientStateFields; +use ibc_relayer_components::chain::traits::types::consensus_state::HasConsensusStateType; +use ibc_relayer_components::chain::traits::types::height::CanIncrementHeight; +use ibc_relayer_components::chain::traits::types::ibc::HasCounterpartyMessageHeight; +use ibc_relayer_components::chain::traits::types::ibc_events::write_ack::HasWriteAcknowledgementEvent; +use ibc_relayer_components::core::traits::component::HasComponents; +use ibc_relayer_components::core::traits::error::HasErrorType; +use ibc_relayer_components::core::traits::sync::Async; +use ibc_relayer_components::logger::traits::has_logger::HasLogger; +use ibc_relayer_components::logger::traits::level::HasBaseLogLevels; +use ibc_relayer_components::relay::traits::chains::HasRelayChains; +use ibc_relayer_components::relay::traits::components::packet_relayers::ack_packet::CanRelayAckPacket; +use ibc_relayer_components::relay::traits::target::SourceTarget; +use ibc_relayer_components::runtime::traits::runtime::HasRuntime; +use ibc_relayer_components::runtime::traits::sleep::CanSleep; + +use crate::batch::traits::channel::HasMessageBatchSender; +use crate::components::extra::relay::ExtraRelayComponents; +use crate::runtime::traits::channel::CanUseChannels; +use crate::runtime::traits::channel_once::{CanCreateChannelsOnce, CanUseChannelsOnce}; + +pub trait CanUseExtraAckPacketRelayer: UseExtraAckPacketRelayer +where + Self::DstChain: HasWriteAcknowledgementEvent, +{ +} + +pub trait UseExtraAckPacketRelayer: CanRelayAckPacket +where + Self::DstChain: HasWriteAcknowledgementEvent, +{ +} + +impl UseExtraAckPacketRelayer for Relay +where + Relay: HasRelayChains + + HasLogger + + HasMessageBatchSender + + HasComponents>, + SrcChain: HasErrorType + + HasRuntime + + HasChainId + + CanSendMessages + + HasConsensusStateType + + HasCounterpartyMessageHeight + + CanReadPacketFields + + CanQueryClientState + + CanQueryConsensusState + + CanFindConsensusStateHeight + + CanBuildAckPacketMessage + + CanBuildUpdateClientMessage, + DstChain: HasErrorType + + HasRuntime + + HasChainId + + CanIncrementHeight + + CanQueryChainStatus + + HasClientStateFields + + HasConsensusStateType + + CanReadPacketFields + + CanBuildAckPacketPayload + + CanBuildUpdateClientPayload, + SrcChain::Height: Clone, + DstChain::Height: Clone, + SrcChain::Runtime: CanCreateChannelsOnce + CanUseChannels + CanUseChannelsOnce, + DstChain::Runtime: CanSleep, + Relay::Logger: HasBaseLogLevels, + BaseRelayComponents: Async, +{ +} diff --git a/crates/relayer-components-extra/src/components/extra/closures/relay/auto_relayer.rs b/crates/relayer-components-extra/src/components/extra/closures/relay/auto_relayer.rs new file mode 100644 index 0000000000..417593cc53 --- /dev/null +++ b/crates/relayer-components-extra/src/components/extra/closures/relay/auto_relayer.rs @@ -0,0 +1,44 @@ +use ibc_relayer_components::chain::traits::event_subscription::HasEventSubscription; +use ibc_relayer_components::chain::traits::logs::event::CanLogChainEvent; +use ibc_relayer_components::chain::traits::types::chain_id::HasChainId; +use ibc_relayer_components::core::traits::component::HasComponents; +use ibc_relayer_components::core::traits::sync::Async; +use ibc_relayer_components::logger::traits::has_logger::{HasLogger, HasLoggerType}; +use ibc_relayer_components::logger::traits::level::HasBaseLogLevels; +use ibc_relayer_components::relay::traits::chains::HasRelayChains; +use ibc_relayer_components::relay::traits::components::auto_relayer::CanAutoRelay; +use ibc_relayer_components::runtime::traits::runtime::HasRuntime; + +use crate::components::extra::closures::relay::event_relayer::UseExtraEventRelayer; +use crate::components::extra::relay::ExtraRelayComponents; +use crate::runtime::traits::spawn::HasSpawner; + +pub trait CanUseExtraAutoRelayer: UseExtraAutoRelayer {} + +pub trait UseExtraAutoRelayer: CanAutoRelay {} + +impl UseExtraAutoRelayer for Relay +where + Relay: Clone + + HasRuntime + + HasLogger + + HasRelayChains + + UseExtraEventRelayer + + HasComponents>, + Relay::SrcChain: HasRuntime + + HasChainId + + HasEventSubscription + + HasLoggerType + + CanLogChainEvent, + Relay::DstChain: HasRuntime + + HasChainId + + HasEventSubscription + + HasLoggerType + + CanLogChainEvent, + Relay::Runtime: HasSpawner, + Relay::Logger: HasBaseLogLevels, + ::Runtime: HasSpawner, + ::Runtime: HasSpawner, + BaseRelayComponents: Async, +{ +} diff --git a/crates/relayer-components-extra/src/components/extra/closures/relay/components.rs b/crates/relayer-components-extra/src/components/extra/closures/relay/components.rs new file mode 100644 index 0000000000..df499bf29c --- /dev/null +++ b/crates/relayer-components-extra/src/components/extra/closures/relay/components.rs @@ -0,0 +1,8 @@ +use crate::components::extra::closures::relay::auto_relayer::UseExtraAutoRelayer; +use crate::components::extra::closures::relay::event_relayer::UseExtraEventRelayer; +use crate::components::extra::closures::relay::packet_relayer::UseExtraPacketRelayer; + +pub trait CanUseExtraRelayComponents: + UseExtraPacketRelayer + UseExtraEventRelayer + UseExtraAutoRelayer +{ +} diff --git a/crates/relayer-components-extra/src/components/extra/closures/relay/event_relayer.rs b/crates/relayer-components-extra/src/components/extra/closures/relay/event_relayer.rs new file mode 100644 index 0000000000..7f038bdafe --- /dev/null +++ b/crates/relayer-components-extra/src/components/extra/closures/relay/event_relayer.rs @@ -0,0 +1,47 @@ +use ibc_relayer_components::chain::traits::logs::packet::CanLogChainPacket; +use ibc_relayer_components::chain::traits::queries::channel::CanQueryCounterpartyChainIdFromChannel; +use ibc_relayer_components::chain::traits::types::chain_id::HasChainId; +use ibc_relayer_components::chain::traits::types::ibc_events::send_packet::HasSendPacketEvent; +use ibc_relayer_components::chain::traits::types::ibc_events::write_ack::CanBuildPacketFromWriteAckEvent; +use ibc_relayer_components::core::traits::component::HasComponents; +use ibc_relayer_components::logger::traits::has_logger::{HasLogger, HasLoggerType}; +use ibc_relayer_components::logger::traits::level::HasBaseLogLevels; +use ibc_relayer_components::relay::traits::chains::HasRelayChains; +use ibc_relayer_components::relay::traits::components::event_relayer::CanRelayEvent; +use ibc_relayer_components::relay::traits::components::packet_filter::PacketFilter; +use ibc_relayer_components::relay::traits::packet::HasRelayPacketFields; +use ibc_relayer_components::relay::traits::packet_lock::HasPacketLock; +use ibc_relayer_components::relay::traits::target::{DestinationTarget, SourceTarget}; + +use crate::components::extra::closures::relay::ack_packet_relayer::UseExtraAckPacketRelayer; +use crate::components::extra::closures::relay::packet_relayer::UseExtraPacketRelayer; +use crate::components::extra::relay::ExtraRelayComponents; + +pub trait CanUseExtraEventRelayer: UseExtraEventRelayer {} + +pub trait UseExtraEventRelayer: + CanRelayEvent + CanRelayEvent +{ +} + +impl UseExtraEventRelayer for Relay +where + Relay: HasRelayChains + + HasPacketLock + + HasLogger + + HasRelayPacketFields + + UseExtraAckPacketRelayer + + UseExtraPacketRelayer + + HasComponents>, + Relay::SrcChain: HasChainId + + HasLoggerType + + CanLogChainPacket + + HasSendPacketEvent + + CanQueryCounterpartyChainIdFromChannel, + Relay::DstChain: HasChainId + + CanQueryCounterpartyChainIdFromChannel + + CanBuildPacketFromWriteAckEvent, + Relay::Logger: HasBaseLogLevels, + BaseRelayComponents: PacketFilter, +{ +} diff --git a/crates/relayer-components-extra/src/components/extra/closures/relay/mod.rs b/crates/relayer-components-extra/src/components/extra/closures/relay/mod.rs new file mode 100644 index 0000000000..173419b254 --- /dev/null +++ b/crates/relayer-components-extra/src/components/extra/closures/relay/mod.rs @@ -0,0 +1,5 @@ +pub mod ack_packet_relayer; +pub mod auto_relayer; +pub mod components; +pub mod event_relayer; +pub mod packet_relayer; diff --git a/crates/relayer-components-extra/src/components/extra/closures/relay/packet_relayer.rs b/crates/relayer-components-extra/src/components/extra/closures/relay/packet_relayer.rs new file mode 100644 index 0000000000..8e835f74f0 --- /dev/null +++ b/crates/relayer-components-extra/src/components/extra/closures/relay/packet_relayer.rs @@ -0,0 +1,99 @@ +use ibc_relayer_components::chain::traits::client::client_state::CanQueryClientState; +use ibc_relayer_components::chain::traits::client::consensus_state::CanFindConsensusStateHeight; +use ibc_relayer_components::chain::traits::client::update::{ + CanBuildUpdateClientMessage, CanBuildUpdateClientPayload, +}; +use ibc_relayer_components::chain::traits::components::packet_fields_reader::CanReadPacketFields; +use ibc_relayer_components::chain::traits::logs::packet::CanLogChainPacket; +use ibc_relayer_components::chain::traits::message_builders::ack_packet::{ + CanBuildAckPacketMessage, CanBuildAckPacketPayload, +}; +use ibc_relayer_components::chain::traits::message_builders::receive_packet::{ + CanBuildReceivePacketMessage, CanBuildReceivePacketPayload, +}; +use ibc_relayer_components::chain::traits::message_builders::timeout_unordered_packet::{ + CanBuildTimeoutUnorderedPacketMessage, CanBuildTimeoutUnorderedPacketPayload, +}; +use ibc_relayer_components::chain::traits::queries::received_packet::CanQueryReceivedPacket; +use ibc_relayer_components::chain::traits::types::chain_id::HasChainId; +use ibc_relayer_components::chain::traits::types::client_state::HasClientStateFields; +use ibc_relayer_components::chain::traits::types::consensus_state::HasConsensusStateType; +use ibc_relayer_components::chain::traits::types::height::CanIncrementHeight; +use ibc_relayer_components::chain::traits::types::ibc::HasCounterpartyMessageHeight; +use ibc_relayer_components::chain::traits::types::ibc_events::write_ack::HasWriteAcknowledgementEvent; +use ibc_relayer_components::core::traits::component::HasComponents; +use ibc_relayer_components::core::traits::error::HasErrorType; +use ibc_relayer_components::logger::traits::has_logger::{HasLogger, HasLoggerType}; +use ibc_relayer_components::logger::traits::level::HasBaseLogLevels; +use ibc_relayer_components::relay::traits::chains::HasRelayChains; +use ibc_relayer_components::relay::traits::components::packet_filter::PacketFilter; +use ibc_relayer_components::relay::traits::components::packet_relayer::CanRelayPacket; +use ibc_relayer_components::relay::traits::packet_lock::HasPacketLock; +use ibc_relayer_components::relay::traits::target::{DestinationTarget, SourceTarget}; +use ibc_relayer_components::runtime::traits::runtime::HasRuntime; +use ibc_relayer_components::runtime::traits::sleep::CanSleep; + +use crate::batch::traits::channel::HasMessageBatchSender; +use crate::components::extra::closures::chain::UseExtraChainComponents; +use crate::components::extra::relay::ExtraRelayComponents; +use crate::relay::components::packet_relayers::retry::SupportsPacketRetry; +use crate::runtime::traits::channel::CanUseChannels; +use crate::runtime::traits::channel_once::{CanCreateChannelsOnce, CanUseChannelsOnce}; + +pub trait CanUseExtraPacketRelayer: UseExtraPacketRelayer {} + +pub trait UseExtraPacketRelayer: CanRelayPacket {} + +impl UseExtraPacketRelayer for Relay +where + Relay: HasRelayChains + + HasLogger + + HasPacketLock + + SupportsPacketRetry + + HasMessageBatchSender + + HasMessageBatchSender + + HasComponents>, + SrcChain: HasErrorType + + HasRuntime + + HasChainId + + CanIncrementHeight + + HasLoggerType + + HasClientStateFields + + HasConsensusStateType + + HasCounterpartyMessageHeight + + CanReadPacketFields + + CanLogChainPacket + + CanQueryClientState + + CanFindConsensusStateHeight + + CanBuildReceivePacketPayload + + CanBuildUpdateClientPayload + + CanBuildAckPacketMessage + + CanBuildUpdateClientMessage + + CanBuildTimeoutUnorderedPacketMessage + + UseExtraChainComponents, + DstChain: HasErrorType + + HasRuntime + + HasChainId + + CanIncrementHeight + + HasClientStateFields + + HasConsensusStateType + + HasCounterpartyMessageHeight + + HasWriteAcknowledgementEvent + + CanReadPacketFields + + CanQueryClientState + + CanQueryReceivedPacket + + CanFindConsensusStateHeight + + CanBuildAckPacketPayload + + CanBuildUpdateClientPayload + + CanBuildTimeoutUnorderedPacketPayload + + CanBuildUpdateClientMessage + + CanBuildReceivePacketMessage + + UseExtraChainComponents, + SrcChain::Height: Clone, + DstChain::Height: Clone, + SrcChain::Runtime: CanSleep + CanCreateChannelsOnce + CanUseChannels + CanUseChannelsOnce, + DstChain::Runtime: CanSleep + CanCreateChannelsOnce + CanUseChannels + CanUseChannelsOnce, + Relay::Logger: HasBaseLogLevels, + BaseRelayComponents: PacketFilter, +{ +} diff --git a/crates/relayer-components-extra/src/components/extra/mod.rs b/crates/relayer-components-extra/src/components/extra/mod.rs new file mode 100644 index 0000000000..b0a50173ea --- /dev/null +++ b/crates/relayer-components-extra/src/components/extra/mod.rs @@ -0,0 +1,6 @@ +pub mod birelay; +pub mod build; +pub mod chain; +pub mod closures; +pub mod relay; +pub mod transaction; diff --git a/crates/relayer-components-extra/src/components/extra/relay.rs b/crates/relayer-components-extra/src/components/extra/relay.rs new file mode 100644 index 0000000000..d18a251a02 --- /dev/null +++ b/crates/relayer-components-extra/src/components/extra/relay.rs @@ -0,0 +1,69 @@ +use core::marker::PhantomData; + +use ibc_relayer_components::components::default::relay::DefaultRelayComponents; +use ibc_relayer_components::relay::components::message_senders::chain_sender::SendIbcMessagesToChain; +use ibc_relayer_components::relay::components::message_senders::update_client::SendIbcMessagesWithUpdateClient; +use ibc_relayer_components::relay::components::packet_relayers::general::filter_relayer::FilterRelayer; +use ibc_relayer_components::relay::components::packet_relayers::general::full_relay::FullCycleRelayer; +use ibc_relayer_components::relay::components::packet_relayers::general::lock::LockPacketRelayer; +use ibc_relayer_components::relay::components::packet_relayers::general::log::LoggerRelayer; +use ibc_relayer_components::relay::traits::components::auto_relayer::AutoRelayerComponent; +use ibc_relayer_components::relay::traits::components::client_creator::ClientCreatorComponent; +use ibc_relayer_components::relay::traits::components::event_relayer::EventRelayerComponent; +use ibc_relayer_components::relay::traits::components::ibc_message_sender::{ + IbcMessageSenderComponent, MainSink, +}; +use ibc_relayer_components::relay::traits::components::packet_clearer::PacketClearerComponent; +use ibc_relayer_components::relay::traits::components::packet_filter::PacketFilterComponent; +use ibc_relayer_components::relay::traits::components::packet_relayer::PacketRelayerComponent; +use ibc_relayer_components::relay::traits::components::packet_relayers::ack_packet::AckPacketRelayerComponent; +use ibc_relayer_components::relay::traits::components::packet_relayers::receive_packet::ReceivePacketRelayerComponnent; +use ibc_relayer_components::relay::traits::components::packet_relayers::timeout_unordered_packet::TimeoutUnorderedPacketRelayerComponent; +use ibc_relayer_components::relay::traits::components::update_client_message_builder::UpdateClientMessageBuilderComponent; + +use crate::batch::components::message_sender::SendMessagesToBatchWorker; +use crate::batch::types::sink::BatchWorkerSink; +use crate::relay::components::auto_relayers::parallel_bidirectional::ParallelBidirectionalRelayer; +use crate::relay::components::auto_relayers::parallel_event::ParallelEventSubscriptionRelayer; +use crate::relay::components::packet_relayers::retry::RetryRelayer; + +pub struct ExtraRelayComponents(pub PhantomData); + +ibc_relayer_components::delegate_component!( + IbcMessageSenderComponent, + ExtraRelayComponents, + SendMessagesToBatchWorker, +); + +ibc_relayer_components::delegate_component!( + IbcMessageSenderComponent, + ExtraRelayComponents, + SendIbcMessagesWithUpdateClient, +); + +ibc_relayer_components::delegate_component!( + PacketRelayerComponent, + ExtraRelayComponents, + LockPacketRelayer>>>, +); + +ibc_relayer_components::delegate_component!( + AutoRelayerComponent, + ExtraRelayComponents, + ParallelBidirectionalRelayer, +); + +ibc_relayer_components::delegate_components!( + [ + UpdateClientMessageBuilderComponent, + PacketFilterComponent, + ReceivePacketRelayerComponnent, + AckPacketRelayerComponent, + TimeoutUnorderedPacketRelayerComponent, + EventRelayerComponent, + ClientCreatorComponent, + PacketClearerComponent, + ], + ExtraRelayComponents, + DefaultRelayComponents, +); diff --git a/crates/relayer-components-extra/src/components/extra/transaction.rs b/crates/relayer-components-extra/src/components/extra/transaction.rs new file mode 100644 index 0000000000..ae7f1ff98c --- /dev/null +++ b/crates/relayer-components-extra/src/components/extra/transaction.rs @@ -0,0 +1,30 @@ +use core::marker::PhantomData; + +use ibc_relayer_components::chain::traits::components::message_sender::MessageSenderComponent; +use ibc_relayer_components::components::default::transaction::DefaultTxComponents; +use ibc_relayer_components::transaction::traits::components::message_as_tx_sender::MessageAsTxSenderComponent; +use ibc_relayer_components::transaction::traits::components::nonce_allocater::NonceAllocatorComponent; +use ibc_relayer_components::transaction::traits::components::nonce_querier::NonceQuerierComponent; +use ibc_relayer_components::transaction::traits::components::tx_encoder::TxEncoderComponent; +use ibc_relayer_components::transaction::traits::components::tx_fee_estimater::TxFeeEstimatorComponent; +use ibc_relayer_components::transaction::traits::components::tx_response_poller::TxResponsePollerComponent; +use ibc_relayer_components::transaction::traits::components::tx_response_querier::TxResponseQuerierComponent; +use ibc_relayer_components::transaction::traits::components::tx_submitter::TxSubmitterComponent; + +pub struct ExtraTxComponents(pub PhantomData); + +ibc_relayer_components::delegate_components!( + [ + MessageSenderComponent, + MessageAsTxSenderComponent, + NonceQuerierComponent, + NonceAllocatorComponent, + TxEncoderComponent, + TxFeeEstimatorComponent, + TxSubmitterComponent, + TxResponsePollerComponent, + TxResponseQuerierComponent, + ], + ExtraTxComponents, + DefaultTxComponents, +); diff --git a/crates/relayer-components-extra/src/components/mod.rs b/crates/relayer-components-extra/src/components/mod.rs new file mode 100644 index 0000000000..d5d7148613 --- /dev/null +++ b/crates/relayer-components-extra/src/components/mod.rs @@ -0,0 +1 @@ +pub mod extra; diff --git a/crates/relayer-components-extra/src/lib.rs b/crates/relayer-components-extra/src/lib.rs new file mode 100644 index 0000000000..5925fd213b --- /dev/null +++ b/crates/relayer-components-extra/src/lib.rs @@ -0,0 +1,14 @@ +#![no_std] +#![allow(clippy::type_complexity)] +#![allow(clippy::too_many_arguments)] + +mod std_prelude; +extern crate alloc; + +pub mod batch; +pub mod build; +pub mod clear_packet; +pub mod components; +pub mod relay; +pub mod runtime; +pub mod telemetry; diff --git a/crates/relayer-components-extra/src/relay/components/auto_relayers/mod.rs b/crates/relayer-components-extra/src/relay/components/auto_relayers/mod.rs new file mode 100644 index 0000000000..ca13191b5d --- /dev/null +++ b/crates/relayer-components-extra/src/relay/components/auto_relayers/mod.rs @@ -0,0 +1,3 @@ +pub mod parallel_bidirectional; +pub mod parallel_event; +pub mod parallel_two_way; diff --git a/crates/relayer-components-extra/src/relay/components/auto_relayers/parallel_bidirectional.rs b/crates/relayer-components-extra/src/relay/components/auto_relayers/parallel_bidirectional.rs new file mode 100644 index 0000000000..8bf8a4146c --- /dev/null +++ b/crates/relayer-components-extra/src/relay/components/auto_relayers/parallel_bidirectional.rs @@ -0,0 +1,57 @@ +use core::marker::PhantomData; + +use async_trait::async_trait; +use ibc_relayer_components::relay::traits::chains::HasRelayChains; +use ibc_relayer_components::relay::traits::components::auto_relayer::{ + AutoRelayer, AutoRelayerWithTarget, +}; +use ibc_relayer_components::relay::traits::target::{DestinationTarget, SourceTarget}; +use ibc_relayer_components::runtime::traits::runtime::HasRuntime; + +use crate::runtime::traits::spawn::{HasSpawner, Spawner}; +use crate::std_prelude::*; + +/// A parallel variant of +/// [`ConcurrentBidirectionalRelayer`](ibc_relayer_components::relay::impls::auto_relayers::concurrent_bidirectional::ConcurrentBidirectionalRelayer) +/// that spawns two separate +/// tasks, each responsible for relaying for one of the targets. As such, it has an +/// additional `HasSpawner` constraint that the concurrent variant does not require. +pub struct ParallelBidirectionalRelayer(pub PhantomData); + +#[async_trait] +impl AutoRelayer for ParallelBidirectionalRelayer +where + Relay: HasRelayChains + HasRuntime + Clone, + InRelayer: AutoRelayerWithTarget, + InRelayer: AutoRelayerWithTarget, + Runtime: HasSpawner, +{ + async fn auto_relay(relay: &Relay) -> Result<(), Relay::Error> { + let src_relay = relay.clone(); + let dst_relay = relay.clone(); + let spawner = src_relay.runtime().spawner(); + + let handle1 = spawner.spawn(async move { + let _ = >::auto_relay_with_target( + &dst_relay, + ) + .await; + }); + + let handle2 = spawner.spawn(async move { + let _ = + >::auto_relay_with_target( + &src_relay, + ) + .await; + }); + + // Wait for handle1 and handle2 to finish. + // Equivalent to Join. + // TODO: Confirm with Soares + handle1.into_future().await; + handle2.into_future().await; + + Ok(()) + } +} diff --git a/crates/relayer-components-extra/src/relay/components/auto_relayers/parallel_event.rs b/crates/relayer-components-extra/src/relay/components/auto_relayers/parallel_event.rs new file mode 100644 index 0000000000..05b882ce1f --- /dev/null +++ b/crates/relayer-components-extra/src/relay/components/auto_relayers/parallel_event.rs @@ -0,0 +1,63 @@ +use async_trait::async_trait; +use futures_util::stream::StreamExt; +use ibc_relayer_components::chain::traits::event_subscription::HasEventSubscription; +use ibc_relayer_components::logger::traits::level::HasBaseLogLevels; +use ibc_relayer_components::relay::traits::components::auto_relayer::AutoRelayerWithTarget; +use ibc_relayer_components::relay::traits::components::event_relayer::CanRelayEvent; +use ibc_relayer_components::relay::traits::logs::event::CanLogTargetEvent; +use ibc_relayer_components::relay::traits::logs::logger::CanLogRelay; +use ibc_relayer_components::relay::traits::target::ChainTarget; +use ibc_relayer_components::runtime::traits::runtime::HasRuntime; + +use crate::runtime::traits::spawn::{HasSpawner, Spawner}; +use crate::std_prelude::*; + +pub struct ParallelEventSubscriptionRelayer; + +#[async_trait] +impl AutoRelayerWithTarget + for ParallelEventSubscriptionRelayer +where + Relay: Clone + CanRelayEvent + CanLogRelay + CanLogTargetEvent, + Target: ChainTarget, + Target::TargetChain: HasRuntime + HasEventSubscription, + Runtime: HasSpawner, +{ + async fn auto_relay_with_target(relay: &Relay) -> Result<(), Relay::Error> { + let chain = Target::target_chain(relay); + let runtime = chain.runtime(); + let subscription = chain.event_subscription(); + + loop { + if let Some(event_stream) = subscription.subscribe().await { + event_stream + .for_each_concurrent(None, |item| async move { + let relay = relay.clone(); + let spawner = runtime.spawner(); + + let handle = spawner.spawn(async move { + let (height, event) = item; + + let res = relay.relay_chain_event(&height, &event).await; + + if let Err(e) = res { + relay.log_relay( + Relay::Logger::LEVEL_ERROR, + "error relaying chain event", + |log| { + log.field("event", Relay::log_target_event(&event)) + .debug("error", &e); + }, + ) + } + }); + + handle.into_future().await; + }) + .await; + } else { + return Ok(()); + } + } + } +} diff --git a/crates/relayer-components-extra/src/relay/components/auto_relayers/parallel_two_way.rs b/crates/relayer-components-extra/src/relay/components/auto_relayers/parallel_two_way.rs new file mode 100644 index 0000000000..443e84c939 --- /dev/null +++ b/crates/relayer-components-extra/src/relay/components/auto_relayers/parallel_two_way.rs @@ -0,0 +1,46 @@ +use async_trait::async_trait; +use ibc_relayer_components::relay::traits::components::auto_relayer::{AutoRelayer, CanAutoRelay}; +use ibc_relayer_components::relay::traits::two_way::HasTwoWayRelay; +use ibc_relayer_components::runtime::traits::runtime::HasRuntime; + +use crate::runtime::traits::spawn::{HasSpawner, Spawner}; +use crate::std_prelude::*; + +/// A parallel two-way relay context that is composed of a `BiRelay` type that +/// can auto-relay between two connected targets. +/// +/// As opposed to the +/// [`ConcurrentTwoWayAutoRelay`](ibc_relayer_components::relay::impls::auto_relayers::concurrent_two_way::ConcurrentTwoWayAutoRelay) +/// variant, this type achieves +/// concurrency by spawning two tasks, each one responsible for relaying in +/// different directions. As such, it has an additional `HasSpawner` constraint +/// that the concurrent variant does not require. +pub struct ParallelTwoWayAutoRelay; + +#[async_trait] +impl AutoRelayer for ParallelTwoWayAutoRelay +where + BiRelay: HasTwoWayRelay, + Runtime: HasSpawner, + BiRelay::RelayAToB: CanAutoRelay + HasRuntime + Clone, + BiRelay::RelayBToA: CanAutoRelay + Clone, +{ + async fn auto_relay(birelay: &BiRelay) -> Result<(), BiRelay::Error> { + let relay_a_to_b = birelay.relay_a_to_b().clone(); + let relay_b_to_a = birelay.relay_b_to_a().clone(); + let spawner = relay_a_to_b.runtime().spawner(); + + let handle1 = spawner.spawn(async move { + let _ = BiRelay::RelayAToB::auto_relay(&relay_a_to_b).await; + }); + + let handle2 = spawner.spawn(async move { + let _ = BiRelay::RelayBToA::auto_relay(&relay_b_to_a).await; + }); + + handle1.into_future().await; + handle2.into_future().await; + + Ok(()) + } +} diff --git a/crates/relayer-components-extra/src/relay/components/mod.rs b/crates/relayer-components-extra/src/relay/components/mod.rs new file mode 100644 index 0000000000..35ebc79a34 --- /dev/null +++ b/crates/relayer-components-extra/src/relay/components/mod.rs @@ -0,0 +1,2 @@ +pub mod auto_relayers; +pub mod packet_relayers; diff --git a/crates/relayer-components-extra/src/relay/components/packet_relayers/mod.rs b/crates/relayer-components-extra/src/relay/components/packet_relayers/mod.rs new file mode 100644 index 0000000000..f53ac7233d --- /dev/null +++ b/crates/relayer-components-extra/src/relay/components/packet_relayers/mod.rs @@ -0,0 +1 @@ +pub mod retry; diff --git a/crates/relayer-components-extra/src/relay/components/packet_relayers/retry.rs b/crates/relayer-components-extra/src/relay/components/packet_relayers/retry.rs new file mode 100644 index 0000000000..e9f50b3e89 --- /dev/null +++ b/crates/relayer-components-extra/src/relay/components/packet_relayers/retry.rs @@ -0,0 +1,53 @@ +use core::marker::PhantomData; + +use async_trait::async_trait; +use ibc_relayer_components::core::traits::error::HasErrorType; +use ibc_relayer_components::relay::traits::chains::HasRelayChains; +use ibc_relayer_components::relay::traits::components::packet_relayer::PacketRelayer; +use ibc_relayer_components::relay::types::aliases::Packet; + +use crate::std_prelude::*; + +pub struct RetryRelayer { + pub phantom: PhantomData, +} + +pub trait SupportsPacketRetry: HasErrorType { + const MAX_RETRY: usize; + + fn is_retryable_error(e: &Self::Error) -> bool; + + fn max_retry_exceeded_error(e: Self::Error) -> Self::Error; +} + +#[async_trait] +impl PacketRelayer for RetryRelayer +where + Relay: HasRelayChains, + InRelayer: PacketRelayer, + Relay: SupportsPacketRetry, +{ + async fn relay_packet(context: &Relay, packet: &Packet) -> Result<(), Relay::Error> { + let mut retries_made: usize = 0; + loop { + let res = InRelayer::relay_packet(context, packet).await; + + match res { + Ok(()) => { + return Ok(()); + } + Err(e) => { + if Relay::is_retryable_error(&e) { + retries_made += 1; + + if retries_made > Relay::MAX_RETRY { + return Err(Relay::max_retry_exceeded_error(e)); + } + } else { + return Err(e); + } + } + } + } + } +} diff --git a/crates/relayer-components-extra/src/relay/mod.rs b/crates/relayer-components-extra/src/relay/mod.rs new file mode 100644 index 0000000000..f188f2c26d --- /dev/null +++ b/crates/relayer-components-extra/src/relay/mod.rs @@ -0,0 +1 @@ +pub mod components; diff --git a/crates/relayer-components-extra/src/runtime/impls/mod.rs b/crates/relayer-components-extra/src/runtime/impls/mod.rs new file mode 100644 index 0000000000..3e515356fa --- /dev/null +++ b/crates/relayer-components-extra/src/runtime/impls/mod.rs @@ -0,0 +1 @@ +pub mod subscription; diff --git a/crates/relayer-components-extra/src/runtime/impls/subscription/mod.rs b/crates/relayer-components-extra/src/runtime/impls/subscription/mod.rs new file mode 100644 index 0000000000..60d588fcc4 --- /dev/null +++ b/crates/relayer-components-extra/src/runtime/impls/subscription/mod.rs @@ -0,0 +1,2 @@ +pub mod multiplex; +pub mod stream; diff --git a/crates/relayer-components-extra/src/runtime/impls/subscription/multiplex.rs b/crates/relayer-components-extra/src/runtime/impls/subscription/multiplex.rs new file mode 100644 index 0000000000..095ef9c905 --- /dev/null +++ b/crates/relayer-components-extra/src/runtime/impls/subscription/multiplex.rs @@ -0,0 +1,173 @@ +use alloc::sync::Arc; +use core::ops::DerefMut; +use core::pin::Pin; + +use async_trait::async_trait; +use futures_core::stream::Stream; +use futures_util::stream::StreamExt; +use ibc_relayer_components::core::traits::sync::Async; +use ibc_relayer_components::runtime::traits::mutex::HasMutex; +use ibc_relayer_components::runtime::traits::subscription::Subscription; + +use crate::runtime::traits::channel::{ + CanCreateChannels, CanStreamReceiver, CanUseChannels, HasChannelTypes, +}; +use crate::runtime::traits::spawn::{HasSpawner, Spawner}; +use crate::std_prelude::*; + +/** + Multiplex the incoming [`Stream`] provided by an underlying [`Subscription`] + into multiple outgoing [`Stream`]s through a background task. This is + an auto trait implemented by all runtime contexts that implement + [`HasSpawner`], [`HasMutex`], [`CanCreateChannels`], [`CanUseChannels`], + and [`CanStreamReceiver`]. + + This can be used to improve the efficiency of naive subscriptions created from + [`CanCreateClosureSubscription`](ibc_relayer_components::runtime::impls::subscription::closure::CanCreateClosureSubscription). + For example, one can first create a subscription closure that establishes + new network connection each time `subscribe` is called. The subscription + closure is then passed to [`multiplex_subscription`](Self::multiplex_subscription), + which would return a wrapped subscription which would only create one + network connection at a time. + + The multiplexed subscription also attempts to recover by calling the + [`subscribe`](Subscription::subscribe) method of the underlying subsciption + again, if a given [`Stream`] terminates. This would allow for auto recovery + from underlying errors such as network disconnection. The multiplexed + subscription would only terminate if the underlying + [`subscribe`](Subscription::subscribe) returns `None`. + + The streams returned from the [`subscribe`](Subscription::subscribe) of + the multiplexed subscription will automatically resume streaming from + a new underlying stream, if the original underlying stream is terminated. + However, since a consumer cannot know if a [`Subscription`] implementation + is multiplexed or not, it should always retry calling + [`subscribe`](Subscription::subscribe) in case a [`Stream`] ends. +*/ +pub trait CanMultiplexSubscription { + /** + Multiplex a given subscription, with a mapper function that maps the + item coming from the underlying subscription from `T` to `U`. Returns + a new multiplexed subscription that shares the same underlying [`Stream`]. + */ + fn multiplex_subscription( + &self, + subscription: impl Subscription, + map_item: impl Fn(T) -> U + Send + Sync + 'static, + ) -> Arc> + where + T: Async + Clone, + U: Async + Clone; +} + +impl CanMultiplexSubscription for Runtime +where + Runtime: HasSpawner + HasMutex + CanCreateChannels + CanUseChannels + CanStreamReceiver, +{ + fn multiplex_subscription( + &self, + in_subscription: impl Subscription, + map_item: impl Fn(T) -> U + Send + Sync + 'static, + ) -> Arc> + where + T: Async + Clone, + U: Async + Clone, + { + let stream_senders = Arc::new(Runtime::new_mutex(Some(Vec::new()))); + + let spawner = self.spawner(); + let task_senders = stream_senders.clone(); + + spawner.spawn(async move { + loop { + let m_stream = in_subscription.subscribe().await; + + match m_stream { + Some(stream) => { + let task_senders = &task_senders; + let map_item = &map_item; + + stream + .for_each(|item| async move { + let mapped = map_item(item); + let mut m_senders = Runtime::acquire_mutex(task_senders).await; + + if let Some(senders) = m_senders.deref_mut() { + // Remove senders where the receiver side has been dropped + senders.retain(|sender| { + Runtime::send(sender, mapped.clone()).is_ok() + }); + } + }) + .await; + } + None => { + // If the underlying subscription returns `None` from `subscribe`, clears the senders + // queue inside the mutex and set it to `None` and return. This will cause subsequent + // calls to `subscribe` for the multiplexed subscription to also return `None`. + let mut senders = Runtime::acquire_mutex(&task_senders).await; + *senders = None; + return; + } + } + } + }); + + let subscription: MultiplexingSubscription = + MultiplexingSubscription { stream_senders }; + + Arc::new(subscription) + } +} + +type StreamSender = ::Sender; + +type StreamSenders = + Arc<::Mutex>>>>; + +pub struct MultiplexingSubscription +where + T: Async, + Runtime: HasChannelTypes + HasMutex, +{ + pub stream_senders: StreamSenders, +} + +impl Clone for MultiplexingSubscription +where + T: Async, + Runtime: HasChannelTypes + HasMutex, +{ + fn clone(&self) -> Self { + Self { + stream_senders: self.stream_senders.clone(), + } + } +} + +#[async_trait] +impl Subscription for MultiplexingSubscription +where + T: Async, + Runtime: HasMutex + CanCreateChannels + CanStreamReceiver, +{ + type Item = T; + + async fn subscribe(&self) -> Option + Send + 'static>>> + where + T: Async, + { + let mut m_senders = Runtime::acquire_mutex(&self.stream_senders).await; + + match m_senders.as_mut() { + Some(senders) => { + let (sender, receiver) = Runtime::new_channel(); + senders.push(sender); + + let stream = Runtime::receiver_to_stream(receiver); + Some(stream) + } + None => None, + } + } +} diff --git a/crates/relayer-components-extra/src/runtime/impls/subscription/stream.rs b/crates/relayer-components-extra/src/runtime/impls/subscription/stream.rs new file mode 100644 index 0000000000..94a2db9c95 --- /dev/null +++ b/crates/relayer-components-extra/src/runtime/impls/subscription/stream.rs @@ -0,0 +1,71 @@ +use alloc::sync::Arc; +use core::ops::DerefMut; + +use futures_core::stream::Stream; +use futures_util::stream::StreamExt; +use ibc_relayer_components::core::traits::sync::Async; +use ibc_relayer_components::runtime::traits::mutex::HasMutex; +use ibc_relayer_components::runtime::traits::subscription::Subscription; + +use crate::runtime::impls::subscription::multiplex::MultiplexingSubscription; +use crate::runtime::traits::channel::{CanCreateChannels, CanStreamReceiver, CanUseChannels}; +use crate::runtime::traits::spawn::{HasSpawner, Spawner}; +use crate::std_prelude::*; + +/** + Allows multiplexing of a single [`Stream`] into a subscription. + This is an auto trait implemented by all runtime contexts that implement + [`HasSpawner`], [`HasMutex`], [`CanCreateChannels`], [`CanUseChannels`], + and [`CanStreamReceiver`]. + + When the stream terminates, the subscription also terminates. +*/ +pub trait CanStreamSubscription { + fn stream_subscription( + &self, + stream: impl Stream + Send + 'static, + ) -> Arc> + where + T: Async + Clone; +} + +impl CanStreamSubscription for Runtime +where + Runtime: HasSpawner + HasMutex + CanCreateChannels + CanUseChannels + CanStreamReceiver, +{ + fn stream_subscription( + &self, + stream: impl Stream + Send + 'static, + ) -> Arc> + where + T: Async + Clone, + { + let stream_senders = Arc::new(Runtime::new_mutex(Some(Vec::new()))); + + let spawner = self.spawner(); + let task_senders = stream_senders.clone(); + + spawner.spawn(async move { + let task_senders = &task_senders; + + stream + .for_each(|item| async move { + let mut m_senders = Runtime::acquire_mutex(task_senders).await; + + if let Some(senders) = m_senders.deref_mut() { + // Remove senders where the receiver side has been dropped + senders.retain(|sender| Runtime::send(sender, item.clone()).is_ok()); + } + }) + .await; + + let mut senders = Runtime::acquire_mutex(task_senders).await; + *senders = None; + }); + + let subscription: MultiplexingSubscription = + MultiplexingSubscription { stream_senders }; + + Arc::new(subscription) + } +} diff --git a/crates/relayer-components-extra/src/runtime/mod.rs b/crates/relayer-components-extra/src/runtime/mod.rs new file mode 100644 index 0000000000..d44cc4a184 --- /dev/null +++ b/crates/relayer-components-extra/src/runtime/mod.rs @@ -0,0 +1,2 @@ +pub mod impls; +pub mod traits; diff --git a/crates/relayer-components-extra/src/runtime/traits/channel.rs b/crates/relayer-components-extra/src/runtime/traits/channel.rs new file mode 100644 index 0000000000..e16a4b6d80 --- /dev/null +++ b/crates/relayer-components-extra/src/runtime/traits/channel.rs @@ -0,0 +1,159 @@ +/*! + Support for use of abstract communication channels within a runtime context. + + This module define the abstract traits that can be implemented by a runtime + context to support message-passing concurrency. This provides similar + functionalities as the Rust channel types defined in + [`std::sync::mpsc::channel`](https://doc.rust-lang.org/std/sync/mpsc/fn.channel.html). +*/ + +use core::pin::Pin; + +use async_trait::async_trait; +use futures_core::stream::Stream; +use ibc_relayer_components::core::traits::error::HasErrorType; +use ibc_relayer_components::core::traits::sync::Async; + +use crate::std_prelude::*; + +/** + Provides the abstract `Sender` and `Receiver` types for messsage-passing. + + The `Sender` and `Receiver` types are parameterized by an arbitrary payload + type `T` using generic associated types. Given any payload type `T` that + implements [`Async`], a runtime context that implements `HasChannelTypes` + should be able to provide the concrete types `Sender` and `Receiver`, + where messages of type `T` can be sent over to the `Sender` end and + received from the `Receiver` end. + + The abstract `Sender` and `Receiver` types need to support the + message-passing passing asynchronously, i.e. inside async functions. + As a result, although it work similar to the Rust channel provided in + the standard library, + [`std::sync::mpsc::channel`](https://doc.rust-lang.org/std/sync/mpsc/fn.channel.html), + the standard Rust channels are not suited for use here, as they could block + the entire thread running the async tasks. + + Instead, there are async equivalent of the channel types offered by async + libraries such as Tokio's + [tokio::sync::mpsc::channel](https://docs.rs/tokio/latest/tokio/sync/mpsc/fn.channel.html) + or async-std's [async_std::channel](https://docs.rs/async-std/latest/async_std/channel/index.html). + + A main difference between the channel types defined here and the common + MPSC (multiple producer, single consumer) channels in the stated libraries + is that we allow multiple consumers to use the same receiver. This is to + avoid the use of `&mut` references, which would require the entire context + containing a receiver to be mutable. Instead, concrete types can wrap a + single-consumer receiver as `Arc>>` in the concrete + type definition, so that it can be used as a multi-consumer receiver. + + The methods for using the abstract channel types are available in separate + traits, [`CanCreateChannels`] and [`CanUseChannels`]. This is because code + that needs to create new channels do not necessary need to use the channels, + and vice versa. Having separate traits makes it clear which capability a + component needs from the runtime. + + There is also a similar trait + [`HasChannelOnceTypes`](super::channel_once::HasChannelOnceTypes), + which defines abstract one-shot channel types that allow at most one message + to be sent over. +*/ +pub trait HasChannelTypes: HasErrorType { + /** + The sender end of a channel with payload type `T`. + */ + type Sender: Async + where + T: Async; + + /** + The receiver end of a channel with payload type `T`. + */ + type Receiver: Async + where + T: Async; +} + +/** + Allow the creation of new sender-receiver pairs for the channel types + defined in [`HasChannelTypes`]. +*/ +pub trait CanCreateChannels: HasChannelTypes { + /** + Given a generic payload type `T`, create a + [`Sender`](HasChannelTypes::Sender) and + [`Receiver`](HasChannelTypes::Receiver) pair that are connected. + + The returned sender-receiver pair is expected to satisfy the following + invariants: + + - Messages sent from the returned sender are expected to be received + via the returned receiver. + + - Messages sent from mismatch sender should never be received by the + given receiver. + + More invariants may be added in the future to better specify the + requirements of the abstract channel. For now, we assume that mainstream + implementation of Rust channels can all satisfy the same requirements. + */ + fn new_channel() -> (Self::Sender, Self::Receiver) + where + T: Async; +} + +/** + Allow the sending and receiving of message payloads over the + [`Sender`](HasChannelTypes::Sender) and + [`Receiver`](HasChannelTypes::Receiver) ends of a channel. +*/ +#[async_trait] +pub trait CanUseChannels: HasChannelTypes { + /** + Given a reference to [`Sender`](HasChannelTypes::Sender), + send a message payload of type `T` over the sender. + + If the receiver side of the channel has been dropped, the sending would + fail and an error will be returned. + + The sending operation is _synchronous_. This ensures the payload is + guaranteed to be in the channel queue after `send()` is called. + + The receiving operation is expected to be _asynchronous_. This means + that any operation after `receive()` is called on the receiving end + should _never_ execute within `send()`. + */ + fn send(sender: &Self::Sender, value: T) -> Result<(), Self::Error> + where + T: Async; + + /** + Given a reference to [`Receiver`](HasChannelTypes::Receiver), + asynchronously receive a message payload of type `T` that is sent + over the sender end. + + If the sender end is dropped before any value is sent, this would result + in an error in `receive()` + */ + async fn receive(receiver: &mut Self::Receiver) -> Result + where + T: Async; + + fn try_receive(receiver: &mut Self::Receiver) -> Result, Self::Error> + where + T: Async; +} + +pub trait CanStreamReceiver: HasChannelTypes { + fn receiver_to_stream( + receiver: Self::Receiver, + ) -> Pin + Send + 'static>> + where + T: Async; +} + +pub trait CanCloneSender: HasChannelTypes { + fn clone_sender(sender: &Self::Sender) -> Self::Sender + where + T: Async; +} diff --git a/crates/relayer-components-extra/src/runtime/traits/channel_once.rs b/crates/relayer-components-extra/src/runtime/traits/channel_once.rs new file mode 100644 index 0000000000..abcb40596a --- /dev/null +++ b/crates/relayer-components-extra/src/runtime/traits/channel_once.rs @@ -0,0 +1,32 @@ +use async_trait::async_trait; +use ibc_relayer_components::core::traits::error::HasErrorType; +use ibc_relayer_components::core::traits::sync::Async; + +use crate::std_prelude::*; + +pub trait HasChannelOnceTypes: HasErrorType { + type SenderOnce: Async + where + T: Async; + + type ReceiverOnce: Async + where + T: Async; +} + +pub trait CanCreateChannelsOnce: HasChannelOnceTypes { + fn new_channel_once() -> (Self::SenderOnce, Self::ReceiverOnce) + where + T: Async; +} + +#[async_trait] +pub trait CanUseChannelsOnce: HasChannelOnceTypes { + fn send_once(sender: Self::SenderOnce, value: T) -> Result<(), Self::Error> + where + T: Async; + + async fn receive_once(receiver: Self::ReceiverOnce) -> Result + where + T: Async; +} diff --git a/crates/relayer-components-extra/src/runtime/traits/mod.rs b/crates/relayer-components-extra/src/runtime/traits/mod.rs new file mode 100644 index 0000000000..6992c82585 --- /dev/null +++ b/crates/relayer-components-extra/src/runtime/traits/mod.rs @@ -0,0 +1,3 @@ +pub mod channel; +pub mod channel_once; +pub mod spawn; diff --git a/crates/relayer-components-extra/src/runtime/traits/spawn.rs b/crates/relayer-components-extra/src/runtime/traits/spawn.rs new file mode 100644 index 0000000000..e3d66ca1ff --- /dev/null +++ b/crates/relayer-components-extra/src/runtime/traits/spawn.rs @@ -0,0 +1,42 @@ +use core::future::Future; +use core::pin::Pin; + +use futures_util::stream::{self, StreamExt}; +use ibc_relayer_components::core::traits::sync::Async; + +use crate::std_prelude::*; + +pub trait HasSpawner: Async { + type Spawner: Spawner; + + fn spawner(&self) -> Self::Spawner; +} + +pub trait Spawner: Async { + fn spawn(&self, task: F) -> Box + where + F: Future + Send + 'static, + F::Output: Send + 'static; +} + +pub trait TaskHandle: Send + 'static { + fn abort(self: Box); + + fn into_future(self: Box) -> Pin + Send + 'static>>; +} + +impl TaskHandle for Vec> { + fn abort(self: Box) { + for handle in self.into_iter() { + handle.abort(); + } + } + + fn into_future(self: Box) -> Pin + Send + 'static>> { + Box::pin(async move { + stream::iter(self.into_iter()) + .for_each_concurrent(None, |handle| handle.into_future()) + .await; + }) + } +} diff --git a/crates/relayer-components-extra/src/std_prelude.rs b/crates/relayer-components-extra/src/std_prelude.rs new file mode 100644 index 0000000000..ac791e1a54 --- /dev/null +++ b/crates/relayer-components-extra/src/std_prelude.rs @@ -0,0 +1,12 @@ +// Re-export according to alloc::prelude::v1 because it is not yet stabilized +// https://doc.rust-lang.org/src/alloc/prelude/v1.rs.html +pub use alloc::borrow::ToOwned; +pub use alloc::boxed::Box; +pub use alloc::format; +pub use alloc::string::{String, ToString}; +pub use alloc::vec; +pub use alloc::vec::Vec; +// Those are exported by default in the std prelude in Rust 2021 +pub use core::convert::{TryFrom, TryInto}; +pub use core::iter::FromIterator; +pub use core::prelude::v1::*; diff --git a/crates/relayer-components-extra/src/telemetry/components/consensus_state.rs b/crates/relayer-components-extra/src/telemetry/components/consensus_state.rs new file mode 100644 index 0000000000..74f80428ef --- /dev/null +++ b/crates/relayer-components-extra/src/telemetry/components/consensus_state.rs @@ -0,0 +1,37 @@ +use async_trait::async_trait; +use ibc_relayer_components::chain::traits::components::consensus_state_querier::ConsensusStateQuerier; +use ibc_relayer_components::chain::traits::types::consensus_state::HasConsensusStateType; +use ibc_relayer_components::chain::traits::types::height::HasHeightType; +use ibc_relayer_components::chain::traits::types::ibc::HasIbcChainTypes; +use ibc_relayer_components::core::traits::error::HasErrorType; + +use crate::std_prelude::*; +use crate::telemetry::traits::metrics::{HasMetric, TelemetryCounter}; +use crate::telemetry::traits::telemetry::HasTelemetry; + +pub struct ConsensusStateTelemetryQuerier { + pub querier: InQuerier, +} + +#[async_trait] +impl ConsensusStateQuerier + for ConsensusStateTelemetryQuerier +where + Chain: HasIbcChainTypes + HasTelemetry + HasErrorType, + Counterparty: HasConsensusStateType + HasHeightType, + InQuerier: ConsensusStateQuerier, + Telemetry: HasMetric, + Telemetry::Value: From, +{ + async fn query_consensus_state( + chain: &Chain, + client_id: &Chain::ClientId, + height: &Counterparty::Height, + ) -> Result { + let telemetry = chain.telemetry(); + let label = Telemetry::new_label("query_type", "consensus_state"); + telemetry.update_metric("query", &[label], 1u64.into(), None, None); + let status = InQuerier::query_consensus_state(chain, client_id, height).await?; + Ok(status) + } +} diff --git a/crates/relayer-components-extra/src/telemetry/components/mod.rs b/crates/relayer-components-extra/src/telemetry/components/mod.rs new file mode 100644 index 0000000000..0ef40c755e --- /dev/null +++ b/crates/relayer-components-extra/src/telemetry/components/mod.rs @@ -0,0 +1,2 @@ +pub mod consensus_state; +pub mod status; diff --git a/crates/relayer-components-extra/src/telemetry/components/status.rs b/crates/relayer-components-extra/src/telemetry/components/status.rs new file mode 100644 index 0000000000..80dd9515b9 --- /dev/null +++ b/crates/relayer-components-extra/src/telemetry/components/status.rs @@ -0,0 +1,30 @@ +use async_trait::async_trait; +use ibc_relayer_components::chain::traits::components::chain_status_querier::*; +use ibc_relayer_components::chain::traits::types::status::HasChainStatusType; +use ibc_relayer_components::core::traits::error::HasErrorType; + +use crate::std_prelude::*; +use crate::telemetry::traits::metrics::{HasMetric, TelemetryCounter}; +use crate::telemetry::traits::telemetry::HasTelemetry; + +pub struct ChainStatusTelemetryQuerier { + pub querier: InQuerier, +} + +#[async_trait] +impl ChainStatusQuerier + for ChainStatusTelemetryQuerier +where + InQuerier: ChainStatusQuerier, + Chain: HasChainStatusType + HasTelemetry + HasErrorType, + Telemetry: HasMetric, + Telemetry::Value: From, +{ + async fn query_chain_status(context: &Chain) -> Result { + let telemetry = context.telemetry(); + let label = Telemetry::new_label("query_type", "status"); + telemetry.update_metric("query", &[label], 1u64.into(), None, None); + let status = InQuerier::query_chain_status(context).await?; + Ok(status) + } +} diff --git a/crates/relayer-components-extra/src/telemetry/mod.rs b/crates/relayer-components-extra/src/telemetry/mod.rs new file mode 100644 index 0000000000..1f44d235ab --- /dev/null +++ b/crates/relayer-components-extra/src/telemetry/mod.rs @@ -0,0 +1,2 @@ +pub mod components; +pub mod traits; diff --git a/crates/relayer-components-extra/src/telemetry/traits/metrics.rs b/crates/relayer-components-extra/src/telemetry/traits/metrics.rs new file mode 100644 index 0000000000..f57271d996 --- /dev/null +++ b/crates/relayer-components-extra/src/telemetry/traits/metrics.rs @@ -0,0 +1,38 @@ +use ibc_relayer_components::core::traits::sync::Async; + +pub trait HasLabel: Async { + type Label: Async; + fn new_label(key: &str, value: &str) -> Self::Label; +} + +pub trait HasMetric: Async + HasLabel { + type Value: Async; + type Unit: Async; + + fn update_metric( + &self, + name: &str, + labels: &[Self::Label], + value: Self::Value, + description: Option<&str>, + unit: Option, + ); +} + +pub struct TelemetryCounter; +pub struct TelemetryValueRecorder; +pub struct TelemetryUpDownCounter; + +pub trait HasBasicMetrics: + HasMetric + + HasMetric + + HasMetric +{ +} + +impl HasBasicMetrics for Telemetry where + Telemetry: HasMetric + + HasMetric + + HasMetric +{ +} diff --git a/crates/relayer-components-extra/src/telemetry/traits/mod.rs b/crates/relayer-components-extra/src/telemetry/traits/mod.rs new file mode 100644 index 0000000000..23d15728b9 --- /dev/null +++ b/crates/relayer-components-extra/src/telemetry/traits/mod.rs @@ -0,0 +1,2 @@ +pub mod metrics; +pub mod telemetry; diff --git a/crates/relayer-components-extra/src/telemetry/traits/telemetry.rs b/crates/relayer-components-extra/src/telemetry/traits/telemetry.rs new file mode 100644 index 0000000000..91782da358 --- /dev/null +++ b/crates/relayer-components-extra/src/telemetry/traits/telemetry.rs @@ -0,0 +1,7 @@ +use ibc_relayer_components::core::traits::sync::Async; + +pub trait HasTelemetry { + type Telemetry: Async; + + fn telemetry(&self) -> &Self::Telemetry; +} diff --git a/crates/relayer-components/Cargo.toml b/crates/relayer-components/Cargo.toml new file mode 100644 index 0000000000..37ee8756a3 --- /dev/null +++ b/crates/relayer-components/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "ibc-relayer-components" +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" +readme = "README.md" +keywords = ["blockchain", "consensus", "cosmos", "ibc", "tendermint"] +repository = "https://github.com/informalsystems/ibc-rs" +authors = ["Informal Systems "] +rust-version = "1.68" +description = """ + Implementation of an IBC Relayer in Rust, as a library +""" + +[package.metadata.docs.rs] +all-features = true + +[features] + +[dependencies] +async-trait = "0.1.56" +futures-core = { version = "0.3", default-features = false, features = ["alloc"] } +futures-util = { version = "0.3", default-features = false, features = ["alloc"] } diff --git a/crates/relayer-components/src/build/components/birelay.rs b/crates/relayer-components/src/build/components/birelay.rs new file mode 100644 index 0000000000..7f0459be96 --- /dev/null +++ b/crates/relayer-components/src/build/components/birelay.rs @@ -0,0 +1,49 @@ +use async_trait::async_trait; + +use crate::build::traits::components::birelay_builder::BiRelayBuilder; +use crate::build::traits::components::birelay_from_relay_builder::CanBuildBiRelayFromRelays; +use crate::build::traits::components::relay_builder::CanBuildRelay; +use crate::build::traits::target::relay::{RelayAToBTarget, RelayBToATarget}; +use crate::build::types::aliases::{ChainIdA, ChainIdB, ClientIdA, ClientIdB}; +use crate::std_prelude::*; + +pub struct BuildBiRelayFromRelays; + +#[async_trait] +impl BiRelayBuilder for BuildBiRelayFromRelays +where + Builder: + CanBuildBiRelayFromRelays + CanBuildRelay + CanBuildRelay, +{ + async fn build_birelay( + builder: &Builder, + chain_id_a: &ChainIdA, + chain_id_b: &ChainIdB, + client_id_a: &ClientIdA, + client_id_b: &ClientIdB, + ) -> Result { + let relay_a_to_b = builder + .build_relay( + RelayAToBTarget, + chain_id_a, + chain_id_b, + client_id_a, + client_id_b, + ) + .await?; + + let relay_b_to_a = builder + .build_relay( + RelayBToATarget, + chain_id_b, + chain_id_a, + client_id_b, + client_id_a, + ) + .await?; + + builder + .build_birelay_from_relays(relay_a_to_b, relay_b_to_a) + .await + } +} diff --git a/crates/relayer-components/src/build/components/chain/cache.rs b/crates/relayer-components/src/build/components/chain/cache.rs new file mode 100644 index 0000000000..bbb218bd72 --- /dev/null +++ b/crates/relayer-components/src/build/components/chain/cache.rs @@ -0,0 +1,39 @@ +use core::marker::PhantomData; + +use async_trait::async_trait; + +use crate::build::traits::cache::HasChainCache; +use crate::build::traits::components::chain_builder::ChainBuilder; +use crate::build::traits::target::chain::ChainBuildTarget; +use crate::build::types::aliases::{TargetChain, TargetChainId}; +use crate::core::traits::error::HasErrorType; +use crate::runtime::traits::mutex::HasMutex; +use crate::std_prelude::*; + +pub struct BuildChainWithCache(pub PhantomData); + +#[async_trait] +impl ChainBuilder for BuildChainWithCache +where + TargetChain: Clone, + TargetChainId: Ord + Clone, + Build: HasChainCache + HasErrorType, + InBuilder: ChainBuilder, + Target: ChainBuildTarget, +{ + async fn build_chain( + build: &Build, + chain_id: &TargetChainId, + ) -> Result, Build::Error> { + let mut cache = Build::Runtime::acquire_mutex(build.chain_cache()).await; + + if let Some(chain) = cache.get(chain_id) { + Ok(chain.clone()) + } else { + let chain = InBuilder::build_chain(build, chain_id).await?; + cache.insert(chain_id.clone(), chain.clone()); + + Ok(chain) + } + } +} diff --git a/crates/relayer-components/src/build/components/chain/mod.rs b/crates/relayer-components/src/build/components/chain/mod.rs new file mode 100644 index 0000000000..a5c08fdb0d --- /dev/null +++ b/crates/relayer-components/src/build/components/chain/mod.rs @@ -0,0 +1 @@ +pub mod cache; diff --git a/crates/relayer-components/src/build/components/mod.rs b/crates/relayer-components/src/build/components/mod.rs new file mode 100644 index 0000000000..676452be7b --- /dev/null +++ b/crates/relayer-components/src/build/components/mod.rs @@ -0,0 +1,3 @@ +pub mod birelay; +pub mod chain; +pub mod relay; diff --git a/crates/relayer-components/src/build/components/relay/build_from_chain.rs b/crates/relayer-components/src/build/components/relay/build_from_chain.rs new file mode 100644 index 0000000000..a01fb2c429 --- /dev/null +++ b/crates/relayer-components/src/build/components/relay/build_from_chain.rs @@ -0,0 +1,43 @@ +use async_trait::async_trait; + +use crate::build::traits::components::chain_builder::CanBuildChain; +use crate::build::traits::components::relay_builder::RelayBuilder; +use crate::build::traits::components::relay_from_chains_builder::CanBuildRelayFromChains; +use crate::build::traits::target::relay::RelayBuildTarget; +use crate::build::types::aliases::{DstChainTarget, SrcChainTarget}; +use crate::build::types::aliases::{ + TargetDstChainId, TargetDstClientId, TargetRelay, TargetSrcChainId, TargetSrcClientId, +}; +use crate::std_prelude::*; + +pub struct BuildRelayFromChains; + +#[async_trait] +impl RelayBuilder for BuildRelayFromChains +where + Build: CanBuildChain> + + CanBuildChain> + + CanBuildRelayFromChains, + Target: RelayBuildTarget, +{ + async fn build_relay( + build: &Build, + target: Target, + src_chain_id: &TargetSrcChainId, + dst_chain_id: &TargetDstChainId, + src_client_id: &TargetSrcClientId, + dst_client_id: &TargetDstClientId, + ) -> Result, Build::Error> { + let src_chain = build + .build_chain(>::default(), src_chain_id) + .await?; + + let dst_chain = build + .build_chain(>::default(), dst_chain_id) + .await?; + + build + .build_relay_from_chains(target, src_client_id, dst_client_id, src_chain, dst_chain) + .await + } +} diff --git a/crates/relayer-components/src/build/components/relay/cache.rs b/crates/relayer-components/src/build/components/relay/cache.rs new file mode 100644 index 0000000000..13a712fedb --- /dev/null +++ b/crates/relayer-components/src/build/components/relay/cache.rs @@ -0,0 +1,63 @@ +use core::marker::PhantomData; + +use async_trait::async_trait; + +use crate::build::traits::cache::HasRelayCache; +use crate::build::traits::components::relay_builder::RelayBuilder; +use crate::build::traits::target::relay::RelayBuildTarget; +use crate::build::types::aliases::{ + TargetDstChainId, TargetDstClientId, TargetRelay, TargetSrcChainId, TargetSrcClientId, +}; +use crate::core::traits::error::HasErrorType; +use crate::runtime::traits::mutex::HasMutex; +use crate::std_prelude::*; + +pub struct BuildRelayWithCache(pub PhantomData); + +#[async_trait] +impl RelayBuilder for BuildRelayWithCache +where + TargetSrcChainId: Ord + Clone, + TargetDstChainId: Ord + Clone, + TargetSrcClientId: Ord + Clone, + TargetDstClientId: Ord + Clone, + TargetRelay: Clone, + Build: HasRelayCache + HasErrorType, + InBuilder: RelayBuilder, + Target: RelayBuildTarget, +{ + async fn build_relay( + build: &Build, + target: Target, + src_chain_id: &TargetSrcChainId, + dst_chain_id: &TargetDstChainId, + src_client_id: &TargetSrcClientId, + dst_client_id: &TargetDstClientId, + ) -> Result, Build::Error> { + let relay_id = ( + src_chain_id.clone(), + dst_chain_id.clone(), + src_client_id.clone(), + dst_client_id.clone(), + ); + + let mut cache = Build::Runtime::acquire_mutex(build.relay_cache()).await; + + if let Some(relay) = cache.get(&relay_id) { + Ok(relay.clone()) + } else { + let relay = InBuilder::build_relay( + build, + target, + src_chain_id, + dst_chain_id, + src_client_id, + dst_client_id, + ) + .await?; + cache.insert(relay_id, relay.clone()); + + Ok(relay) + } + } +} diff --git a/crates/relayer-components/src/build/components/relay/mod.rs b/crates/relayer-components/src/build/components/relay/mod.rs new file mode 100644 index 0000000000..ccccaf6935 --- /dev/null +++ b/crates/relayer-components/src/build/components/relay/mod.rs @@ -0,0 +1,2 @@ +pub mod build_from_chain; +pub mod cache; diff --git a/crates/relayer-components/src/build/impls/bootstrap/birelay.rs b/crates/relayer-components/src/build/impls/bootstrap/birelay.rs new file mode 100644 index 0000000000..ac8fd2f5e9 --- /dev/null +++ b/crates/relayer-components/src/build/impls/bootstrap/birelay.rs @@ -0,0 +1,69 @@ +use async_trait::async_trait; + +use crate::build::impls::bootstrap::relay::CanBootstrapRelay; +use crate::build::traits::birelay::HasBiRelayType; +use crate::build::traits::components::birelay_builder::CanBuildBiRelay; +use crate::build::traits::target::relay::RelayAToBTarget; +use crate::build::types::aliases::{ChainA, ChainB, ChainIdA, ChainIdB}; +use crate::chain::traits::client::create::HasCreateClientOptions; +use crate::core::traits::error::HasErrorType; +use crate::relay::traits::chains::HasRelayChains; +use crate::relay::traits::two_way::HasTwoWayRelay; +use crate::std_prelude::*; + +#[async_trait] +pub trait CanBootstrapBiRelay: HasBiRelayType + HasErrorType +where + ChainA: HasCreateClientOptions>, + ChainB: HasCreateClientOptions>, +{ + async fn bootstrap_birelay( + &self, + chain_id_a: &ChainIdA, + chain_id_b: &ChainIdB, + payload_options_a: & as HasCreateClientOptions>>::CreateClientPayloadOptions, + payload_options_b: & as HasCreateClientOptions>>::CreateClientPayloadOptions, + ) -> Result; +} + +#[async_trait] +impl CanBootstrapBiRelay for Build +where + Build: HasBiRelayType + + HasErrorType + + CanBuildBiRelay + + CanBootstrapRelay, + BiRelay: HasTwoWayRelay, + RelayAToB: HasRelayChains, + ChainA: HasCreateClientOptions, + ChainB: HasCreateClientOptions, +{ + async fn bootstrap_birelay( + &self, + chain_id_a: &ChainA::ChainId, + chain_id_b: &ChainB::ChainId, + payload_options_a: &ChainA::CreateClientPayloadOptions, + payload_options_b: &ChainB::CreateClientPayloadOptions, + ) -> Result { + let relay_a_to_b = self + .bootstrap_relay( + RelayAToBTarget, + chain_id_a, + chain_id_b, + payload_options_a, + payload_options_b, + ) + .await?; + + let bi_relay = self + .build_birelay( + chain_id_a, + chain_id_b, + relay_a_to_b.src_client_id(), + relay_a_to_b.dst_client_id(), + ) + .await?; + + Ok(bi_relay) + } +} diff --git a/crates/relayer-components/src/build/impls/bootstrap/mod.rs b/crates/relayer-components/src/build/impls/bootstrap/mod.rs new file mode 100644 index 0000000000..990666ad2d --- /dev/null +++ b/crates/relayer-components/src/build/impls/bootstrap/mod.rs @@ -0,0 +1,2 @@ +pub mod birelay; +pub mod relay; diff --git a/crates/relayer-components/src/build/impls/bootstrap/relay.rs b/crates/relayer-components/src/build/impls/bootstrap/relay.rs new file mode 100644 index 0000000000..a7dee51ea6 --- /dev/null +++ b/crates/relayer-components/src/build/impls/bootstrap/relay.rs @@ -0,0 +1,96 @@ +use async_trait::async_trait; + +use crate::build::traits::birelay::HasBiRelayType; +use crate::build::traits::components::chain_builder::CanBuildChain; +use crate::build::traits::components::relay_builder::CanBuildRelay; +use crate::build::traits::target::relay::RelayBuildTarget; +use crate::build::types::aliases::{ + RelayError, TargetDstChain, TargetDstChainId, TargetRelay, TargetSrcChain, TargetSrcChainId, +}; +use crate::chain::traits::client::create::HasCreateClientOptions; +use crate::core::traits::error::HasErrorType; +use crate::relay::traits::chains::HasRelayChains; +use crate::relay::traits::components::client_creator::CanCreateClient; +use crate::relay::traits::target::{DestinationTarget, SourceTarget}; +use crate::relay::traits::two_way::HasTwoWayRelay; +use crate::std_prelude::*; + +#[async_trait] +pub trait CanBootstrapRelay: HasBiRelayType + HasErrorType +where + Target: RelayBuildTarget, + TargetSrcChain: HasCreateClientOptions>, + TargetDstChain: HasCreateClientOptions>, +{ + async fn bootstrap_relay( + &self, + target: Target, + src_chain_id: &TargetSrcChainId, + dst_chain_id: &TargetDstChainId, + src_options: & as HasCreateClientOptions< + TargetDstChain, + >>::CreateClientPayloadOptions, + dst_options: & as HasCreateClientOptions< + TargetSrcChain, + >>::CreateClientPayloadOptions, + ) -> Result, Self::Error>; +} + +#[async_trait] +impl CanBootstrapRelay for Build +where + Build: CanBuildRelay + + CanBuildChain + + CanBuildChain, + Target: RelayBuildTarget, + Relay: HasRelayChains> + + CanCreateClient + + CanCreateClient, + SrcChain: HasCreateClientOptions, + DstChain: HasCreateClientOptions, +{ + async fn bootstrap_relay( + &self, + target: Target, + src_chain_id: &SrcChain::ChainId, + dst_chain_id: &DstChain::ChainId, + src_payload_options: &SrcChain::CreateClientPayloadOptions, + dst_payload_options: &DstChain::CreateClientPayloadOptions, + ) -> Result { + let src_chain = self + .build_chain(Target::SrcChainTarget::default(), src_chain_id) + .await?; + + let dst_chain = self + .build_chain(Target::DstChainTarget::default(), dst_chain_id) + .await?; + + let src_client_id = + Relay::create_client(SourceTarget, &src_chain, &dst_chain, dst_payload_options) + .await + .map_err(Build::BiRelay::relay_error) + .map_err(Build::birelay_error)?; + + let dst_client_id = Relay::create_client( + DestinationTarget, + &dst_chain, + &src_chain, + src_payload_options, + ) + .await + .map_err(Build::BiRelay::relay_error) + .map_err(Build::birelay_error)?; + + let relay = self + .build_relay( + target, + src_chain_id, + dst_chain_id, + &src_client_id, + &dst_client_id, + ) + .await?; + + Ok(relay) + } +} diff --git a/crates/relayer-components/src/build/impls/mod.rs b/crates/relayer-components/src/build/impls/mod.rs new file mode 100644 index 0000000000..4c8bab29b4 --- /dev/null +++ b/crates/relayer-components/src/build/impls/mod.rs @@ -0,0 +1 @@ +pub mod bootstrap; diff --git a/crates/relayer-components/src/build/mod.rs b/crates/relayer-components/src/build/mod.rs new file mode 100644 index 0000000000..3c8cbfdc51 --- /dev/null +++ b/crates/relayer-components/src/build/mod.rs @@ -0,0 +1,4 @@ +pub mod components; +pub mod impls; +pub mod traits; +pub mod types; diff --git a/crates/relayer-components/src/build/traits/birelay.rs b/crates/relayer-components/src/build/traits/birelay.rs new file mode 100644 index 0000000000..ebd0026581 --- /dev/null +++ b/crates/relayer-components/src/build/traits/birelay.rs @@ -0,0 +1,12 @@ +use crate::core::traits::error::HasErrorType; +use crate::relay::traits::two_way::HasTwoWayRelay; + +/// Trait for types that have access to a bi-directional relayer +/// that can relay between two connected chains in both directions. +pub trait HasBiRelayType: HasErrorType { + /// A relay context that can relay between two chains in a bi- + /// directional fashion. + type BiRelay: HasTwoWayRelay; + + fn birelay_error(e: ::Error) -> Self::Error; +} diff --git a/crates/relayer-components/src/build/traits/cache.rs b/crates/relayer-components/src/build/traits/cache.rs new file mode 100644 index 0000000000..440ee28aac --- /dev/null +++ b/crates/relayer-components/src/build/traits/cache.rs @@ -0,0 +1,19 @@ +use crate::build::traits::birelay::HasBiRelayType; +use crate::build::traits::target::chain::ChainBuildTarget; +use crate::build::traits::target::relay::RelayBuildTarget; +use crate::build::types::aliases::{TargetChainCache, TargetRelayCache}; +use crate::runtime::traits::mutex::HasRuntimeWithMutex; + +pub trait HasChainCache: HasBiRelayType + HasRuntimeWithMutex +where + Target: ChainBuildTarget, +{ + fn chain_cache(&self) -> &TargetChainCache; +} + +pub trait HasRelayCache: HasBiRelayType + HasRuntimeWithMutex +where + Target: RelayBuildTarget, +{ + fn relay_cache(&self) -> &TargetRelayCache; +} diff --git a/crates/relayer-components/src/build/traits/components/birelay_builder.rs b/crates/relayer-components/src/build/traits/components/birelay_builder.rs new file mode 100644 index 0000000000..d21798984c --- /dev/null +++ b/crates/relayer-components/src/build/traits/components/birelay_builder.rs @@ -0,0 +1,71 @@ +use async_trait::async_trait; + +use crate::build::traits::birelay::HasBiRelayType; +use crate::build::types::aliases::{ChainIdA, ChainIdB, ClientIdA, ClientIdB}; +use crate::core::traits::component::{DelegateComponent, HasComponents}; +use crate::core::traits::error::HasErrorType; +use crate::std_prelude::*; + +pub struct BiRelayBuilderComponent; + +#[async_trait] +pub trait BiRelayBuilder +where + Build: HasBiRelayType + HasErrorType, +{ + async fn build_birelay( + build: &Build, + chain_id_a: &ChainIdA, + chain_id_b: &ChainIdB, + client_id_a: &ClientIdA, + client_id_b: &ClientIdB, + ) -> Result; +} + +#[async_trait] +impl BiRelayBuilder for Component +where + Build: HasBiRelayType + HasErrorType, + Component: DelegateComponent, + Component::Delegate: BiRelayBuilder, +{ + async fn build_birelay( + build: &Build, + chain_id_a: &ChainIdA, + chain_id_b: &ChainIdB, + client_id_a: &ClientIdA, + client_id_b: &ClientIdB, + ) -> Result { + Component::Delegate::build_birelay(build, chain_id_a, chain_id_b, client_id_a, client_id_b) + .await + } +} + +#[async_trait] +pub trait CanBuildBiRelay: HasBiRelayType + HasErrorType { + async fn build_birelay( + &self, + chain_id_a: &ChainIdA, + chain_id_b: &ChainIdB, + client_id_a: &ClientIdA, + client_id_b: &ClientIdB, + ) -> Result; +} + +#[async_trait] +impl CanBuildBiRelay for Build +where + Build: HasBiRelayType + HasErrorType + HasComponents, + Build::Components: BiRelayBuilder, +{ + async fn build_birelay( + &self, + chain_id_a: &ChainIdA, + chain_id_b: &ChainIdB, + client_id_a: &ClientIdA, + client_id_b: &ClientIdB, + ) -> Result { + Build::Components::build_birelay(self, chain_id_a, chain_id_b, client_id_a, client_id_b) + .await + } +} diff --git a/crates/relayer-components/src/build/traits/components/birelay_from_relay_builder.rs b/crates/relayer-components/src/build/traits/components/birelay_from_relay_builder.rs new file mode 100644 index 0000000000..8df606e14d --- /dev/null +++ b/crates/relayer-components/src/build/traits/components/birelay_from_relay_builder.rs @@ -0,0 +1,62 @@ +use async_trait::async_trait; + +use crate::build::traits::birelay::HasBiRelayType; +use crate::build::types::aliases::{RelayAToB, RelayBToA}; +use crate::core::traits::component::{DelegateComponent, HasComponents}; +use crate::core::traits::error::HasErrorType; +use crate::core::traits::sync::Async; +use crate::std_prelude::*; + +pub struct BiRelayFromRelayBuilderComponent; + +#[async_trait] +pub trait BiRelayFromRelayBuilder: Async +where + Build: HasBiRelayType + HasErrorType, +{ + async fn build_birelay_from_relays( + build: &Build, + relay_a_to_b: RelayAToB, + relay_b_to_a: RelayBToA, + ) -> Result; +} + +#[async_trait] +impl BiRelayFromRelayBuilder for Component +where + Build: HasBiRelayType + HasErrorType, + Component: DelegateComponent, + Component::Delegate: BiRelayFromRelayBuilder, +{ + async fn build_birelay_from_relays( + build: &Build, + relay_a_to_b: RelayAToB, + relay_b_to_a: RelayBToA, + ) -> Result { + Component::Delegate::build_birelay_from_relays(build, relay_a_to_b, relay_b_to_a).await + } +} + +#[async_trait] +pub trait CanBuildBiRelayFromRelays: HasBiRelayType + HasErrorType { + async fn build_birelay_from_relays( + &self, + relay_a_to_b: RelayAToB, + relay_b_to_a: RelayBToA, + ) -> Result; +} + +#[async_trait] +impl CanBuildBiRelayFromRelays for Build +where + Build: HasBiRelayType + HasErrorType + HasComponents, + Build::Components: BiRelayFromRelayBuilder, +{ + async fn build_birelay_from_relays( + &self, + relay_a_to_b: RelayAToB, + relay_b_to_a: RelayBToA, + ) -> Result { + Build::Components::build_birelay_from_relays(self, relay_a_to_b, relay_b_to_a).await + } +} diff --git a/crates/relayer-components/src/build/traits/components/chain_builder.rs b/crates/relayer-components/src/build/traits/components/chain_builder.rs new file mode 100644 index 0000000000..d141494d44 --- /dev/null +++ b/crates/relayer-components/src/build/traits/components/chain_builder.rs @@ -0,0 +1,66 @@ +use async_trait::async_trait; + +use crate::build::traits::birelay::HasBiRelayType; +use crate::build::traits::target::chain::ChainBuildTarget; +use crate::build::types::aliases::{TargetChain, TargetChainId}; +use crate::core::traits::component::{DelegateComponent, HasComponents}; +use crate::core::traits::error::HasErrorType; +use crate::std_prelude::*; + +pub struct ChainBuilderComponent; + +#[async_trait] +pub trait ChainBuilder +where + Build: HasBiRelayType + HasErrorType, + Target: ChainBuildTarget, +{ + async fn build_chain( + build: &Build, + chain_id: &TargetChainId, + ) -> Result, Build::Error>; +} + +#[async_trait] +impl ChainBuilder for Component +where + Build: HasBiRelayType + HasErrorType, + Target: ChainBuildTarget, + Component: DelegateComponent, + Component::Delegate: ChainBuilder, +{ + async fn build_chain( + build: &Build, + chain_id: &TargetChainId, + ) -> Result, Build::Error> { + Component::Delegate::build_chain(build, chain_id).await + } +} + +#[async_trait] +pub trait CanBuildChain: HasBiRelayType + HasErrorType +where + Target: ChainBuildTarget, +{ + async fn build_chain( + &self, + _target: Target, + chain_id: &TargetChainId, + ) -> Result, Self::Error>; +} + +#[async_trait] +impl CanBuildChain for Build +where + Build: HasBiRelayType + HasErrorType + HasComponents, + Target: ChainBuildTarget, + Build::Components: ChainBuilder, +{ + async fn build_chain( + &self, + _target: Target, + chain_id: &TargetChainId, + ) -> Result, Self::Error> { + Build::Components::build_chain(self, chain_id).await + } +} diff --git a/crates/relayer-components/src/build/traits/components/mod.rs b/crates/relayer-components/src/build/traits/components/mod.rs new file mode 100644 index 0000000000..6f9e4a9bdc --- /dev/null +++ b/crates/relayer-components/src/build/traits/components/mod.rs @@ -0,0 +1,5 @@ +pub mod birelay_builder; +pub mod birelay_from_relay_builder; +pub mod chain_builder; +pub mod relay_builder; +pub mod relay_from_chains_builder; diff --git a/crates/relayer-components/src/build/traits/components/relay_builder.rs b/crates/relayer-components/src/build/traits/components/relay_builder.rs new file mode 100644 index 0000000000..4e6cc473fa --- /dev/null +++ b/crates/relayer-components/src/build/traits/components/relay_builder.rs @@ -0,0 +1,98 @@ +use async_trait::async_trait; + +use crate::build::traits::birelay::HasBiRelayType; +use crate::build::traits::target::relay::RelayBuildTarget; +use crate::build::types::aliases::{ + TargetDstChainId, TargetDstClientId, TargetRelay, TargetSrcChainId, TargetSrcClientId, +}; +use crate::core::traits::component::{DelegateComponent, HasComponents}; +use crate::core::traits::error::HasErrorType; +use crate::std_prelude::*; + +pub struct RelayBuilderComponent; + +#[async_trait] +pub trait RelayBuilder +where + Build: HasBiRelayType + HasErrorType, + Target: RelayBuildTarget, +{ + async fn build_relay( + build: &Build, + target: Target, + src_chain_id: &TargetSrcChainId, + dst_chain_id: &TargetDstChainId, + src_client_id: &TargetSrcClientId, + dst_client_id: &TargetDstClientId, + ) -> Result, Build::Error>; +} + +#[async_trait] +impl RelayBuilder for Component +where + Build: HasBiRelayType + HasErrorType, + Target: RelayBuildTarget, + Component: DelegateComponent, + Component::Delegate: RelayBuilder, +{ + async fn build_relay( + build: &Build, + target: Target, + src_chain_id: &TargetSrcChainId, + dst_chain_id: &TargetDstChainId, + src_client_id: &TargetSrcClientId, + dst_client_id: &TargetDstClientId, + ) -> Result, Build::Error> { + Component::Delegate::build_relay( + build, + target, + src_chain_id, + dst_chain_id, + src_client_id, + dst_client_id, + ) + .await + } +} + +#[async_trait] +pub trait CanBuildRelay: HasBiRelayType + HasErrorType +where + Target: RelayBuildTarget, +{ + async fn build_relay( + &self, + target: Target, + src_chain_id: &TargetSrcChainId, + dst_chain_id: &TargetDstChainId, + src_client_id: &TargetSrcClientId, + dst_client_id: &TargetDstClientId, + ) -> Result, Self::Error>; +} + +#[async_trait] +impl CanBuildRelay for Build +where + Build: HasBiRelayType + HasErrorType + HasComponents, + Target: RelayBuildTarget, + Build::Components: RelayBuilder, +{ + async fn build_relay( + &self, + target: Target, + src_chain_id: &TargetSrcChainId, + dst_chain_id: &TargetDstChainId, + src_client_id: &TargetSrcClientId, + dst_client_id: &TargetDstClientId, + ) -> Result, Self::Error> { + Build::Components::build_relay( + self, + target, + src_chain_id, + dst_chain_id, + src_client_id, + dst_client_id, + ) + .await + } +} diff --git a/crates/relayer-components/src/build/traits/components/relay_from_chains_builder.rs b/crates/relayer-components/src/build/traits/components/relay_from_chains_builder.rs new file mode 100644 index 0000000000..68d4ac2979 --- /dev/null +++ b/crates/relayer-components/src/build/traits/components/relay_from_chains_builder.rs @@ -0,0 +1,94 @@ +use async_trait::async_trait; + +use crate::build::traits::birelay::HasBiRelayType; +use crate::build::traits::target::relay::RelayBuildTarget; +use crate::build::types::aliases::{ + TargetDstChain, TargetDstClientId, TargetRelay, TargetSrcChain, TargetSrcClientId, +}; +use crate::core::traits::component::{DelegateComponent, HasComponents}; +use crate::core::traits::error::HasErrorType; +use crate::std_prelude::*; + +pub struct RelayFromChainsBuilderComponent; + +#[async_trait] +pub trait RelayFromChainsBuilder +where + Build: HasBiRelayType + HasErrorType, + Target: RelayBuildTarget, +{ + async fn build_relay_from_chains( + build: &Build, + src_client_id: &TargetSrcClientId, + dst_client_id: &TargetDstClientId, + src_chain: TargetSrcChain, + dst_chain: TargetDstChain, + ) -> Result, Build::Error>; +} + +#[async_trait] +impl RelayFromChainsBuilder for Component +where + Build: HasBiRelayType + HasErrorType, + Target: RelayBuildTarget, + Component: DelegateComponent, + Component::Delegate: RelayFromChainsBuilder, +{ + async fn build_relay_from_chains( + build: &Build, + src_client_id: &TargetSrcClientId, + dst_client_id: &TargetDstClientId, + src_chain: TargetSrcChain, + dst_chain: TargetDstChain, + ) -> Result, Build::Error> { + Component::Delegate::build_relay_from_chains( + build, + src_client_id, + dst_client_id, + src_chain, + dst_chain, + ) + .await + } +} + +#[async_trait] +pub trait CanBuildRelayFromChains: HasBiRelayType + HasErrorType +where + Target: RelayBuildTarget, +{ + async fn build_relay_from_chains( + &self, + target: Target, + src_client_id: &TargetSrcClientId, + dst_client_id: &TargetDstClientId, + src_chain: TargetSrcChain, + dst_chain: TargetDstChain, + ) -> Result, Self::Error>; +} + +#[async_trait] +impl CanBuildRelayFromChains for Build +where + Build: HasBiRelayType + HasErrorType + HasComponents, + Target: RelayBuildTarget, + Build::Components: RelayFromChainsBuilder, +{ + async fn build_relay_from_chains( + &self, + _target: Target, + src_client_id: &TargetSrcClientId, + dst_client_id: &TargetDstClientId, + src_chain: TargetSrcChain, + dst_chain: TargetDstChain, + ) -> Result, Self::Error> { + Build::Components::build_relay_from_chains( + self, + src_client_id, + dst_client_id, + src_chain, + dst_chain, + ) + .await + } +} diff --git a/crates/relayer-components/src/build/traits/mod.rs b/crates/relayer-components/src/build/traits/mod.rs new file mode 100644 index 0000000000..cf61402cb7 --- /dev/null +++ b/crates/relayer-components/src/build/traits/mod.rs @@ -0,0 +1,4 @@ +pub mod birelay; +pub mod cache; +pub mod components; +pub mod target; diff --git a/crates/relayer-components/src/build/traits/target/chain.rs b/crates/relayer-components/src/build/traits/target/chain.rs new file mode 100644 index 0000000000..5090fb6485 --- /dev/null +++ b/crates/relayer-components/src/build/traits/target/chain.rs @@ -0,0 +1,34 @@ +use crate::build::traits::birelay::HasBiRelayType; +use crate::build::types::aliases::{ChainA, ChainB}; +use crate::chain::traits::types::ibc::HasIbcChainTypes; +use crate::core::traits::sync::Async; + +#[derive(Default)] +pub struct ChainATarget; + +#[derive(Default)] +pub struct ChainBTarget; + +pub trait ChainBuildTarget: Default + Async { + type TargetChain: HasIbcChainTypes; + + type CounterpartyChain: HasIbcChainTypes; +} + +impl ChainBuildTarget for ChainATarget +where + Build: HasBiRelayType, +{ + type TargetChain = ChainA; + + type CounterpartyChain = ChainB; +} + +impl ChainBuildTarget for ChainBTarget +where + Build: HasBiRelayType, +{ + type TargetChain = ChainB; + + type CounterpartyChain = ChainA; +} diff --git a/crates/relayer-components/src/build/traits/target/mod.rs b/crates/relayer-components/src/build/traits/target/mod.rs new file mode 100644 index 0000000000..d8d5e5ef66 --- /dev/null +++ b/crates/relayer-components/src/build/traits/target/mod.rs @@ -0,0 +1,2 @@ +pub mod chain; +pub mod relay; diff --git a/crates/relayer-components/src/build/traits/target/relay.rs b/crates/relayer-components/src/build/traits/target/relay.rs new file mode 100644 index 0000000000..335e63dafe --- /dev/null +++ b/crates/relayer-components/src/build/traits/target/relay.rs @@ -0,0 +1,52 @@ +use crate::build::traits::birelay::HasBiRelayType; +use crate::build::traits::target::chain::{ChainATarget, ChainBTarget, ChainBuildTarget}; +use crate::build::types::aliases::{RelayAToB, RelayBToA, RelayError}; +use crate::core::traits::sync::Async; +use crate::relay::traits::chains::HasRelayChains; + +#[derive(Default)] +pub struct RelayAToBTarget; + +#[derive(Default)] +pub struct RelayBToATarget; + +pub trait RelayBuildTarget: Default + Async +where + Build: HasBiRelayType, +{ + type TargetRelay: HasRelayChains>; + + type SrcChainTarget: ChainBuildTarget< + Build, + TargetChain = ::SrcChain, + CounterpartyChain = ::DstChain, + >; + + type DstChainTarget: ChainBuildTarget< + Build, + TargetChain = ::DstChain, + CounterpartyChain = ::SrcChain, + >; +} + +impl RelayBuildTarget for RelayAToBTarget +where + Build: HasBiRelayType, +{ + type TargetRelay = RelayAToB; + + type SrcChainTarget = ChainATarget; + + type DstChainTarget = ChainBTarget; +} + +impl RelayBuildTarget for RelayBToATarget +where + Build: HasBiRelayType, +{ + type TargetRelay = RelayBToA; + + type SrcChainTarget = ChainBTarget; + + type DstChainTarget = ChainATarget; +} diff --git a/crates/relayer-components/src/build/types/aliases.rs b/crates/relayer-components/src/build/types/aliases.rs new file mode 100644 index 0000000000..13ebc408f9 --- /dev/null +++ b/crates/relayer-components/src/build/types/aliases.rs @@ -0,0 +1,117 @@ +use alloc::collections::BTreeMap; + +use crate::build::traits::birelay::HasBiRelayType; +use crate::build::traits::target::chain::ChainBuildTarget; +use crate::build::traits::target::relay::RelayBuildTarget; +use crate::chain::traits::types::chain_id::HasChainIdType; +use crate::chain::traits::types::ibc::HasIbcChainTypes; +use crate::core::traits::error::HasErrorType; +use crate::relay::traits::chains::HasRelayChains; +use crate::relay::traits::two_way::HasTwoWayRelay; +use crate::runtime::traits::runtime::HasRuntime; +use crate::runtime::types::aliases::Mutex; + +pub type RelayAToB = <::BiRelay as HasTwoWayRelay>::RelayAToB; + +pub type RelayBToA = <::BiRelay as HasTwoWayRelay>::RelayBToA; + +pub type ChainA = as HasRelayChains>::SrcChain; + +pub type ChainB = as HasRelayChains>::DstChain; + +pub type ChainIdA = as HasChainIdType>::ChainId; + +pub type ChainIdB = as HasChainIdType>::ChainId; + +pub type ClientIdA = as HasIbcChainTypes>>::ClientId; + +pub type ClientIdB = as HasIbcChainTypes>>::ClientId; + +pub type ChainACache = Mutex, ChainA>>; + +pub type ChainBCache = Mutex, ChainB>>; + +pub type RelayError = as HasErrorType>::Error; + +pub type RelayAToBCache = Mutex< + Build, + BTreeMap< + ( + ChainIdA, + ChainIdB, + ClientIdA, + ClientIdB, + ), + RelayAToB, + >, +>; + +pub type RelayBToACache = Mutex< + Build, + BTreeMap< + ( + ChainIdB, + ChainIdA, + ClientIdB, + ClientIdA, + ), + RelayBToA, + >, +>; + +pub type TargetChain = >::TargetChain; + +pub type TargetChainRuntime = as HasRuntime>::Runtime; + +pub type TargetChainId = as HasChainIdType>::ChainId; + +pub type TargetClientId = + as HasIbcChainTypes>>::ClientId; + +pub type CounterpartyChain = >::CounterpartyChain; + +pub type CounterpartyChainId = + as HasChainIdType>::ChainId; + +pub type CounterpartyClientId = + as HasIbcChainTypes>>::ClientId; + +pub type TargetChainCache = + Mutex, TargetChain>>; + +pub type TargetRelay = >::TargetRelay; + +pub type TargetRelayError = as HasErrorType>::Error; + +pub type SrcChainTarget = >::SrcChainTarget; + +pub type DstChainTarget = >::DstChainTarget; + +pub type TargetSrcChain = as HasRelayChains>::SrcChain; + +pub type TargetDstChain = as HasRelayChains>::DstChain; + +pub type TargetSrcChainId = + as HasChainIdType>::ChainId; + +pub type TargetDstChainId = + as HasChainIdType>::ChainId; + +pub type TargetSrcClientId = + as HasIbcChainTypes>>::ClientId; + +pub type TargetDstClientId = + as HasIbcChainTypes>>::ClientId; + +pub type TargetRelayCache = Mutex< + Build, + BTreeMap< + ( + TargetSrcChainId, + TargetDstChainId, + TargetSrcClientId, + TargetDstClientId, + ), + TargetRelay, + >, +>; diff --git a/crates/relayer-components/src/build/types/mod.rs b/crates/relayer-components/src/build/types/mod.rs new file mode 100644 index 0000000000..d293a01b6f --- /dev/null +++ b/crates/relayer-components/src/build/types/mod.rs @@ -0,0 +1 @@ +pub mod aliases; diff --git a/crates/relayer-components/src/chain/mod.rs b/crates/relayer-components/src/chain/mod.rs new file mode 100644 index 0000000000..1b58516def --- /dev/null +++ b/crates/relayer-components/src/chain/mod.rs @@ -0,0 +1,68 @@ +/*! + Constructs for the chain context. + + A chain context corresponds to the context that the relayer uses to + interact with a single chain. For the purpose of the relayer, the + chain context acts as a _client_ to the chain. This is in contrast + with other provider-side chain constructs, which are used for implementing + a blockchain, and are not covered by this chain context. + + At its core, a chain context should implement + [`HasChainTypes`](traits::types::chain::HasChainTypes), which declares the abstract + types that are commonly used inside a chain context. + + The chain context provides functionalities for querying the chain, + such as through [`CanQueryChainStatus`](traits::queries::status::CanQueryChainStatus). + It also supports sending of messages to a chain using + [`CanSendMessages`](traits::message_sender::CanSendMessages). + + ## List of Traits + + Here is a non-comprehensive list of chain traits that are defined in this module: + + ### Type Traits + + - Chain types: + - [`HasChainTypes`](traits::types::chain::HasChainTypes) + - [`HasHeightType`](traits::types::height::HasHeightType) + - [`HasMessageType`](traits::types::message::HasMessageType) + - [`HasEventType`](traits::types::event::HasEventType) + - [`HasChainIdType`](traits::types::chain_id::HasChainIdType) + - [`HasTimestampType`](traits::types::timestamp::HasTimestampType) + - [`HasChainStatusType`](traits::types::status::HasChainStatusType) + - [`HasConsensusStateType`](traits::types::consensus_state::HasConsensusStateType) + - IBC chain types: + - [`HasIbcChainTypes`](traits::types::ibc::HasIbcChainTypes) + - [`HasIbcPacketTypes`](traits::types::packet::HasIbcPacketTypes) + - IBC events: + - [`HasWriteAcknowledgementEvent`](traits::types::ibc_events::write_ack::HasWriteAcknowledgementEvent) + - [`HasSendPacketEvent`](traits::types::ibc_events::send_packet::HasSendPacketEvent) + + ### Consumer Traits + + - Chain methods: + - [`HasChainId`](traits::types::chain_id::HasChainId) + - [`CanSendMessages`](traits::message_sender::CanSendMessages) + - [`CanEstimateMessageSize`](traits::types::message::CanEstimateMessageSize) + - [`HasCounterpartyMessageHeight`](traits::types::ibc::HasCounterpartyMessageHeight) + - Message builders: + - [`CanBuildAckPacketMessage`](traits::message_builders::ack_packet::CanBuildAckPacketMessage) + - [`CanBuildReceivePacketMessage`](traits::message_builders::receive_packet::CanBuildReceivePacketMessage) + - [`CanBuildTimeoutUnorderedPacketMessage`](traits::message_builders::timeout_unordered_packet::CanBuildTimeoutUnorderedPacketMessage) + - Chain queries: + - [`CanQueryChainStatus`](traits::queries::status::CanQueryChainStatus) + - [`CanQueryConsensusState`](traits::queries::consensus_state::CanQueryConsensusState) + - [`CanQueryReceivedPacket`](traits::queries::received_packet::CanQueryReceivedPacket) + - [`CanQueryCounterpartyChainIdFromChannel`](traits::queries::channel::CanQueryCounterpartyChainIdFromChannel) + - [`CanQueryWriteAcknowledgement`](traits::queries::write_ack::CanQueryWriteAcknowledgement) + + ### Provider Traits + + - [`MessageSender`](traits::message_sender::MessageSender) + - [`ChainStatusQuerier`](traits::queries::status::ChainStatusQuerier) + - [`ConsensusStateQuerier`](traits::queries::consensus_state::ConsensusStateQuerier) + - [`ReceivedPacketQuerier`](traits::queries::received_packet::ReceivedPacketQuerier) +*/ + +pub mod traits; +pub mod types; diff --git a/crates/relayer-components/src/chain/traits/client/client_state.rs b/crates/relayer-components/src/chain/traits/client/client_state.rs new file mode 100644 index 0000000000..46fd9e43ac --- /dev/null +++ b/crates/relayer-components/src/chain/traits/client/client_state.rs @@ -0,0 +1,17 @@ +use async_trait::async_trait; + +use crate::chain::traits::types::client_state::HasClientStateType; +use crate::chain::traits::types::ibc::HasIbcChainTypes; +use crate::core::traits::error::HasErrorType; +use crate::std_prelude::*; + +#[async_trait] +pub trait CanQueryClientState: HasIbcChainTypes + HasErrorType +where + Counterparty: HasClientStateType, +{ + async fn query_client_state( + &self, + client_id: &Self::ClientId, + ) -> Result; +} diff --git a/crates/relayer-components/src/chain/traits/client/consensus_state.rs b/crates/relayer-components/src/chain/traits/client/consensus_state.rs new file mode 100644 index 0000000000..7cfdf0b2bb --- /dev/null +++ b/crates/relayer-components/src/chain/traits/client/consensus_state.rs @@ -0,0 +1,27 @@ +use async_trait::async_trait; + +use crate::chain::traits::types::height::HasHeightType; +use crate::chain::traits::types::ibc::HasIbcChainTypes; +use crate::core::traits::error::HasErrorType; +use crate::std_prelude::*; + +#[async_trait] +pub trait CanFindConsensusStateHeight: + HasIbcChainTypes + HasErrorType +where + Counterparty: HasHeightType, +{ + /** + Query the chain to find a consensus state that has a height that is + less than or equal the target height. This is needed as a base trusted + height to build the headers for UpdateClient. + + Invariant: the returned height must be less than or equal to the given + target height. + */ + async fn find_consensus_state_height_before( + &self, + client_id: &Self::ClientId, + target_height: &Counterparty::Height, + ) -> Result; +} diff --git a/crates/relayer-components/src/chain/traits/client/create.rs b/crates/relayer-components/src/chain/traits/client/create.rs new file mode 100644 index 0000000000..1a7b2711f2 --- /dev/null +++ b/crates/relayer-components/src/chain/traits/client/create.rs @@ -0,0 +1,44 @@ +use async_trait::async_trait; + +use crate::chain::traits::types::ibc::HasIbcChainTypes; +use crate::core::traits::error::HasErrorType; +use crate::core::traits::sync::Async; +use crate::std_prelude::*; + +pub trait HasCreateClientOptions: HasIbcChainTypes { + type CreateClientPayloadOptions: Async; +} + +pub trait HasCreateClientPayload: HasIbcChainTypes { + type CreateClientPayload: Async; +} + +pub trait HasCreateClientEvent: HasIbcChainTypes { + type CreateClientEvent: Async; + + fn try_extract_create_client_event(event: Self::Event) -> Option; + + fn create_client_event_client_id(event: &Self::CreateClientEvent) -> &Self::ClientId; +} + +#[async_trait] +pub trait CanBuildCreateClientPayload: + HasCreateClientOptions + HasCreateClientPayload + HasErrorType +{ + async fn build_create_client_payload( + &self, + create_client_options: &Self::CreateClientPayloadOptions, + ) -> Result; +} + +#[async_trait] +pub trait CanBuildCreateClientMessage: + HasIbcChainTypes + HasErrorType +where + Counterparty: HasCreateClientPayload, +{ + async fn build_create_client_message( + &self, + counterparty_payload: Counterparty::CreateClientPayload, + ) -> Result; +} diff --git a/crates/relayer-components/src/chain/traits/client/mod.rs b/crates/relayer-components/src/chain/traits/client/mod.rs new file mode 100644 index 0000000000..31b7e8c435 --- /dev/null +++ b/crates/relayer-components/src/chain/traits/client/mod.rs @@ -0,0 +1,4 @@ +pub mod client_state; +pub mod consensus_state; +pub mod create; +pub mod update; diff --git a/crates/relayer-components/src/chain/traits/client/update.rs b/crates/relayer-components/src/chain/traits/client/update.rs new file mode 100644 index 0000000000..011a2746cf --- /dev/null +++ b/crates/relayer-components/src/chain/traits/client/update.rs @@ -0,0 +1,36 @@ +use async_trait::async_trait; + +use crate::chain::traits::types::client_state::HasClientStateType; +use crate::chain::traits::types::ibc::HasIbcChainTypes; +use crate::core::traits::error::HasErrorType; +use crate::core::traits::sync::Async; +use crate::std_prelude::*; + +pub trait HasUpdateClientPayload: HasIbcChainTypes { + type UpdateClientPayload: Async; +} + +#[async_trait] +pub trait CanBuildUpdateClientPayload: + HasUpdateClientPayload + HasClientStateType + HasErrorType +{ + async fn build_update_client_payload( + &self, + trusted_height: &Self::Height, + target_height: &Self::Height, + client_state: Self::ClientState, + ) -> Result; +} + +#[async_trait] +pub trait CanBuildUpdateClientMessage: + HasIbcChainTypes + HasErrorType +where + Counterparty: HasUpdateClientPayload, +{ + async fn build_update_client_message( + &self, + client_id: &Self::ClientId, + payload: Counterparty::UpdateClientPayload, + ) -> Result, Self::Error>; +} diff --git a/crates/relayer-components/src/chain/traits/components/chain_status_querier.rs b/crates/relayer-components/src/chain/traits/components/chain_status_querier.rs new file mode 100644 index 0000000000..14573ff924 --- /dev/null +++ b/crates/relayer-components/src/chain/traits/components/chain_status_querier.rs @@ -0,0 +1,86 @@ +use async_trait::async_trait; + +use crate::chain::traits::types::height::HasHeightType; +use crate::chain::traits::types::status::HasChainStatusType; +use crate::core::traits::component::{DelegateComponent, HasComponents}; +use crate::core::traits::error::HasErrorType; +use crate::std_prelude::*; + +pub struct ChainStatusQuerierComponent; + +/** + The provider trait for [`ChainStatusQuerier`]. +*/ +#[async_trait] +pub trait ChainStatusQuerier +where + Chain: HasChainStatusType + HasErrorType, +{ + async fn query_chain_status(chain: &Chain) -> Result; +} + +#[async_trait] +impl ChainStatusQuerier for Component +where + Chain: HasChainStatusType + HasErrorType, + Component: DelegateComponent, + Component::Delegate: ChainStatusQuerier, +{ + async fn query_chain_status(chain: &Chain) -> Result { + Component::Delegate::query_chain_status(chain).await + } +} + +/** + Implemented by a chain context to provide method for querying the + [current status](HasChainStatusType::ChainStatus) of the blockchain. +*/ +#[async_trait] +pub trait CanQueryChainStatus: HasChainStatusType + HasErrorType { + /** + Query the current status of the blockchain. The returned + [status](HasChainStatusType::ChainStatus) is required to have the same + or increasing + [height](crate::chain::traits::types::height::HasHeightType::Height) + and + [timestamp](crate::chain::traits::types::timestamp::HasTimestampType::Timestamp) + each time the query is called. + + The querying of the chain status may fail due to errors such as the full + node not responding, or from network disconnection. + + TODO: Since the chain status can be queried frequently by the relayer, + we should implement a cache middleware that cache the result returned + from the chain query. + */ + async fn query_chain_status(&self) -> Result; +} + +#[async_trait] +impl CanQueryChainStatus for Chain +where + Chain: HasChainStatusType + HasErrorType + HasComponents, + Chain::Components: ChainStatusQuerier, +{ + async fn query_chain_status(&self) -> Result { + Chain::Components::query_chain_status(self).await + } +} + +#[async_trait] +pub trait CanQueryChainHeight: HasHeightType + HasErrorType { + async fn query_chain_height(&self) -> Result; +} + +#[async_trait] +impl CanQueryChainHeight for Chain +where + Chain: CanQueryChainStatus, + Chain::Height: Clone, +{ + async fn query_chain_height(&self) -> Result { + let status = self.query_chain_status().await?; + let height = Chain::chain_status_height(&status); + Ok(height.clone()) + } +} diff --git a/crates/relayer-components/src/chain/traits/components/consensus_state_querier.rs b/crates/relayer-components/src/chain/traits/components/consensus_state_querier.rs new file mode 100644 index 0000000000..89997935db --- /dev/null +++ b/crates/relayer-components/src/chain/traits/components/consensus_state_querier.rs @@ -0,0 +1,69 @@ +use async_trait::async_trait; + +use crate::chain::traits::types::consensus_state::HasConsensusStateType; +use crate::chain::traits::types::height::HasHeightType; +use crate::chain::traits::types::ibc::HasIbcChainTypes; +use crate::core::traits::component::{DelegateComponent, HasComponents}; +use crate::core::traits::error::HasErrorType; +use crate::std_prelude::*; + +pub struct ConsensusStateQuerierComponent; + +#[async_trait] +pub trait ConsensusStateQuerier +where + Chain: HasIbcChainTypes + HasErrorType, + Counterparty: HasConsensusStateType + HasHeightType, +{ + async fn query_consensus_state( + chain: &Chain, + client_id: &Chain::ClientId, + height: &Counterparty::Height, + ) -> Result; +} + +#[async_trait] +impl ConsensusStateQuerier for Component +where + Chain: HasIbcChainTypes + HasErrorType, + Counterparty: HasConsensusStateType + HasHeightType, + Component: DelegateComponent, + Component::Delegate: ConsensusStateQuerier, +{ + async fn query_consensus_state( + chain: &Chain, + client_id: &Chain::ClientId, + height: &Counterparty::Height, + ) -> Result { + Component::Delegate::query_consensus_state(chain, client_id, height).await + } +} + +#[async_trait] +pub trait CanQueryConsensusState: + HasIbcChainTypes + HasErrorType +where + Counterparty: HasConsensusStateType + HasHeightType, +{ + async fn query_consensus_state( + &self, + client_id: &Self::ClientId, + height: &Counterparty::Height, + ) -> Result; +} + +#[async_trait] +impl CanQueryConsensusState for Chain +where + Chain: HasIbcChainTypes + HasErrorType + HasComponents, + Counterparty: HasConsensusStateType + HasHeightType, + Chain::Components: ConsensusStateQuerier, +{ + async fn query_consensus_state( + &self, + client_id: &Self::ClientId, + height: &Counterparty::Height, + ) -> Result { + Chain::Components::query_consensus_state(self, client_id, height).await + } +} diff --git a/crates/relayer-components/src/chain/traits/components/message_sender.rs b/crates/relayer-components/src/chain/traits/components/message_sender.rs new file mode 100644 index 0000000000..289fc71e9e --- /dev/null +++ b/crates/relayer-components/src/chain/traits/components/message_sender.rs @@ -0,0 +1,190 @@ +/*! + Trait definitions for [`CanSendMessages`] and [`MessageSender`]. +*/ + +use async_trait::async_trait; + +use crate::chain::traits::types::event::HasEventType; +use crate::chain::traits::types::message::HasMessageType; +use crate::core::traits::component::{DelegateComponent, HasComponents}; +use crate::core::traits::error::HasErrorType; +use crate::core::traits::sync::Async; +use crate::std_prelude::*; + +pub struct MessageSenderComponent; + +/** + The provider trait for [`CanSendMessages`]. +*/ +#[async_trait] +pub trait MessageSender: Async +where + Chain: HasMessageType + HasEventType + HasErrorType, +{ + /** + Corresponds to [`CanSendMessages::send_messages`] + */ + async fn send_messages( + chain: &Chain, + messages: Vec, + ) -> Result>, Chain::Error>; +} + +#[async_trait] +impl MessageSender for Component +where + Chain: HasMessageType + HasEventType + HasErrorType, + Component: DelegateComponent, + Component::Delegate: MessageSender, +{ + async fn send_messages( + chain: &Chain, + messages: Vec, + ) -> Result>, Chain::Error> { + Component::Delegate::send_messages(chain, messages).await + } +} + +/** + This is a simplified interface offered by a chain context or a transaction + context for atomically sending a list of [messages](HasMessageType::Message) + to a chain. + + Behind the scene, the chain context may implement this by encoding the + given messages into a transaction, and then sending it to the chain. + + Before the given messages are submitted as a transaction, the chain context + may also perform additional operations, such as batching messages sent from + other tasks into the same transaction. + + A chain context may also use other strategies of submitting messages. For + example, a mock chain context can provide a mock implementation of sending + messages, without mocking the part for submitting the messages as + transactions. + + The implementation of [`send_messages`](Self::send_messages) _must_ treat + the sending of messages as an atomic operation. i.e. the messages must all + sent successfully, or all failed. + + In case if the total size of a given list of messages exceed some underlying + transaction limit, the implementation _must not_ attempt to split the given + messages into multiple transactions. This is because doing so could cause + partial failure, which violates the atomicity constraint. Instead, the + chain implementation should return an error and leave the task of splitting + the messages to smaller batch to the caller. + + For example, the + [`MaxTxSizeExceededError`](crate::transaction::impls::encoders::max_tx_size::MaxTxSizeExceededError) + error is returned from the + [`CheckEncodedTxSize`](crate::transaction::impls::encoders::max_tx_size::CheckEncodedTxSize) + component if the total message size exceeds a given transaction size limit. + A component like `CanSpawnBatchMessageWorker` + can then try and match against the error, and split the sent messages into + smaller batches. + + This trait is automatically implemented by + `OfaChainWrapper` for a chain context that implements `OfaBaseChain`. + From there, the [`CanSendMessages::send_messages`] method is derived from + `OfaBaseChain::send_messages`. + + Additionally, this trait is also automatically implemented by + `OfaTxWrapper` for a chain context that implements `OfaTxContext`. + From there, the + [`SendMessagesAsTx`](crate::transaction::impls::message_sender::SendMessagesAsTx) + component is used to submit the messages as transaction using the transaction + context. In other words, both the chain context and the transaction context + provides the same interface for sending messages using this trait. +*/ +#[async_trait] +pub trait CanSendMessages: HasMessageType + HasEventType + HasErrorType { + /** + Given a list of [messages](HasMessageType::Message), submit the messages + atomically to the chain. + + On success, the method returns a _nested_ list of + [events](HasEventType::Event). The length of the outer list must match + the length of the input message list. Each list of events in the + outer list corresponds to the events emitted from processing the message + at the same position in the input message list. + + On failure, the method returns an + [error](crate::core::traits::error::HasErrorType::Error). + Note that since the message sending must be atomic, the sending of + messages must either all succeed or all failed. i.e. partial failure + is forbidden. + */ + async fn send_messages( + &self, + messages: Vec, + ) -> Result>, Self::Error>; +} + +#[async_trait] +impl CanSendMessages for Chain +where + Chain: HasMessageType + HasEventType + HasErrorType + HasComponents, + Chain::Components: MessageSender, +{ + async fn send_messages( + &self, + messages: Vec, + ) -> Result>, Self::Error> { + Chain::Components::send_messages(self, messages).await + } +} + +pub trait InjectMismatchIbcEventsCountError: HasErrorType { + fn mismatch_ibc_events_count_error(expected: usize, actual: usize) -> Self::Error; +} + +#[async_trait] +pub trait CanSendFixSizedMessages: HasMessageType + HasEventType + HasErrorType { + async fn send_messages_fixed( + &self, + messages: [Self::Message; COUNT], + ) -> Result<[Vec; COUNT], Self::Error>; +} + +#[async_trait] +pub trait CanSendSingleMessage: HasMessageType + HasEventType + HasErrorType { + async fn send_message(&self, message: Self::Message) -> Result, Self::Error>; +} + +#[async_trait] +impl CanSendFixSizedMessages for Chain +where + Chain: CanSendMessages + InjectMismatchIbcEventsCountError, +{ + async fn send_messages_fixed( + &self, + messages: [Chain::Message; COUNT], + ) -> Result<[Vec; COUNT], Self::Error> { + let events_vec = self.send_messages(messages.into()).await?; + + let events = events_vec + .try_into() + .map_err(|e: Vec<_>| Chain::mismatch_ibc_events_count_error(COUNT, e.len()))?; + + Ok(events) + } +} + +#[async_trait] +impl CanSendSingleMessage for Chain +where + Chain: CanSendMessages, +{ + async fn send_message( + &self, + message: Chain::Message, + ) -> Result, Chain::Error> { + let events = self + .send_messages(vec![message]) + .await? + .into_iter() + .flatten() + .collect(); + + Ok(events) + } +} diff --git a/crates/relayer-components/src/chain/traits/components/mod.rs b/crates/relayer-components/src/chain/traits/components/mod.rs new file mode 100644 index 0000000000..c46766cdf5 --- /dev/null +++ b/crates/relayer-components/src/chain/traits/components/mod.rs @@ -0,0 +1,4 @@ +pub mod chain_status_querier; +pub mod consensus_state_querier; +pub mod message_sender; +pub mod packet_fields_reader; diff --git a/crates/relayer-components/src/chain/traits/components/packet_fields_reader.rs b/crates/relayer-components/src/chain/traits/components/packet_fields_reader.rs new file mode 100644 index 0000000000..0251d0e3c7 --- /dev/null +++ b/crates/relayer-components/src/chain/traits/components/packet_fields_reader.rs @@ -0,0 +1,215 @@ +use crate::chain::traits::types::ibc::HasIbcChainTypes; +use crate::chain::traits::types::packet::HasIbcPacketTypes; +use crate::core::traits::component::{DelegateComponent, HasComponents}; + +pub struct PacketFieldsReaderComponent; + +pub trait PacketFieldsReader +where + Chain: HasIbcPacketTypes + HasIbcChainTypes, + Counterparty: HasIbcChainTypes, +{ + fn incoming_packet_src_channel_id(packet: &Chain::IncomingPacket) -> &Counterparty::ChannelId; + + fn incoming_packet_dst_channel_id(packet: &Chain::IncomingPacket) -> &Chain::ChannelId; + + fn incoming_packet_src_port(packet: &Chain::IncomingPacket) -> &Counterparty::PortId; + + fn incoming_packet_dst_port(packet: &Chain::IncomingPacket) -> &Chain::PortId; + + fn incoming_packet_sequence(packet: &Chain::IncomingPacket) -> &Counterparty::Sequence; + + fn incoming_packet_timeout_height(packet: &Chain::IncomingPacket) -> Option<&Chain::Height>; + + fn incoming_packet_timeout_timestamp(packet: &Chain::IncomingPacket) -> &Chain::Timestamp; + + fn outgoing_packet_src_channel_id(packet: &Chain::OutgoingPacket) -> &Chain::ChannelId; + + fn outgoing_packet_dst_channel_id(packet: &Chain::OutgoingPacket) -> &Counterparty::ChannelId; + + fn outgoing_packet_src_port(packet: &Chain::OutgoingPacket) -> &Chain::PortId; + + fn outgoing_packet_dst_port(packet: &Chain::OutgoingPacket) -> &Counterparty::PortId; + + fn outgoing_packet_sequence(packet: &Chain::OutgoingPacket) -> &Chain::Sequence; + + fn outgoing_packet_timeout_height( + packet: &Chain::OutgoingPacket, + ) -> Option<&Counterparty::Height>; + + fn outgoing_packet_timeout_timestamp( + packet: &Chain::OutgoingPacket, + ) -> &Counterparty::Timestamp; +} + +pub trait CanReadPacketFields: + HasIbcPacketTypes + HasIbcChainTypes +where + Counterparty: HasIbcChainTypes, +{ + fn incoming_packet_src_channel_id(packet: &Self::IncomingPacket) -> &Counterparty::ChannelId; + + fn incoming_packet_dst_channel_id(packet: &Self::IncomingPacket) -> &Self::ChannelId; + + fn incoming_packet_src_port(packet: &Self::IncomingPacket) -> &Counterparty::PortId; + + fn incoming_packet_dst_port(packet: &Self::IncomingPacket) -> &Self::PortId; + + fn incoming_packet_sequence(packet: &Self::IncomingPacket) -> &Counterparty::Sequence; + + fn incoming_packet_timeout_height(packet: &Self::IncomingPacket) -> Option<&Self::Height>; + + fn incoming_packet_timeout_timestamp(packet: &Self::IncomingPacket) -> &Self::Timestamp; + + fn outgoing_packet_src_channel_id(packet: &Self::OutgoingPacket) -> &Self::ChannelId; + + fn outgoing_packet_dst_channel_id(packet: &Self::OutgoingPacket) -> &Counterparty::ChannelId; + + fn outgoing_packet_src_port(packet: &Self::OutgoingPacket) -> &Self::PortId; + + fn outgoing_packet_dst_port(packet: &Self::OutgoingPacket) -> &Counterparty::PortId; + + fn outgoing_packet_sequence(packet: &Self::OutgoingPacket) -> &Self::Sequence; + + fn outgoing_packet_timeout_height( + packet: &Self::OutgoingPacket, + ) -> Option<&Counterparty::Height>; + + fn outgoing_packet_timeout_timestamp(packet: &Self::OutgoingPacket) + -> &Counterparty::Timestamp; +} + +impl PacketFieldsReader for Component +where + Chain: HasIbcPacketTypes + HasIbcChainTypes, + Counterparty: HasIbcChainTypes, + Component: DelegateComponent, + Component::Delegate: PacketFieldsReader, +{ + fn incoming_packet_src_channel_id(packet: &Chain::IncomingPacket) -> &Counterparty::ChannelId { + Component::Delegate::incoming_packet_src_channel_id(packet) + } + + fn incoming_packet_dst_channel_id(packet: &Chain::IncomingPacket) -> &Chain::ChannelId { + Component::Delegate::incoming_packet_dst_channel_id(packet) + } + + fn incoming_packet_src_port(packet: &Chain::IncomingPacket) -> &Counterparty::PortId { + Component::Delegate::incoming_packet_src_port(packet) + } + + fn incoming_packet_dst_port(packet: &Chain::IncomingPacket) -> &Chain::PortId { + Component::Delegate::incoming_packet_dst_port(packet) + } + + fn incoming_packet_sequence(packet: &Chain::IncomingPacket) -> &Counterparty::Sequence { + Component::Delegate::incoming_packet_sequence(packet) + } + + fn incoming_packet_timeout_height(packet: &Chain::IncomingPacket) -> Option<&Chain::Height> { + Component::Delegate::incoming_packet_timeout_height(packet) + } + + fn incoming_packet_timeout_timestamp(packet: &Chain::IncomingPacket) -> &Chain::Timestamp { + Component::Delegate::incoming_packet_timeout_timestamp(packet) + } + + fn outgoing_packet_src_channel_id(packet: &Chain::OutgoingPacket) -> &Chain::ChannelId { + Component::Delegate::outgoing_packet_src_channel_id(packet) + } + + fn outgoing_packet_dst_channel_id(packet: &Chain::OutgoingPacket) -> &Counterparty::ChannelId { + Component::Delegate::outgoing_packet_dst_channel_id(packet) + } + + fn outgoing_packet_src_port(packet: &Chain::OutgoingPacket) -> &Chain::PortId { + Component::Delegate::outgoing_packet_src_port(packet) + } + + fn outgoing_packet_dst_port(packet: &Chain::OutgoingPacket) -> &Counterparty::PortId { + Component::Delegate::outgoing_packet_dst_port(packet) + } + + fn outgoing_packet_sequence(packet: &Chain::OutgoingPacket) -> &Chain::Sequence { + Component::Delegate::outgoing_packet_sequence(packet) + } + + fn outgoing_packet_timeout_height( + packet: &Chain::OutgoingPacket, + ) -> Option<&Counterparty::Height> { + Component::Delegate::outgoing_packet_timeout_height(packet) + } + + fn outgoing_packet_timeout_timestamp( + packet: &Chain::OutgoingPacket, + ) -> &Counterparty::Timestamp { + Component::Delegate::outgoing_packet_timeout_timestamp(packet) + } +} + +impl CanReadPacketFields for Chain +where + Chain: HasIbcPacketTypes + HasIbcChainTypes + HasComponents, + Counterparty: HasIbcChainTypes, + Chain::Components: PacketFieldsReader, +{ + fn incoming_packet_src_channel_id(packet: &Chain::IncomingPacket) -> &Counterparty::ChannelId { + Chain::Components::incoming_packet_src_channel_id(packet) + } + + fn incoming_packet_dst_channel_id(packet: &Chain::IncomingPacket) -> &Chain::ChannelId { + Chain::Components::incoming_packet_dst_channel_id(packet) + } + + fn incoming_packet_src_port(packet: &Chain::IncomingPacket) -> &Counterparty::PortId { + Chain::Components::incoming_packet_src_port(packet) + } + + fn incoming_packet_dst_port(packet: &Chain::IncomingPacket) -> &Chain::PortId { + Chain::Components::incoming_packet_dst_port(packet) + } + + fn incoming_packet_sequence(packet: &Chain::IncomingPacket) -> &Counterparty::Sequence { + Chain::Components::incoming_packet_sequence(packet) + } + + fn incoming_packet_timeout_height(packet: &Chain::IncomingPacket) -> Option<&Chain::Height> { + Chain::Components::incoming_packet_timeout_height(packet) + } + + fn incoming_packet_timeout_timestamp(packet: &Chain::IncomingPacket) -> &Chain::Timestamp { + Chain::Components::incoming_packet_timeout_timestamp(packet) + } + + fn outgoing_packet_src_channel_id(packet: &Chain::OutgoingPacket) -> &Chain::ChannelId { + Chain::Components::outgoing_packet_src_channel_id(packet) + } + + fn outgoing_packet_dst_channel_id(packet: &Chain::OutgoingPacket) -> &Counterparty::ChannelId { + Chain::Components::outgoing_packet_dst_channel_id(packet) + } + + fn outgoing_packet_src_port(packet: &Chain::OutgoingPacket) -> &Chain::PortId { + Chain::Components::outgoing_packet_src_port(packet) + } + + fn outgoing_packet_dst_port(packet: &Chain::OutgoingPacket) -> &Counterparty::PortId { + Chain::Components::outgoing_packet_dst_port(packet) + } + + fn outgoing_packet_sequence(packet: &Chain::OutgoingPacket) -> &Chain::Sequence { + Chain::Components::outgoing_packet_sequence(packet) + } + + fn outgoing_packet_timeout_height( + packet: &Chain::OutgoingPacket, + ) -> Option<&Counterparty::Height> { + Chain::Components::outgoing_packet_timeout_height(packet) + } + + fn outgoing_packet_timeout_timestamp( + packet: &Chain::OutgoingPacket, + ) -> &Counterparty::Timestamp { + Chain::Components::outgoing_packet_timeout_timestamp(packet) + } +} diff --git a/crates/relayer-components/src/chain/traits/event_subscription.rs b/crates/relayer-components/src/chain/traits/event_subscription.rs new file mode 100644 index 0000000000..67644a4e77 --- /dev/null +++ b/crates/relayer-components/src/chain/traits/event_subscription.rs @@ -0,0 +1,9 @@ +use alloc::sync::Arc; + +use crate::chain::traits::types::event::HasEventType; +use crate::chain::traits::types::height::HasHeightType; +use crate::runtime::traits::subscription::Subscription; + +pub trait HasEventSubscription: HasHeightType + HasEventType { + fn event_subscription(&self) -> &Arc>; +} diff --git a/crates/relayer-components/src/chain/traits/logs/event.rs b/crates/relayer-components/src/chain/traits/logs/event.rs new file mode 100644 index 0000000000..1119a6663b --- /dev/null +++ b/crates/relayer-components/src/chain/traits/logs/event.rs @@ -0,0 +1,7 @@ +use crate::chain::traits::types::event::HasEventType; +use crate::logger::traits::has_logger::HasLoggerType; +use crate::logger::traits::logger::BaseLogger; + +pub trait CanLogChainEvent: HasEventType + HasLoggerType { + fn log_event<'a>(event: &'a Self::Event) -> ::LogValue<'a>; +} diff --git a/crates/relayer-components/src/chain/traits/logs/message.rs b/crates/relayer-components/src/chain/traits/logs/message.rs new file mode 100644 index 0000000000..a6740b69cf --- /dev/null +++ b/crates/relayer-components/src/chain/traits/logs/message.rs @@ -0,0 +1,9 @@ +use crate::chain::traits::types::message::HasMessageType; +use crate::logger::traits::has_logger::HasLoggerType; +use crate::logger::traits::logger::BaseLogger; + +// TODO: refactor `CosmosIbcMessage` to support `Debug` before adding this +// to `OfaChain`. +pub trait CanLogChainMessage: HasMessageType + HasLoggerType { + fn log_message<'a>(message: &'a Self::Message) -> ::LogValue<'a>; +} diff --git a/crates/relayer-components/src/chain/traits/logs/mod.rs b/crates/relayer-components/src/chain/traits/logs/mod.rs new file mode 100644 index 0000000000..29e6eb1c98 --- /dev/null +++ b/crates/relayer-components/src/chain/traits/logs/mod.rs @@ -0,0 +1,2 @@ +pub mod event; +pub mod packet; diff --git a/crates/relayer-components/src/chain/traits/logs/packet.rs b/crates/relayer-components/src/chain/traits/logs/packet.rs new file mode 100644 index 0000000000..bec18b368a --- /dev/null +++ b/crates/relayer-components/src/chain/traits/logs/packet.rs @@ -0,0 +1,17 @@ +use crate::chain::traits::types::ibc::HasIbcChainTypes; +use crate::chain::traits::types::packet::HasIbcPacketTypes; +use crate::logger::traits::has_logger::HasLoggerType; +use crate::logger::traits::logger::BaseLogger; + +pub trait CanLogChainPacket: HasLoggerType + HasIbcPacketTypes +where + Counterparty: HasIbcChainTypes, +{ + fn log_outgoing_packet<'a>( + packet: &'a Self::OutgoingPacket, + ) -> ::LogValue<'a>; + + fn log_incoming_packet<'a>( + packet: &'a Self::IncomingPacket, + ) -> ::LogValue<'a>; +} diff --git a/crates/relayer-components/src/chain/traits/message_builders/ack_packet.rs b/crates/relayer-components/src/chain/traits/message_builders/ack_packet.rs new file mode 100644 index 0000000000..4f58ebb50b --- /dev/null +++ b/crates/relayer-components/src/chain/traits/message_builders/ack_packet.rs @@ -0,0 +1,44 @@ +use async_trait::async_trait; + +use crate::chain::traits::types::client_state::HasClientStateType; +use crate::chain::traits::types::height::HasHeightType; +use crate::chain::traits::types::ibc::HasIbcChainTypes; +use crate::chain::traits::types::ibc_events::write_ack::HasWriteAcknowledgementEvent; +use crate::chain::traits::types::message::HasMessageType; +use crate::chain::traits::types::packet::HasIbcPacketTypes; +use crate::chain::traits::types::packets::ack::HasAckPacketPayload; +use crate::core::traits::error::HasErrorType; +use crate::std_prelude::*; + +#[async_trait] +pub trait CanBuildAckPacketPayload: + HasAckPacketPayload + + HasWriteAcknowledgementEvent + + HasIbcPacketTypes + + HasClientStateType + + HasHeightType + + HasErrorType +where + Counterparty: HasIbcChainTypes, +{ + async fn build_ack_packet_payload( + &self, + client_state: &Self::ClientState, + height: &Self::Height, + packet: &Self::IncomingPacket, + ack: &Self::WriteAcknowledgementEvent, + ) -> Result; +} + +#[async_trait] +pub trait CanBuildAckPacketMessage: + HasMessageType + HasErrorType + HasIbcPacketTypes +where + Counterparty: HasAckPacketPayload, +{ + async fn build_ack_packet_message( + &self, + packet: &Self::OutgoingPacket, + payload: Counterparty::AckPacketPayload, + ) -> Result; +} diff --git a/crates/relayer-components/src/chain/traits/message_builders/channel.rs b/crates/relayer-components/src/chain/traits/message_builders/channel.rs new file mode 100644 index 0000000000..0256c0b13f --- /dev/null +++ b/crates/relayer-components/src/chain/traits/message_builders/channel.rs @@ -0,0 +1,74 @@ +use async_trait::async_trait; + +use crate::chain::traits::types::channel::{ + HasChannelHandshakePayloads, HasInitChannelOptionsType, +}; +use crate::chain::traits::types::client_state::HasClientStateType; +use crate::core::traits::error::HasErrorType; +use crate::std_prelude::*; + +#[async_trait] +pub trait CanBuildChannelHandshakePayloads: + HasChannelHandshakePayloads + HasClientStateType + HasErrorType +{ + async fn build_channel_open_try_payload( + &self, + client_state: &Self::ClientState, + height: &Self::Height, + port_id: &Self::PortId, + channel_id: &Self::ChannelId, + ) -> Result; + + async fn build_channel_open_ack_payload( + &self, + client_state: &Self::ClientState, + height: &Self::Height, + port_id: &Self::PortId, + channel_id: &Self::ChannelId, + ) -> Result; + + async fn build_channel_open_confirm_payload( + &self, + client_state: &Self::ClientState, + height: &Self::Height, + port_id: &Self::PortId, + channel_id: &Self::ChannelId, + ) -> Result; +} + +#[async_trait] +pub trait CanBuildChannelHandshakeMessages: + HasInitChannelOptionsType + HasErrorType +where + Counterparty: HasChannelHandshakePayloads, +{ + async fn build_channel_open_init_message( + &self, + port_id: &Self::PortId, + counterparty_port_id: &Counterparty::PortId, + init_channel_options: &Self::InitChannelOptions, + ) -> Result; + + async fn build_channel_open_try_message( + &self, + port_id: &Self::PortId, + counterparty_port_id: &Counterparty::PortId, + counterparty_channel_id: &Counterparty::ChannelId, + counterparty_payload: Counterparty::ChannelOpenTryPayload, + ) -> Result; + + async fn build_channel_open_ack_message( + &self, + port_id: &Self::PortId, + channel_id: &Self::ChannelId, + counterparty_channel_id: &Counterparty::ChannelId, + counterparty_payload: Counterparty::ChannelOpenAckPayload, + ) -> Result; + + async fn build_channel_open_confirm_message( + &self, + port_id: &Self::PortId, + channel_id: &Self::ChannelId, + counterparty_payload: Counterparty::ChannelOpenConfirmPayload, + ) -> Result; +} diff --git a/crates/relayer-components/src/chain/traits/message_builders/connection.rs b/crates/relayer-components/src/chain/traits/message_builders/connection.rs new file mode 100644 index 0000000000..703854735c --- /dev/null +++ b/crates/relayer-components/src/chain/traits/message_builders/connection.rs @@ -0,0 +1,78 @@ +use async_trait::async_trait; + +use crate::chain::traits::types::client_state::HasClientStateType; +use crate::chain::traits::types::connection::{ + HasConnectionHandshakePayloads, HasInitConnectionOptionsType, +}; +use crate::core::traits::error::HasErrorType; +use crate::std_prelude::*; + +#[async_trait] +pub trait CanBuildConnectionHandshakePayloads: + HasConnectionHandshakePayloads + HasClientStateType + HasErrorType +{ + async fn build_connection_open_init_payload( + &self, + client_state: &Self::ClientState, + ) -> Result; + + async fn build_connection_open_try_payload( + &self, + client_state: &Self::ClientState, + height: &Self::Height, + client_id: &Self::ClientId, + connection_id: &Self::ConnectionId, + ) -> Result; + + async fn build_connection_open_ack_payload( + &self, + client_state: &Self::ClientState, + height: &Self::Height, + client_id: &Self::ClientId, + connection_id: &Self::ConnectionId, + ) -> Result; + + async fn build_connection_open_confirm_payload( + &self, + client_state: &Self::ClientState, + height: &Self::Height, + client_id: &Self::ClientId, + connection_id: &Self::ConnectionId, + ) -> Result; +} + +#[async_trait] +pub trait CanBuildConnectionHandshakeMessages: + HasInitConnectionOptionsType + HasErrorType +where + Counterparty: HasConnectionHandshakePayloads, +{ + async fn build_connection_open_init_message( + &self, + client_id: &Self::ClientId, + counterparty_client_id: &Counterparty::ClientId, + init_connection_options: &Self::InitConnectionOptions, + counterparty_payload: Counterparty::ConnectionOpenInitPayload, + ) -> Result; + + async fn build_connection_open_try_message( + &self, + client_id: &Self::ClientId, + counterparty_client_id: &Counterparty::ClientId, + counterparty_connection_id: &Counterparty::ConnectionId, + counterparty_payload: Counterparty::ConnectionOpenTryPayload, + ) -> Result; + + async fn build_connection_open_ack_message( + &self, + connection_id: &Self::ConnectionId, + counterparty_connection_id: &Counterparty::ConnectionId, + counterparty_payload: Counterparty::ConnectionOpenAckPayload, + ) -> Result; + + async fn build_connection_open_confirm_message( + &self, + connection_id: &Self::ConnectionId, + counterparty_payload: Counterparty::ConnectionOpenConfirmPayload, + ) -> Result; +} diff --git a/crates/relayer-components/src/chain/traits/message_builders/create_client.rs b/crates/relayer-components/src/chain/traits/message_builders/create_client.rs new file mode 100644 index 0000000000..addb06bb1b --- /dev/null +++ b/crates/relayer-components/src/chain/traits/message_builders/create_client.rs @@ -0,0 +1,19 @@ +use async_trait::async_trait; + +use crate::chain::traits::types::client_state::HasClientStateType; +use crate::chain::traits::types::consensus_state::HasConsensusStateType; +use crate::chain::traits::types::ibc::HasIbcChainTypes; +use crate::chain::traits::types::message::HasMessageType; +use crate::core::traits::error::HasErrorType; +use crate::std_prelude::*; + +#[async_trait] +pub trait CanBuildCreateClientMessage: HasMessageType + HasErrorType +where + Counterparty: HasIbcChainTypes + HasClientStateType + HasConsensusStateType, +{ + async fn build_create_client_message( + client_state: &Counterparty::ClientState, + consensus_state: &Counterparty::ConsensusState, + ) -> Result; +} diff --git a/crates/relayer-components/src/chain/traits/message_builders/mod.rs b/crates/relayer-components/src/chain/traits/message_builders/mod.rs new file mode 100644 index 0000000000..0b475ee4cf --- /dev/null +++ b/crates/relayer-components/src/chain/traits/message_builders/mod.rs @@ -0,0 +1,6 @@ +pub mod ack_packet; +pub mod channel; +pub mod connection; +pub mod create_client; +pub mod receive_packet; +pub mod timeout_unordered_packet; diff --git a/crates/relayer-components/src/chain/traits/message_builders/receive_packet.rs b/crates/relayer-components/src/chain/traits/message_builders/receive_packet.rs new file mode 100644 index 0000000000..48baf08f55 --- /dev/null +++ b/crates/relayer-components/src/chain/traits/message_builders/receive_packet.rs @@ -0,0 +1,41 @@ +use async_trait::async_trait; + +use crate::chain::traits::types::client_state::HasClientStateType; +use crate::chain::traits::types::height::HasHeightType; +use crate::chain::traits::types::ibc::HasIbcChainTypes; +use crate::chain::traits::types::message::HasMessageType; +use crate::chain::traits::types::packet::HasIbcPacketTypes; +use crate::chain::traits::types::packets::receive::HasReceivePacketPayload; +use crate::core::traits::error::HasErrorType; +use crate::std_prelude::*; + +#[async_trait] +pub trait CanBuildReceivePacketPayload: + HasReceivePacketPayload + + HasIbcPacketTypes + + HasClientStateType + + HasHeightType + + HasErrorType +where + Counterparty: HasIbcChainTypes, +{ + async fn build_receive_packet_payload( + &self, + client_state: &Self::ClientState, + height: &Self::Height, + packet: &Self::OutgoingPacket, + ) -> Result; +} + +#[async_trait] +pub trait CanBuildReceivePacketMessage: + HasMessageType + HasErrorType + HasIbcPacketTypes +where + Counterparty: HasReceivePacketPayload, +{ + async fn build_receive_packet_message( + &self, + packet: &Self::IncomingPacket, + payload: Counterparty::ReceivePacketPayload, + ) -> Result; +} diff --git a/crates/relayer-components/src/chain/traits/message_builders/timeout_unordered_packet.rs b/crates/relayer-components/src/chain/traits/message_builders/timeout_unordered_packet.rs new file mode 100644 index 0000000000..77fe7ee013 --- /dev/null +++ b/crates/relayer-components/src/chain/traits/message_builders/timeout_unordered_packet.rs @@ -0,0 +1,41 @@ +use async_trait::async_trait; + +use crate::chain::traits::types::client_state::HasClientStateType; +use crate::chain::traits::types::height::HasHeightType; +use crate::chain::traits::types::ibc::HasIbcChainTypes; +use crate::chain::traits::types::message::HasMessageType; +use crate::chain::traits::types::packet::HasIbcPacketTypes; +use crate::chain::traits::types::packets::timeout::HasTimeoutUnorderedPacketPayload; +use crate::core::traits::error::HasErrorType; +use crate::std_prelude::*; + +#[async_trait] +pub trait CanBuildTimeoutUnorderedPacketPayload: + HasTimeoutUnorderedPacketPayload + + HasIbcPacketTypes + + HasClientStateType + + HasHeightType + + HasErrorType +where + Counterparty: HasIbcChainTypes, +{ + async fn build_timeout_unordered_packet_payload( + &self, + client_state: &Self::ClientState, + height: &Self::Height, + packet: &Self::IncomingPacket, + ) -> Result; +} + +#[async_trait] +pub trait CanBuildTimeoutUnorderedPacketMessage: + HasMessageType + HasErrorType + HasIbcPacketTypes +where + Counterparty: HasTimeoutUnorderedPacketPayload, +{ + async fn build_timeout_unordered_packet_message( + &self, + packet: &Self::OutgoingPacket, + payload: Counterparty::TimeoutUnorderedPacketPayload, + ) -> Result; +} diff --git a/crates/relayer-components/src/chain/traits/mod.rs b/crates/relayer-components/src/chain/traits/mod.rs new file mode 100644 index 0000000000..106e17d084 --- /dev/null +++ b/crates/relayer-components/src/chain/traits/mod.rs @@ -0,0 +1,8 @@ +pub mod client; +pub mod components; +pub mod event_subscription; +pub mod logs; +pub mod message_builders; +pub mod queries; +pub mod types; +pub mod wait; diff --git a/crates/relayer-components/src/chain/traits/queries/channel.rs b/crates/relayer-components/src/chain/traits/queries/channel.rs new file mode 100644 index 0000000000..4ddab97fc7 --- /dev/null +++ b/crates/relayer-components/src/chain/traits/queries/channel.rs @@ -0,0 +1,19 @@ +use async_trait::async_trait; + +use crate::chain::traits::types::chain::HasChainTypes; +use crate::chain::traits::types::ibc::HasIbcChainTypes; +use crate::core::traits::error::HasErrorType; +use crate::std_prelude::*; + +#[async_trait] +pub trait CanQueryCounterpartyChainIdFromChannel: + HasIbcChainTypes + HasErrorType +where + Counterparty: HasChainTypes, +{ + async fn query_chain_id_from_channel_id( + &self, + channel_id: &Self::ChannelId, + port_id: &Self::PortId, + ) -> Result; +} diff --git a/crates/relayer-components/src/chain/traits/queries/mod.rs b/crates/relayer-components/src/chain/traits/queries/mod.rs new file mode 100644 index 0000000000..b7e447b786 --- /dev/null +++ b/crates/relayer-components/src/chain/traits/queries/mod.rs @@ -0,0 +1,6 @@ +pub mod channel; +pub mod packet_commitments; +pub mod received_packet; +pub mod send_packet; +pub mod unreceived_packets; +pub mod write_ack; diff --git a/crates/relayer-components/src/chain/traits/queries/packet_commitments.rs b/crates/relayer-components/src/chain/traits/queries/packet_commitments.rs new file mode 100644 index 0000000000..3e17bccf1e --- /dev/null +++ b/crates/relayer-components/src/chain/traits/queries/packet_commitments.rs @@ -0,0 +1,22 @@ +use async_trait::async_trait; + +use crate::chain::traits::types::ibc::HasIbcChainTypes; +use crate::core::traits::error::HasErrorType; +use crate::std_prelude::*; + +#[async_trait] +pub trait CanQueryPacketCommitments: + HasIbcChainTypes + HasErrorType +where + Counterparty: HasIbcChainTypes, +{ + /// Query the sequences of the packets that the chain has committed to be + /// sent to the counterparty chain, of which the full packet relaying is not + /// yet completed. Once the chain receives the ack from the counterparty + /// chain, a given sequence should be removed from the packet commitment list. + async fn query_packet_commitments( + &self, + channel_id: &Self::ChannelId, + port_id: &Self::PortId, + ) -> Result<(Vec, Self::Height), Self::Error>; +} diff --git a/crates/relayer-components/src/chain/traits/queries/received_packet.rs b/crates/relayer-components/src/chain/traits/queries/received_packet.rs new file mode 100644 index 0000000000..fb0f0e8fb7 --- /dev/null +++ b/crates/relayer-components/src/chain/traits/queries/received_packet.rs @@ -0,0 +1,33 @@ +use async_trait::async_trait; + +use crate::chain::traits::types::ibc::HasIbcChainTypes; +use crate::core::traits::error::HasErrorType; +use crate::std_prelude::*; + +#[async_trait] +pub trait ReceivedPacketQuerier +where + Chain: HasIbcChainTypes + HasErrorType, + Counterparty: HasIbcChainTypes, +{ + async fn query_is_packet_received( + chain: &Chain, + port_id: &Chain::PortId, + channel_id: &Chain::ChannelId, + sequence: &Counterparty::Sequence, + ) -> Result; +} + +#[async_trait] +pub trait CanQueryReceivedPacket: + HasIbcChainTypes + HasErrorType +where + Counterparty: HasIbcChainTypes, +{ + async fn query_is_packet_received( + &self, + port_id: &Self::PortId, + channel_id: &Self::ChannelId, + sequence: &Counterparty::Sequence, + ) -> Result; +} diff --git a/crates/relayer-components/src/chain/traits/queries/send_packet.rs b/crates/relayer-components/src/chain/traits/queries/send_packet.rs new file mode 100644 index 0000000000..9d81c075d1 --- /dev/null +++ b/crates/relayer-components/src/chain/traits/queries/send_packet.rs @@ -0,0 +1,28 @@ +use async_trait::async_trait; + +use crate::chain::traits::types::ibc::HasIbcChainTypes; +use crate::chain::traits::types::packet::HasIbcPacketTypes; +use crate::core::traits::error::HasErrorType; +use crate::std_prelude::*; + +#[async_trait] +pub trait CanQuerySendPacketsFromSequences: + HasIbcChainTypes + HasIbcPacketTypes + HasErrorType +where + Counterparty: HasIbcChainTypes, +{ + /// Given a list of sequences, a channel and port will query a list of outgoing + /// packets which have not been relayed. + async fn query_send_packets_from_sequences( + &self, + channel_id: &Self::ChannelId, + port_id: &Self::PortId, + counterparty_channel_id: &Counterparty::ChannelId, + counterparty_port_id: &Counterparty::PortId, + sequences: &[Self::Sequence], + // The height is given to query the packets from a specific height. + // This height should be the same as the query height from the + // `CanQueryPacketCommitments` made on the same chain. + height: &Self::Height, + ) -> Result, Self::Error>; +} diff --git a/crates/relayer-components/src/chain/traits/queries/unreceived_packets.rs b/crates/relayer-components/src/chain/traits/queries/unreceived_packets.rs new file mode 100644 index 0000000000..56aa7044e3 --- /dev/null +++ b/crates/relayer-components/src/chain/traits/queries/unreceived_packets.rs @@ -0,0 +1,23 @@ +use async_trait::async_trait; + +use crate::chain::traits::types::ibc::HasIbcChainTypes; +use crate::chain::traits::types::packet::HasIbcPacketTypes; +use crate::core::traits::error::HasErrorType; +use crate::std_prelude::*; + +#[async_trait] +pub trait CanQueryUnreceivedPacketSequences: + HasIbcChainTypes + HasIbcPacketTypes + HasErrorType +where + Counterparty: HasIbcChainTypes, +{ + /// Given a list of counterparty commitment sequences, + /// return a filtered list of sequences which the chain + /// has not received the packet from the counterparty chain. + async fn query_unreceived_packet_sequences( + &self, + channel_id: &Self::ChannelId, + port_id: &Self::PortId, + sequences: &[Counterparty::Sequence], + ) -> Result, Self::Error>; +} diff --git a/crates/relayer-components/src/chain/traits/queries/write_ack.rs b/crates/relayer-components/src/chain/traits/queries/write_ack.rs new file mode 100644 index 0000000000..eae38a3e6d --- /dev/null +++ b/crates/relayer-components/src/chain/traits/queries/write_ack.rs @@ -0,0 +1,19 @@ +use async_trait::async_trait; + +use crate::chain::traits::types::ibc::HasIbcChainTypes; +use crate::chain::traits::types::ibc_events::write_ack::HasWriteAcknowledgementEvent; +use crate::chain::traits::types::packet::HasIbcPacketTypes; +use crate::core::traits::error::HasErrorType; +use crate::std_prelude::*; + +#[async_trait] +pub trait CanQueryWriteAcknowledgement: + HasWriteAcknowledgementEvent + HasIbcPacketTypes + HasErrorType +where + Counterparty: HasIbcChainTypes, +{ + async fn query_write_acknowledgement_event( + &self, + packet: &Self::IncomingPacket, + ) -> Result, Self::Error>; +} diff --git a/crates/relayer-components/src/chain/traits/types/chain.rs b/crates/relayer-components/src/chain/traits/types/chain.rs new file mode 100644 index 0000000000..00b8ecf0d2 --- /dev/null +++ b/crates/relayer-components/src/chain/traits/types/chain.rs @@ -0,0 +1,47 @@ +/*! + Trait definition for [`HasChainTypes`]. +*/ + +use crate::chain::traits::types::chain_id::HasChainIdType; +use crate::chain::traits::types::event::HasEventType; +use crate::chain::traits::types::height::HasHeightType; +use crate::chain::traits::types::message::HasMessageType; +use crate::chain::traits::types::timestamp::HasTimestampType; + +/** + This covers the minimal abstract types that are used inside a chain context. + + A chain context have the following abstract types: + + - [`Height`](HasHeightType::Height) - the height of a chain, which should + behave like natural numbers. + + - [`Timestamp`](HasTimestampType::Timestamp) - the timestamp of a chain, which should + increment monotonically. + + - [`Message`](HasMessageType::Message) - the messages being submitted + to a chain. + + - [`Event`](HasEventType::Event) - the events that are emitted after + a transaction is committed to a chain. + + This trait only covers chain types that involve a single chain. For IBC + chain types that involve _two_ chains, the abstract types are defined + in [`HasIbcChainTypes`](super::ibc::HasIbcChainTypes). + + Notice that a chain context do not contain a `Transaction` abstract + type. This is because we separate the concerns of normal chain operations + from the special concerns of assembling chain messages into transactions + and broadcasting it to the blockchain. See the + [`transaction`](crate::transaction) module for more information + about the transaction context. +*/ +pub trait HasChainTypes: + HasHeightType + HasMessageType + HasEventType + HasChainIdType + HasTimestampType +{ +} + +impl HasChainTypes for Chain where + Chain: HasHeightType + HasMessageType + HasEventType + HasChainIdType + HasTimestampType +{ +} diff --git a/crates/relayer-components/src/chain/traits/types/chain_id.rs b/crates/relayer-components/src/chain/traits/types/chain_id.rs new file mode 100644 index 0000000000..cae87d4837 --- /dev/null +++ b/crates/relayer-components/src/chain/traits/types/chain_id.rs @@ -0,0 +1,36 @@ +/*! + Trait definitions for [`HasChainIdType`] and [`HasChainId`]. +*/ + +use core::fmt::Display; + +use crate::core::traits::sync::Async; + +/** + This is implemented by a chain context to provide a + [`ChainId`](Self::ChainId) type that should uniquely identify the chain. + + The relay context uses this information to identify whether an IBC packet + corresponds to a given chain, based on the chain ID information that is + queried from a channel ID. +*/ +pub trait HasChainIdType: Async { + /** + The ID of a chain, which should implement [`Eq`] to differentiate chain + ID of two chains with the same type. + */ + type ChainId: Eq + Display + Async; +} + +/** + This implements the accessor method to get a chain context's + [chain ID](HasChainIdType::ChainId). +*/ +pub trait HasChainId: HasChainIdType { + /** + Get the ID of a chain context. A chain context is expected to always + return the same ID. In case there is a chain upgrade, a new chain + context should be created with the new chain ID. + */ + fn chain_id(&self) -> &Self::ChainId; +} diff --git a/crates/relayer-components/src/chain/traits/types/channel.rs b/crates/relayer-components/src/chain/traits/types/channel.rs new file mode 100644 index 0000000000..e20c09098d --- /dev/null +++ b/crates/relayer-components/src/chain/traits/types/channel.rs @@ -0,0 +1,18 @@ +use crate::chain::traits::types::ibc::HasIbcChainTypes; +use crate::core::traits::sync::Async; + +pub trait HasInitChannelOptionsType: HasIbcChainTypes { + type InitChannelOptions: Async; +} + +/** + Payload that contains necessary counterparty information such as proofs and parameters + in order for a self chain to build a channel handshake message. +*/ +pub trait HasChannelHandshakePayloads: HasIbcChainTypes { + type ChannelOpenTryPayload: Async; + + type ChannelOpenAckPayload: Async; + + type ChannelOpenConfirmPayload: Async; +} diff --git a/crates/relayer-components/src/chain/traits/types/client_state.rs b/crates/relayer-components/src/chain/traits/types/client_state.rs new file mode 100644 index 0000000000..eeb8e300f5 --- /dev/null +++ b/crates/relayer-components/src/chain/traits/types/client_state.rs @@ -0,0 +1,13 @@ +use crate::chain::traits::types::ibc::HasIbcChainTypes; +use crate::core::traits::sync::Async; + +pub trait HasClientStateType: HasIbcChainTypes { + /** + The client state of the `Self` chain's client on the `Counterparty` chain + */ + type ClientState: Async; +} + +pub trait HasClientStateFields: HasClientStateType { + fn client_state_latest_height(client_state: &Self::ClientState) -> &Self::Height; +} diff --git a/crates/relayer-components/src/chain/traits/types/connection.rs b/crates/relayer-components/src/chain/traits/types/connection.rs new file mode 100644 index 0000000000..63653ead8b --- /dev/null +++ b/crates/relayer-components/src/chain/traits/types/connection.rs @@ -0,0 +1,32 @@ +use crate::chain::traits::types::ibc::HasIbcChainTypes; +use crate::core::traits::sync::Async; + +pub enum ConnectionBaseState { + Init, + TryOpen, + Open, +} + +pub trait HasConnectionStateType: HasIbcChainTypes { + type ConnectionState: Async; + + fn connection_base_state(state: &Self::ConnectionState) -> Option; +} + +pub trait HasInitConnectionOptionsType: HasIbcChainTypes { + type InitConnectionOptions: Async; +} + +/** + Payload that contains necessary counterparty information such as proofs and parameters + in order for a self chain to build a connection handshake message. +*/ +pub trait HasConnectionHandshakePayloads: HasIbcChainTypes { + type ConnectionOpenInitPayload: Async; + + type ConnectionOpenTryPayload: Async; + + type ConnectionOpenAckPayload: Async; + + type ConnectionOpenConfirmPayload: Async; +} diff --git a/crates/relayer-components/src/chain/traits/types/consensus_state.rs b/crates/relayer-components/src/chain/traits/types/consensus_state.rs new file mode 100644 index 0000000000..d6b3017bc3 --- /dev/null +++ b/crates/relayer-components/src/chain/traits/types/consensus_state.rs @@ -0,0 +1,8 @@ +use crate::core::traits::sync::Async; + +pub trait HasConsensusStateType: Async { + /** + The consensus state of the `Self` chain's client on the `Counterparty` chain + */ + type ConsensusState: Async; +} diff --git a/crates/relayer-components/src/chain/traits/types/event.rs b/crates/relayer-components/src/chain/traits/types/event.rs new file mode 100644 index 0000000000..cad1a5c4a8 --- /dev/null +++ b/crates/relayer-components/src/chain/traits/types/event.rs @@ -0,0 +1,45 @@ +/*! + Trait definition for [`HasEventType`]. +*/ + +use crate::core::traits::sync::Async; + +/** + This is used for the chain context and the transaction context to declare + that they have a unique `Self::Event` type, which corresponds to the + events that are emitted from a transaction being committed to a chain. + + We define this as a separate trait so that we can use it in both a chain + context and also a transaction context. Note that because a concrete context + may implement both chain and transaction context at the same time, + we want to avoid defining multiple associated `Event` types so that + they can never be ambiguous. +*/ +pub trait HasEventType: Async { + /** + The events that are emitted from a transaction being committed to a + blockchain. + + The event type can be either dynamic typed, like `AbciEvent`, or static + typed, like `IbcEvent`. The abstract type here do not inform of which + specific variants an event may have. This is because the on-wire event + format is essentially dynamic, and it is up to chain implementations to + decide which events to emit and which formats the emitted events should + have. + + In order to make the relayer framework general enough to support + non-Cosmos chains, we also cannot make assumption on what events a chain + may emit. By keeping the event type abstract, we can allow non-Cosmos + chains to define custom components that process the events in different + ways, and still allow them to reuse the other components in the relayer + framework that do not interact with the event variants. + + Using dependency injection, we can impose additional constraints on what + properties the `Event` type should have at the use site. An example use + of this is the [`HasIbcEvent`](crate::chain::traits::types::ibc_events::write_ack::HasWriteAcknowledgementEvent) + trait, which contains the IBC event variant types like + [`WriteAcknowledgementEvent`](crate::chain::traits::types::ibc_events::write_ack::HasWriteAcknowledgementEvent::WriteAcknowledgementEvent), + and _extraction_ methods to parse the variant information from the event. + */ + type Event: Async; +} diff --git a/crates/relayer-components/src/chain/traits/types/height.rs b/crates/relayer-components/src/chain/traits/types/height.rs new file mode 100644 index 0000000000..ff93e2655f --- /dev/null +++ b/crates/relayer-components/src/chain/traits/types/height.rs @@ -0,0 +1,30 @@ +/*! + Trait definition for [`HasHeightType`]. +*/ + +use core::fmt::Display; + +use crate::core::traits::error::HasErrorType; +use crate::core::traits::sync::Async; + +pub trait HasHeightType: Async { + /** + The height of the chain, which should behave like natural numbers. + + By default, the height only contains the `Ord` constraint, and does + not support operations like addition. + + We can impose additional constraints at the use site of `HasChainTypes`. + However doing so may impose limitations on which concrete types + the `Height` type can be. + + By keeping the abstract type minimal, we can for example use + `u8` or `u128` as the `Height` type during testing, and use the + more complex Cosmos height type during production. + */ + type Height: Ord + Display + Async; +} + +pub trait CanIncrementHeight: HasHeightType + HasErrorType { + fn increment_height(height: &Self::Height) -> Result; +} diff --git a/crates/relayer-components/src/chain/traits/types/ibc.rs b/crates/relayer-components/src/chain/traits/types/ibc.rs new file mode 100644 index 0000000000..809ecc4873 --- /dev/null +++ b/crates/relayer-components/src/chain/traits/types/ibc.rs @@ -0,0 +1,114 @@ +/*! + Trait definitions for [`HasIbcChainTypes`] and + [`HasCounterpartyMessageHeight`]. +*/ + +use core::fmt::Display; + +use crate::chain::traits::types::chain::HasChainTypes; +use crate::chain::traits::types::height::HasHeightType; +use crate::chain::traits::types::message::HasMessageType; +use crate::core::traits::sync::Async; +use crate::std_prelude::*; + +/** + The abstract types for a chain context when it is used for IBC + communication with a `Counterparty` chain context. + + In contrast to [`HasChainTypes`], this trait is parameterized by a + `Counterparty` chain context. Additionally, the `Counterparty` chain context + is arequired to implement + [`HasChainTypes`]. + + Because of the `Counterparty` parameter, the associated types + in this trait are going to be different when used with different + counterparty chain contexts. In other words, the type + `>::ClientId` is different from + `>::ClientId` if `ChainB` and `ChainC` + are different. + + This is intentional, as we want to distinguish IBC identifiers associated + with different chains and avoid accidentally mixing them up. This is + particularly useful when implementing the relayer, because we cannot + for example accidentally use a `ChannelId` from `SrcChain` to `DstChain` + as a `ChannelId` from `DstChain` to `SrcChain`. + + Having the IBC chain types parameterized on the counterparty chain also + allows a chain context to decide on different concrete types depending + on which counterparty chain it is. For example, a Cosmos chain context + connected with a non-Cosmos chain context may want to use different + `ClientId` type, as compared to connecting to a Cosmos chain. + + Note that even when a chain context implements `HasIbcChainTypes`, it is + _not_ expected to have access to resources on the counterparty chain. That + would require access to the counterparty chain context, which is implemented + separately from the self chain context. Instead, operations that require + access to two chain contexts are handled by the + [relay context](crate::relay). +*/ +pub trait HasIbcChainTypes: HasChainTypes { + /** + The client ID of the counterparty chain, that is stored on the self + chain. + */ + type ClientId: Display + Async; + + /** + The connection ID of the counterparty chain, that is stored on the self + chain. + */ + type ConnectionId: Display + Async; + + /** + The channel ID of the counterparty chain, that is stored on the self + chain. + */ + type ChannelId: Display + Async; + + /** + The port ID of the counterparty chain, that is stored on the self + chain. + */ + type PortId: Display + Async; + + /** + The IBC packet sequence for the packet that is sent from the self chain + to the counterparty chain. + + Note that for sequences of packets that are sent from the counterparty + chain to self, the `Counterparty::Sequence` will be used. + */ + type Sequence: Display + Async; +} + +pub trait HasCounterpartyMessageHeight: HasMessageType +where + Counterparty: HasHeightType, +{ + /** + Get the height of the counterparty chain which the UpdateClient message + should be built. If the message is not IBC-related, this would return `None`. + + This is used by the + [`SendIbcMessagesWithUpdateClient`](crate::relay::impls::message_senders::update_client::SendIbcMessagesWithUpdateClient) + message sender middleware to attach `UpdateClient` messages to the + front of the message batch before sending it to the downstream + message sender. + + The way this works is as follows: recall that the relayer relays IBC + packets by constructing messages from one chain and send it to + the other chain. In this case, we have IBC events happening on + the `Counterparty` chain, which the relayer would construct + messages targetting this self chain. So any IBC message that the self + chain received would correspond to events happening on the `Counterparty` + chain. With this method, we are thus getting the + [`Counterparty::Height`](crate::chain::traits::types::height::HasHeightType::Height) + and _not_ `Self::Height`. + + Note that the message height for UpdateClient is usually an increment + of the height which the proofs are built. + */ + fn counterparty_message_height_for_update_client( + message: &Self::Message, + ) -> Option; +} diff --git a/crates/relayer-components/src/chain/traits/types/ibc_events/channel.rs b/crates/relayer-components/src/chain/traits/types/ibc_events/channel.rs new file mode 100644 index 0000000000..9790b40d9d --- /dev/null +++ b/crates/relayer-components/src/chain/traits/types/ibc_events/channel.rs @@ -0,0 +1,26 @@ +use crate::chain::traits::types::ibc::HasIbcChainTypes; +use crate::core::traits::sync::Async; + +pub trait HasChannelOpenInitEvent: HasIbcChainTypes +where + Counterparty: HasIbcChainTypes, +{ + type ChannelOpenInitEvent: Async; + + fn try_extract_channel_open_init_event( + event: Self::Event, + ) -> Option; + + fn channel_open_init_event_channel_id(event: &Self::ChannelOpenInitEvent) -> &Self::ChannelId; +} + +pub trait HasChannelOpenTryEvent: HasIbcChainTypes +where + Counterparty: HasIbcChainTypes, +{ + type ChannelOpenTryEvent: Async; + + fn try_extract_channel_open_try_event(event: Self::Event) -> Option; + + fn channel_open_try_event_channel_id(event: &Self::ChannelOpenTryEvent) -> &Self::ChannelId; +} diff --git a/crates/relayer-components/src/chain/traits/types/ibc_events/connection.rs b/crates/relayer-components/src/chain/traits/types/ibc_events/connection.rs new file mode 100644 index 0000000000..d57c94effa --- /dev/null +++ b/crates/relayer-components/src/chain/traits/types/ibc_events/connection.rs @@ -0,0 +1,32 @@ +use crate::chain::traits::types::ibc::HasIbcChainTypes; +use crate::core::traits::sync::Async; + +pub trait HasConnectionOpenTryEvent: HasIbcChainTypes +where + Counterparty: HasIbcChainTypes, +{ + type ConnectionOpenTryEvent: Async; + + fn try_extract_connection_open_try_event( + event: Self::Event, + ) -> Option; + + fn connection_open_try_event_connection_id( + event: &Self::ConnectionOpenTryEvent, + ) -> &Self::ConnectionId; +} + +pub trait HasConnectionOpenInitEvent: HasIbcChainTypes +where + Counterparty: HasIbcChainTypes, +{ + type ConnectionOpenInitEvent: Async; + + fn try_extract_connection_open_init_event( + event: Self::Event, + ) -> Option; + + fn connection_open_init_event_connection_id( + event: &Self::ConnectionOpenInitEvent, + ) -> &Self::ConnectionId; +} diff --git a/crates/relayer-components/src/chain/traits/types/ibc_events/mod.rs b/crates/relayer-components/src/chain/traits/types/ibc_events/mod.rs new file mode 100644 index 0000000000..19e735e53a --- /dev/null +++ b/crates/relayer-components/src/chain/traits/types/ibc_events/mod.rs @@ -0,0 +1,10 @@ +/*! + Trait definitions for IBC event types such as + [`HasSendPacketEvent`](send_packet::HasSendPacketEvent) and + [`HasWriteAcknowledgementEvent`](write_ack::HasWriteAcknowledgementEvent). +*/ + +pub mod channel; +pub mod connection; +pub mod send_packet; +pub mod write_ack; diff --git a/crates/relayer-components/src/chain/traits/types/ibc_events/send_packet.rs b/crates/relayer-components/src/chain/traits/types/ibc_events/send_packet.rs new file mode 100644 index 0000000000..212b065a61 --- /dev/null +++ b/crates/relayer-components/src/chain/traits/types/ibc_events/send_packet.rs @@ -0,0 +1,25 @@ +/*! + Trait definitions for [`HasSendPacketEvent`]. +*/ + +use crate::chain::traits::types::event::HasEventType; +use crate::chain::traits::types::ibc::HasIbcChainTypes; +use crate::chain::traits::types::packet::HasIbcPacketTypes; +use crate::core::traits::sync::Async; + +/** + Indicates that a chain context's + [`Event`](crate::chain::traits::types::event::HasEventType::Event) + type contains a [`SendPacketEvent`](Self::SendPacketEvent) variant. +*/ +pub trait HasSendPacketEvent: HasIbcPacketTypes + HasEventType +where + Counterparty: HasIbcChainTypes, +{ + type SendPacketEvent: Async; + + fn try_extract_send_packet_event(event: &Self::Event) -> Option; + + fn extract_packet_from_send_packet_event(event: &Self::SendPacketEvent) + -> Self::OutgoingPacket; +} diff --git a/crates/relayer-components/src/chain/traits/types/ibc_events/write_ack.rs b/crates/relayer-components/src/chain/traits/types/ibc_events/write_ack.rs new file mode 100644 index 0000000000..b58092a7de --- /dev/null +++ b/crates/relayer-components/src/chain/traits/types/ibc_events/write_ack.rs @@ -0,0 +1,70 @@ +/*! + Trait definitions for [`HasWriteAcknowledgementEvent`]. +*/ + +use crate::chain::traits::types::event::HasEventType; +use crate::chain::traits::types::ibc::HasIbcChainTypes; +use crate::chain::traits::types::packet::HasIbcPacketTypes; +use crate::core::traits::sync::Async; + +/** + Indicates that a chain context's + [`Event`](crate::chain::traits::types::event::HasEventType::Event) + type contains a [`WriteAcknowledgementEvent`](Self::WriteAcknowledgementEvent) variant. +*/ +pub trait HasWriteAcknowledgementEvent: HasEventType { + /** + The write acknowledgement event that is emitted when a `RecvPacket` + message is committed to a chain. + + At the moment, there is no need for the relayer framework to know + further information about the write acknowledgement event, other + than passing it down to the concrete context to build the `Ack` + message. + + If new components have the need to extract information out of + the write acknowledgement event, such as the ack payload, + we can add new methods to this trait to do the extraction. + */ + type WriteAcknowledgementEvent: Async; + + /** + Try to extract an abstract + [`Event`](crate::chain::traits::types::event::HasEventType::Event) + type into a + [`WriteAcknowledgementEvent`](Self::WriteAcknowledgementEvent). + If the extraction fails, return `None`. + + Since an event type may contain many variants, it is not guaranteed + that the event extraction would be successful. If the concrete + `Event` is dynamic-typed, then the extraction may also fail due to + parse errors. + */ + fn try_extract_write_acknowledgement_event( + event: &Self::Event, + ) -> Option; +} + +pub trait CanBuildPacketFromWriteAckEvent: + HasWriteAcknowledgementEvent + HasIbcPacketTypes +where + Counterparty: HasIbcChainTypes, +{ + /** + Extract the [`IncomingPacket`](HasIbcPacketTypes::IncomingPacket) + from a write acknowledgement event. + + Since write acknowledgements are emitted from a destination chain (self), + it is necessary for the event to correspond to an incoming packet + (with self being the destination). + + Here we assume that a write acknowledgement event always contains + the packet data. This is currently true for Cosmos chains. However + in case additional queries are required, then this method should be + refactored into a method like + `query_packet_from_write_acknowledgement_event`. + */ + fn build_packet_from_write_acknowledgement_event( + ack: &Self::WriteAcknowledgementEvent, + ) -> &Self::IncomingPacket; +} diff --git a/crates/relayer-components/src/chain/traits/types/message.rs b/crates/relayer-components/src/chain/traits/types/message.rs new file mode 100644 index 0000000000..4cfd1a761b --- /dev/null +++ b/crates/relayer-components/src/chain/traits/types/message.rs @@ -0,0 +1,74 @@ +/*! + Trait definitions for [`HasMessageType`] and [`CanEstimateMessageSize`]. +*/ + +use crate::core::traits::error::HasErrorType; +use crate::core::traits::sync::Async; +use crate::std_prelude::*; + +/** + This is used for the chain context and the transaction context to declare + that they have a unique `Self::Message` type, which corresponds to messages + that are submitted to a chain inside a transaction. + + We define this as a separate trait so that we can use it in both a chain + context and also a transaction context. Note that because a concrete context + may implement both chain and transaction context at the same time, + we want to avoid defining multiple associated `Message` types so that + they can never be ambiguous. +*/ +pub trait HasMessageType: Async { + /** + The messages that can be assembled into transactions and be submitted to + a blockchain. + + The message type can be either dynamic typed, like `Any`, or static typed, + like `Ics26Envelope`. Either way, it is treated as an opaque type by the + relayer framework, so that this can be used for sending messages to + non-Cosmos chains as well. It is worth noting that depending on the + concrete chain, it may be _not necessary_ to support protobufs for the + `Message` type. + + Unlike the current message type in the original relayer, if the `Message` + type is used in a transaction context, it is _required_ + that the `Message` type here to support _late binding_ of the signer field. + In other words, the chain context is required to be able to construct + messages without providing a signer, and then only provide a signer when + assembling the messages into transactions. + + The late binding of the signer field is necessary to make it possible + for the relayer framework to multiplex the submission of transactions + using multiple wallets. Depending on the number of messages being sent + at a given time frame, a message may be assigned with different signers + when being assembled into transactions. + + The relayer framework delegates the _construction_ of messages to + specialized traits such as + [`CanBuildUpdateClientMessage`](crate::relay::traits::messages::update_client::CanBuildUpdateClientMessage). + Because the construction of messages typically also requires querying + from the chain, the relayer framework lets the concrete chain contexts + to perform both the querying operations and message construction + operations at once. As a result, there is rarely a need for the relayer + framework to know about specific message variants, such as + `UpdateCientMesssage`. + */ + type Message: Async; +} + +pub trait CanEstimateMessageSize: HasMessageType + HasErrorType { + /** + Estimate the size of a message after it is encoded into raw bytes + inside a transaction. + + Because the signer field of a message is late-bound, it may not + be possible to get a precise size if the signer field can have + dynamic length. For the purpose of length estimation, the concrete + context may replace the message's signer field with a dummy signer + value, so that it can be encoded into raw bytes. + + This is mainly used by the `BatchMessageWorker` to estimate the + the message size when batching messages. We may consider moving + this method into a separate crate if it is not being used elsewhere. + */ + fn estimate_message_size(message: &Self::Message) -> Result; +} diff --git a/crates/relayer-components/src/chain/traits/types/mod.rs b/crates/relayer-components/src/chain/traits/types/mod.rs new file mode 100644 index 0000000000..52f8ff6482 --- /dev/null +++ b/crates/relayer-components/src/chain/traits/types/mod.rs @@ -0,0 +1,22 @@ +/*! + The traits containing the core abstract types for the chain context. + + A chain context is expected to implement at minimum the traits that + are defined in this module. +*/ + +pub mod chain; +pub mod chain_id; +pub mod channel; +pub mod client_state; +pub mod connection; +pub mod consensus_state; +pub mod event; +pub mod height; +pub mod ibc; +pub mod ibc_events; +pub mod message; +pub mod packet; +pub mod packets; +pub mod status; +pub mod timestamp; diff --git a/crates/relayer-components/src/chain/traits/types/packet.rs b/crates/relayer-components/src/chain/traits/types/packet.rs new file mode 100644 index 0000000000..a3766cde2f --- /dev/null +++ b/crates/relayer-components/src/chain/traits/types/packet.rs @@ -0,0 +1,53 @@ +/*! + Trait definition for [`HasIbcPacketTypes`]. +*/ + +use crate::core::traits::sync::Async; + +/** + Contains the abstract packet types for a chain context to send and receive + IBC packets from a counterparty chain. + + To enable IBC communication, a chain context needs to have _two_ packet + types: [`IncomingPacket`](Self::IncomingPacket) for receiving packets + sent from a counterparty chain to the self (given) chain, and + [`OutgoingPacket`](Self::OutgoingPacket) for sending packets from the self + chain to the counterparty chain. + + The two packet types are distinct, because the fields of the packets are + different. For example, the + [source channel ID](Self::incoming_packet_src_channel_id) + of an incoming packet is the counterparty chain's channel ID, but the + source channel ID of an outgoing packet is the self chain's channel ID. + For each field in a packet, there is one accessor method for the incoming + packet, and one for the outgoing packet. + + The trait [`HasIbcPacketTypes`] also requires the `Counterparty` chain + context to implement [`HasIbcPacketTypes`], with `Self` being the + counterparty of `Counterparty`. Additionally, there is a constraint that + the `Counterparty`'s [`IncomingPacket`](Self::IncomingPacket) is the same + as `Self`'s [`OutgoingPacket`](Self::OutgoingPacket), and that + the `Counterparty`'s [`OutgoingPacket`](Self::OutgoingPacket) is the same + as `Self`'s [`IncomingPacket`](Self::IncomingPacket). This ensures that + there is no type mismatch when a chain's + [`OutgoingPacket`](Self::OutgoingPacket) is sent to a counterparty chain, + and when a chain receives [`IncomingPacket`](Self::IncomingPacket) that is + coming from a counterparty chain. +*/ +pub trait HasIbcPacketTypes: Async { + /** + A packet sent from counterparty to self. + + - Packet source: `Counterparty` + - Packet destination: `Self` + */ + type IncomingPacket: Async; + + /** + A packet sent from self to counterparty. + + - Packet source: `Self` + - Packet destination: `Counterparty` + */ + type OutgoingPacket: Async; +} diff --git a/crates/relayer-components/src/chain/traits/types/packets/ack.rs b/crates/relayer-components/src/chain/traits/types/packets/ack.rs new file mode 100644 index 0000000000..e82209b9fb --- /dev/null +++ b/crates/relayer-components/src/chain/traits/types/packets/ack.rs @@ -0,0 +1,5 @@ +use crate::core::traits::sync::Async; + +pub trait HasAckPacketPayload: Async { + type AckPacketPayload: Async; +} diff --git a/crates/relayer-components/src/chain/traits/types/packets/mod.rs b/crates/relayer-components/src/chain/traits/types/packets/mod.rs new file mode 100644 index 0000000000..237d2334f0 --- /dev/null +++ b/crates/relayer-components/src/chain/traits/types/packets/mod.rs @@ -0,0 +1,3 @@ +pub mod ack; +pub mod receive; +pub mod timeout; diff --git a/crates/relayer-components/src/chain/traits/types/packets/receive.rs b/crates/relayer-components/src/chain/traits/types/packets/receive.rs new file mode 100644 index 0000000000..f2fc8d0888 --- /dev/null +++ b/crates/relayer-components/src/chain/traits/types/packets/receive.rs @@ -0,0 +1,5 @@ +use crate::core::traits::sync::Async; + +pub trait HasReceivePacketPayload: Async { + type ReceivePacketPayload: Async; +} diff --git a/crates/relayer-components/src/chain/traits/types/packets/timeout.rs b/crates/relayer-components/src/chain/traits/types/packets/timeout.rs new file mode 100644 index 0000000000..1e5a031123 --- /dev/null +++ b/crates/relayer-components/src/chain/traits/types/packets/timeout.rs @@ -0,0 +1,5 @@ +use crate::core::traits::sync::Async; + +pub trait HasTimeoutUnorderedPacketPayload: Async { + type TimeoutUnorderedPacketPayload: Async; +} diff --git a/crates/relayer-components/src/chain/traits/types/status.rs b/crates/relayer-components/src/chain/traits/types/status.rs new file mode 100644 index 0000000000..b04f6e589c --- /dev/null +++ b/crates/relayer-components/src/chain/traits/types/status.rs @@ -0,0 +1,46 @@ +use crate::chain::traits::types::chain::HasChainTypes; +use crate::core::traits::sync::Async; + +/** + A chain context that offers a [`ChainStatus`](Self::ChainStatus) type to + contain information about the current status of the chain. + + The `ChainStatus` type contains at minimal a + [`Height`](crate::chain::traits::types::height::HasHeightType::Height) + field and a + [`Timestamp`](crate::chain::traits::types::timestamp::HasTimestampType::Timestamp) + field, which are accessible + via the accessor methods [`chain_status_height`](Self::chain_status_height) + and [`chain_status_timestamp`](Self::chain_status_timestamp). + + Using context-generic programming, the chain context may also expose + additional fields to the chain status by introducing additional traits + containing accessor methods. For example, one may define a + `HasChainHealthStatus` trait to access the health status information + from a given chain status. + + The extensible nature of the abstract [`ChainStatus`](Self::ChainStatus) + type allows the implementation of a caching layer in the future, so that + chain status queries can be cached without needing to know what information + is contained inside the chain status. +*/ +pub trait HasChainStatusType: HasChainTypes { + /** + Contains information about the current status of the blockchain. + */ + type ChainStatus: Async; + + /** + Get the blockchain's current + [height](crate::chain::traits::types::height::HasHeightType::Height) + from the chain status result. + */ + fn chain_status_height(status: &Self::ChainStatus) -> &Self::Height; + + /** + Get the blockchain's current + [timestamp](crate::chain::traits::types::timestamp::HasTimestampType::Timestamp) + from the chain status result. + */ + fn chain_status_timestamp(status: &Self::ChainStatus) -> &Self::Timestamp; +} diff --git a/crates/relayer-components/src/chain/traits/types/timestamp.rs b/crates/relayer-components/src/chain/traits/types/timestamp.rs new file mode 100644 index 0000000000..b55097c3ba --- /dev/null +++ b/crates/relayer-components/src/chain/traits/types/timestamp.rs @@ -0,0 +1,33 @@ +/*! + Trait definition for [`HasTimestampType`]. +*/ + +use core::fmt::Display; + +use crate::core::traits::sync::Async; + +pub trait HasTimestampType: Async { + /** + The timestamp of a chain, which should increment monotonically. + + By default, the timestamp only contains the `Ord` constraint, and does + not support operations like adding to a `Duration`. + + We can impose additional constraints at the use site of `HasChainTypes`. + However doing so may impose limitations on which concrete types + the `Timestamp` type can be. + + By keeping the abstract type minimal, we can for example use + simple `u8` or `u128` in seconds as the `Timestamp` type during testing, + and use the more complex types like `DateTime` type during production. + + This especially helps given that having a canonical time type is + still largely an unsolved problem in software engineering. Depending + on the specific use cases, different concrete contexts may want to + use different date time types to enforce certain invariants. + By keeping this type abstract, we provide the flexibility to + concrete context implementers to decide which exact time type + they would like to use. + */ + type Timestamp: Ord + Display + Async; +} diff --git a/crates/relayer-components/src/chain/traits/wait.rs b/crates/relayer-components/src/chain/traits/wait.rs new file mode 100644 index 0000000000..fc3b97cc90 --- /dev/null +++ b/crates/relayer-components/src/chain/traits/wait.rs @@ -0,0 +1,41 @@ +use core::time::Duration; + +use async_trait::async_trait; + +use crate::chain::traits::components::chain_status_querier::CanQueryChainHeight; +use crate::chain::traits::types::height::HasHeightType; +use crate::core::traits::error::HasErrorType; +use crate::runtime::traits::runtime::HasRuntime; +use crate::runtime::traits::sleep::CanSleep; +use crate::std_prelude::*; + +#[async_trait] +pub trait CanWaitChainReachHeight: HasHeightType + HasErrorType { + async fn wait_chain_reach_height( + &self, + height: &Self::Height, + ) -> Result; +} + +#[async_trait] +impl CanWaitChainReachHeight for Chain +where + Chain: CanQueryChainHeight + HasRuntime, + Chain::Runtime: CanSleep, + Chain::Height: Clone, +{ + async fn wait_chain_reach_height( + &self, + height: &Chain::Height, + ) -> Result { + loop { + let current_height = self.query_chain_height().await?; + + if ¤t_height >= height { + return Ok(current_height.clone()); + } else { + self.runtime().sleep(Duration::from_millis(100)).await; + } + } + } +} diff --git a/crates/relayer-components/src/chain/types/aliases.rs b/crates/relayer-components/src/chain/types/aliases.rs new file mode 100644 index 0000000000..e03e580de6 --- /dev/null +++ b/crates/relayer-components/src/chain/types/aliases.rs @@ -0,0 +1,50 @@ +use alloc::sync::Arc; +use core::pin::Pin; + +use futures_core::stream::Stream; + +use crate::chain::traits::types::chain_id::HasChainIdType; +use crate::chain::traits::types::event::HasEventType; +use crate::chain::traits::types::height::HasHeightType; +use crate::chain::traits::types::ibc::HasIbcChainTypes; +use crate::chain::traits::types::ibc_events::write_ack::HasWriteAcknowledgementEvent; +use crate::chain::traits::types::message::HasMessageType; +use crate::chain::traits::types::packet::HasIbcPacketTypes; +use crate::chain::traits::types::timestamp::HasTimestampType; +use crate::runtime::traits::subscription::Subscription; +use crate::std_prelude::*; + +pub type IncomingPacket = + >::IncomingPacket; + +pub type OutgoingPacket = + >::OutgoingPacket; + +pub type ClientId = >::ClientId; + +pub type ConnectionId = + >::ConnectionId; + +pub type ChannelId = >::ChannelId; + +pub type PortId = >::PortId; + +pub type Sequence = >::Sequence; + +pub type ChainId = ::ChainId; + +pub type Message = ::Message; + +pub type Event = ::Event; + +pub type Height = ::Height; + +pub type Timestamp = ::Timestamp; + +pub type WriteAcknowledgementEvent = + >::WriteAcknowledgementEvent; + +pub type EventStream = + Pin, Event)>> + Send + 'static>>; + +pub type EventSubscription = Arc, Event)>>; diff --git a/crates/relayer-components/src/chain/types/mod.rs b/crates/relayer-components/src/chain/types/mod.rs new file mode 100644 index 0000000000..d293a01b6f --- /dev/null +++ b/crates/relayer-components/src/chain/types/mod.rs @@ -0,0 +1 @@ +pub mod aliases; diff --git a/crates/relayer-components/src/components/default/birelay.rs b/crates/relayer-components/src/components/default/birelay.rs new file mode 100644 index 0000000000..9a8dc83c01 --- /dev/null +++ b/crates/relayer-components/src/components/default/birelay.rs @@ -0,0 +1,11 @@ +use core::marker::PhantomData; + +use crate::relay::components::auto_relayers::concurrent_two_way::ConcurrentTwoWayAutoRelay; +use crate::relay::traits::components::auto_relayer::AutoRelayerComponent; +pub struct DefaultBiRelayComponents(pub PhantomData); + +crate::delegate_component!( + AutoRelayerComponent, + DefaultBiRelayComponents, + ConcurrentTwoWayAutoRelay, +); diff --git a/crates/relayer-components/src/components/default/build.rs b/crates/relayer-components/src/components/default/build.rs new file mode 100644 index 0000000000..2519da354f --- /dev/null +++ b/crates/relayer-components/src/components/default/build.rs @@ -0,0 +1,39 @@ +use core::marker::PhantomData; + +use crate::build::components::birelay::BuildBiRelayFromRelays; +use crate::build::components::chain::cache::BuildChainWithCache; +use crate::build::components::relay::build_from_chain::BuildRelayFromChains; +use crate::build::components::relay::cache::BuildRelayWithCache; +use crate::build::traits::components::birelay_builder::BiRelayBuilderComponent; +use crate::build::traits::components::birelay_from_relay_builder::BiRelayFromRelayBuilderComponent; +use crate::build::traits::components::chain_builder::ChainBuilderComponent; +use crate::build::traits::components::relay_builder::RelayBuilderComponent; +use crate::build::traits::components::relay_from_chains_builder::RelayFromChainsBuilderComponent; +pub struct DefaultBuildComponents(pub PhantomData); + +crate::delegate_component!( + ChainBuilderComponent, + DefaultBuildComponents, + BuildChainWithCache, +); + +crate::delegate_component!( + RelayBuilderComponent, + DefaultBuildComponents, + BuildRelayWithCache, +); + +crate::delegate_component!( + BiRelayBuilderComponent, + DefaultBuildComponents, + BuildBiRelayFromRelays, +); + +crate::delegate_components!( + [ + RelayFromChainsBuilderComponent, + BiRelayFromRelayBuilderComponent, + ], + DefaultBuildComponents, + BaseComponents, +); diff --git a/crates/relayer-components/src/components/default/chain.rs b/crates/relayer-components/src/components/default/chain.rs new file mode 100644 index 0000000000..b5f2565f7a --- /dev/null +++ b/crates/relayer-components/src/components/default/chain.rs @@ -0,0 +1,18 @@ +use core::marker::PhantomData; + +use crate::chain::traits::components::chain_status_querier::ChainStatusQuerierComponent; +use crate::chain::traits::components::consensus_state_querier::ConsensusStateQuerierComponent; +use crate::chain::traits::components::message_sender::MessageSenderComponent; +use crate::chain::traits::components::packet_fields_reader::PacketFieldsReaderComponent; +pub struct DefaultChainComponents(pub PhantomData); + +crate::delegate_components!( + [ + ChainStatusQuerierComponent, + ConsensusStateQuerierComponent, + MessageSenderComponent, + PacketFieldsReaderComponent, + ], + DefaultChainComponents, + BaseComponents, +); diff --git a/crates/relayer-components/src/components/default/closures/build.rs b/crates/relayer-components/src/components/default/closures/build.rs new file mode 100644 index 0000000000..438e83c062 --- /dev/null +++ b/crates/relayer-components/src/components/default/closures/build.rs @@ -0,0 +1,43 @@ +use crate::build::traits::birelay::HasBiRelayType; +use crate::build::traits::cache::{HasChainCache, HasRelayCache}; +use crate::build::traits::components::birelay_builder::CanBuildBiRelay; +use crate::build::traits::components::birelay_from_relay_builder::BiRelayFromRelayBuilder; +use crate::build::traits::components::chain_builder::ChainBuilder; +use crate::build::traits::components::relay_from_chains_builder::RelayFromChainsBuilder; +use crate::build::traits::target::chain::{ChainATarget, ChainBTarget}; +use crate::build::traits::target::relay::{RelayAToBTarget, RelayBToATarget}; +use crate::chain::traits::types::ibc::HasIbcChainTypes; +use crate::components::default::build::DefaultBuildComponents; +use crate::core::traits::component::HasComponents; +use crate::core::traits::error::HasErrorType; +use crate::relay::traits::chains::HasRelayChains; +use crate::relay::traits::two_way::HasTwoWayRelay; + +pub trait UseDefaultBuilderComponents: CanBuildBiRelay {} + +impl + UseDefaultBuilderComponents for Build +where + Build: HasErrorType + + HasBiRelayType + + HasRelayCache + + HasRelayCache + + HasChainCache + + HasChainCache + + HasComponents>, + BiRelay: HasTwoWayRelay, + RelayAToB: Clone + HasRelayChains, + RelayBToA: Clone + HasRelayChains, + ChainA: Clone + HasIbcChainTypes, + ChainB: Clone + HasIbcChainTypes, + ChainA::ChainId: Ord + Clone, + ChainB::ChainId: Ord + Clone, + ChainA::ClientId: Ord + Clone, + ChainB::ClientId: Ord + Clone, + BaseComponents: BiRelayFromRelayBuilder + + RelayFromChainsBuilder + + RelayFromChainsBuilder + + ChainBuilder + + ChainBuilder, +{ +} diff --git a/crates/relayer-components/src/components/default/closures/mod.rs b/crates/relayer-components/src/components/default/closures/mod.rs new file mode 100644 index 0000000000..02966cd3d7 --- /dev/null +++ b/crates/relayer-components/src/components/default/closures/mod.rs @@ -0,0 +1,3 @@ +pub mod build; +pub mod relay; +pub mod transaction; diff --git a/crates/relayer-components/src/components/default/closures/relay/ack_packet_relayer.rs b/crates/relayer-components/src/components/default/closures/relay/ack_packet_relayer.rs new file mode 100644 index 0000000000..239387f1e4 --- /dev/null +++ b/crates/relayer-components/src/components/default/closures/relay/ack_packet_relayer.rs @@ -0,0 +1,74 @@ +use crate::chain::traits::client::client_state::CanQueryClientState; +use crate::chain::traits::client::consensus_state::CanFindConsensusStateHeight; +use crate::chain::traits::client::update::{ + CanBuildUpdateClientMessage, CanBuildUpdateClientPayload, +}; +use crate::chain::traits::components::chain_status_querier::CanQueryChainStatus; +use crate::chain::traits::components::consensus_state_querier::CanQueryConsensusState; +use crate::chain::traits::components::message_sender::CanSendMessages; +use crate::chain::traits::components::packet_fields_reader::CanReadPacketFields; +use crate::chain::traits::message_builders::ack_packet::{ + CanBuildAckPacketMessage, CanBuildAckPacketPayload, +}; +use crate::chain::traits::types::chain_id::HasChainId; +use crate::chain::traits::types::client_state::HasClientStateFields; +use crate::chain::traits::types::consensus_state::HasConsensusStateType; +use crate::chain::traits::types::height::CanIncrementHeight; +use crate::chain::traits::types::ibc::HasCounterpartyMessageHeight; +use crate::chain::traits::types::ibc_events::write_ack::HasWriteAcknowledgementEvent; +use crate::components::default::relay::DefaultRelayComponents; +use crate::core::traits::component::HasComponents; +use crate::core::traits::error::HasErrorType; +use crate::core::traits::sync::Async; +use crate::logger::traits::has_logger::HasLogger; +use crate::logger::traits::level::HasBaseLogLevels; +use crate::relay::traits::chains::HasRelayChains; +use crate::relay::traits::components::packet_relayers::ack_packet::CanRelayAckPacket; +use crate::runtime::traits::runtime::HasRuntime; +use crate::runtime::traits::sleep::CanSleep; + +pub trait CanUseDefaultAckPacketRelayer: UseDefaultAckPacketRelayer +where + Self::DstChain: HasWriteAcknowledgementEvent, +{ +} + +pub trait UseDefaultAckPacketRelayer: CanRelayAckPacket +where + Self::DstChain: HasWriteAcknowledgementEvent, +{ +} + +impl UseDefaultAckPacketRelayer for Relay +where + Relay: HasRelayChains + + HasLogger + + HasComponents>, + SrcChain: HasErrorType + + HasChainId + + CanSendMessages + + HasConsensusStateType + + HasCounterpartyMessageHeight + + CanReadPacketFields + + CanQueryClientState + + CanQueryConsensusState + + CanFindConsensusStateHeight + + CanBuildAckPacketMessage + + CanBuildUpdateClientMessage, + DstChain: HasErrorType + + HasRuntime + + HasChainId + + CanIncrementHeight + + CanQueryChainStatus + + HasClientStateFields + + HasConsensusStateType + + CanReadPacketFields + + CanBuildAckPacketPayload + + CanBuildUpdateClientPayload, + SrcChain::Height: Clone, + DstChain::Height: Clone, + DstChain::Runtime: CanSleep, + Relay::Logger: HasBaseLogLevels, + BaseRelayComponents: Async, +{ +} diff --git a/crates/relayer-components/src/components/default/closures/relay/auto_relayer.rs b/crates/relayer-components/src/components/default/closures/relay/auto_relayer.rs new file mode 100644 index 0000000000..ea7f69311d --- /dev/null +++ b/crates/relayer-components/src/components/default/closures/relay/auto_relayer.rs @@ -0,0 +1,22 @@ +use crate::chain::traits::event_subscription::HasEventSubscription; +use crate::components::default::closures::relay::event_relayer::UseDefaultEventRelayer; +use crate::components::default::relay::DefaultRelayComponents; +use crate::core::traits::component::HasComponents; +use crate::core::traits::sync::Async; +use crate::relay::traits::chains::HasRelayChains; +use crate::relay::traits::components::auto_relayer::CanAutoRelay; + +pub trait CanUseDefaultAutoRelayer: UseDefaultAutoRelayer {} + +pub trait UseDefaultAutoRelayer: CanAutoRelay {} + +impl UseDefaultAutoRelayer for Relay +where + Relay: HasRelayChains + + UseDefaultEventRelayer + + HasComponents>, + Relay::SrcChain: HasEventSubscription, + Relay::DstChain: HasEventSubscription, + BaseRelayComponents: Async, +{ +} diff --git a/crates/relayer-components/src/components/default/closures/relay/components.rs b/crates/relayer-components/src/components/default/closures/relay/components.rs new file mode 100644 index 0000000000..8bfc724fa9 --- /dev/null +++ b/crates/relayer-components/src/components/default/closures/relay/components.rs @@ -0,0 +1,8 @@ +use crate::components::default::closures::relay::auto_relayer::UseDefaultAutoRelayer; +use crate::components::default::closures::relay::event_relayer::UseDefaultEventRelayer; +use crate::components::default::closures::relay::packet_relayer::UseDefaultPacketRelayer; + +pub trait CanUseDefaultRelayComponents: + UseDefaultPacketRelayer + UseDefaultEventRelayer + UseDefaultAutoRelayer +{ +} diff --git a/crates/relayer-components/src/components/default/closures/relay/event_relayer.rs b/crates/relayer-components/src/components/default/closures/relay/event_relayer.rs new file mode 100644 index 0000000000..39cf233638 --- /dev/null +++ b/crates/relayer-components/src/components/default/closures/relay/event_relayer.rs @@ -0,0 +1,46 @@ +use crate::chain::traits::logs::packet::CanLogChainPacket; +use crate::chain::traits::queries::channel::CanQueryCounterpartyChainIdFromChannel; +use crate::chain::traits::types::chain_id::HasChainId; +use crate::chain::traits::types::ibc_events::send_packet::HasSendPacketEvent; +use crate::chain::traits::types::ibc_events::write_ack::CanBuildPacketFromWriteAckEvent; +use crate::components::default::closures::relay::ack_packet_relayer::UseDefaultAckPacketRelayer; +use crate::components::default::closures::relay::packet_relayer::UseDefaultPacketRelayer; +use crate::components::default::relay::DefaultRelayComponents; +use crate::core::traits::component::HasComponents; +use crate::logger::traits::has_logger::{HasLogger, HasLoggerType}; +use crate::logger::traits::level::HasBaseLogLevels; +use crate::relay::traits::chains::HasRelayChains; +use crate::relay::traits::components::event_relayer::CanRelayEvent; +use crate::relay::traits::components::packet_filter::PacketFilter; +use crate::relay::traits::packet::HasRelayPacketFields; +use crate::relay::traits::packet_lock::HasPacketLock; +use crate::relay::traits::target::{DestinationTarget, SourceTarget}; + +pub trait CanUseDefaultEventRelayer: UseDefaultEventRelayer {} + +pub trait UseDefaultEventRelayer: + CanRelayEvent + CanRelayEvent +{ +} + +impl UseDefaultEventRelayer for Relay +where + Relay: HasRelayChains + + HasPacketLock + + HasLogger + + HasRelayPacketFields + + UseDefaultAckPacketRelayer + + UseDefaultPacketRelayer + + HasComponents>, + Relay::SrcChain: HasChainId + + HasLoggerType + + CanLogChainPacket + + HasSendPacketEvent + + CanQueryCounterpartyChainIdFromChannel, + Relay::DstChain: HasChainId + + CanQueryCounterpartyChainIdFromChannel + + CanBuildPacketFromWriteAckEvent, + Relay::Logger: HasBaseLogLevels, + BaseRelayComponents: PacketFilter, +{ +} diff --git a/crates/relayer-components/src/components/default/closures/relay/mod.rs b/crates/relayer-components/src/components/default/closures/relay/mod.rs new file mode 100644 index 0000000000..173419b254 --- /dev/null +++ b/crates/relayer-components/src/components/default/closures/relay/mod.rs @@ -0,0 +1,5 @@ +pub mod ack_packet_relayer; +pub mod auto_relayer; +pub mod components; +pub mod event_relayer; +pub mod packet_relayer; diff --git a/crates/relayer-components/src/components/default/closures/relay/packet_relayer.rs b/crates/relayer-components/src/components/default/closures/relay/packet_relayer.rs new file mode 100644 index 0000000000..b30a7aa538 --- /dev/null +++ b/crates/relayer-components/src/components/default/closures/relay/packet_relayer.rs @@ -0,0 +1,96 @@ +use crate::chain::traits::client::client_state::CanQueryClientState; +use crate::chain::traits::client::consensus_state::CanFindConsensusStateHeight; +use crate::chain::traits::client::update::{ + CanBuildUpdateClientMessage, CanBuildUpdateClientPayload, +}; +use crate::chain::traits::components::chain_status_querier::CanQueryChainStatus; +use crate::chain::traits::components::consensus_state_querier::CanQueryConsensusState; +use crate::chain::traits::components::message_sender::CanSendMessages; +use crate::chain::traits::components::packet_fields_reader::CanReadPacketFields; +use crate::chain::traits::logs::packet::CanLogChainPacket; +use crate::chain::traits::message_builders::ack_packet::{ + CanBuildAckPacketMessage, CanBuildAckPacketPayload, +}; +use crate::chain::traits::message_builders::receive_packet::{ + CanBuildReceivePacketMessage, CanBuildReceivePacketPayload, +}; +use crate::chain::traits::message_builders::timeout_unordered_packet::{ + CanBuildTimeoutUnorderedPacketMessage, CanBuildTimeoutUnorderedPacketPayload, +}; +use crate::chain::traits::queries::received_packet::CanQueryReceivedPacket; +use crate::chain::traits::types::chain_id::HasChainId; +use crate::chain::traits::types::client_state::HasClientStateFields; +use crate::chain::traits::types::consensus_state::HasConsensusStateType; +use crate::chain::traits::types::height::CanIncrementHeight; +use crate::chain::traits::types::ibc::HasCounterpartyMessageHeight; +use crate::chain::traits::types::ibc_events::write_ack::HasWriteAcknowledgementEvent; +use crate::components::default::relay::DefaultRelayComponents; +use crate::core::traits::component::HasComponents; +use crate::core::traits::error::HasErrorType; +use crate::logger::traits::has_logger::{HasLogger, HasLoggerType}; +use crate::logger::traits::level::HasBaseLogLevels; +use crate::relay::traits::chains::HasRelayChains; +use crate::relay::traits::components::packet_filter::PacketFilter; +use crate::relay::traits::components::packet_relayer::CanRelayPacket; +use crate::relay::traits::packet_lock::HasPacketLock; +use crate::runtime::traits::runtime::HasRuntime; +use crate::runtime::traits::sleep::CanSleep; + +pub trait CanUseDefaultPacketRelayer: UseDefaultPacketRelayer {} + +pub trait UseDefaultPacketRelayer: CanRelayPacket {} + +impl UseDefaultPacketRelayer for Relay +where + Relay: HasRelayChains + + HasLogger + + HasPacketLock + + HasComponents>, + SrcChain: HasErrorType + + HasRuntime + + HasChainId + + CanSendMessages + + CanIncrementHeight + + CanQueryChainStatus + + HasLoggerType + + HasClientStateFields + + HasConsensusStateType + + HasCounterpartyMessageHeight + + CanReadPacketFields + + CanLogChainPacket + + CanQueryClientState + + CanQueryConsensusState + + CanFindConsensusStateHeight + + CanBuildReceivePacketPayload + + CanBuildUpdateClientPayload + + CanBuildAckPacketMessage + + CanBuildUpdateClientMessage + + CanBuildTimeoutUnorderedPacketMessage, + DstChain: HasErrorType + + HasRuntime + + HasChainId + + CanSendMessages + + CanIncrementHeight + + CanQueryChainStatus + + HasClientStateFields + + HasConsensusStateType + + HasCounterpartyMessageHeight + + HasWriteAcknowledgementEvent + + CanReadPacketFields + + CanQueryClientState + + CanQueryReceivedPacket + + CanQueryConsensusState + + CanFindConsensusStateHeight + + CanBuildAckPacketPayload + + CanBuildUpdateClientPayload + + CanBuildTimeoutUnorderedPacketPayload + + CanBuildUpdateClientMessage + + CanBuildReceivePacketMessage, + SrcChain::Height: Clone, + DstChain::Height: Clone, + SrcChain::Runtime: CanSleep, + DstChain::Runtime: CanSleep, + Relay::Logger: HasBaseLogLevels, + BaseRelayComponents: PacketFilter, +{ +} diff --git a/crates/relayer-components/src/components/default/closures/transaction.rs b/crates/relayer-components/src/components/default/closures/transaction.rs new file mode 100644 index 0000000000..1287813ea0 --- /dev/null +++ b/crates/relayer-components/src/components/default/closures/transaction.rs @@ -0,0 +1,66 @@ +use crate::chain::traits::components::message_sender::CanSendMessages; +use crate::chain::traits::types::chain_id::HasChainId; +use crate::components::default::transaction::DefaultTxComponents; +use crate::core::traits::component::HasComponents; +use crate::core::traits::error::HasErrorType; +use crate::logger::traits::has_logger::HasLogger; +use crate::logger::traits::level::HasBaseLogLevels; +use crate::runtime::traits::mutex::HasMutex; +use crate::runtime::traits::sleep::CanSleep; +use crate::runtime::traits::time::HasTime; +use crate::transaction::components::poll::{CanRaiseNoTxResponseError, HasPollTimeout}; +use crate::transaction::traits::components::message_as_tx_sender::CanSendMessagesAsTx; +use crate::transaction::traits::components::nonce_allocater::CanAllocateNonce; +use crate::transaction::traits::components::nonce_querier::{CanQueryNonce, NonceQuerier}; +use crate::transaction::traits::components::tx_encoder::{CanEncodeTx, TxEncoder}; +use crate::transaction::traits::components::tx_fee_estimater::{CanEstimateTxFee, TxFeeEstimator}; +use crate::transaction::traits::components::tx_response_poller::CanPollTxResponse; +use crate::transaction::traits::components::tx_response_querier::{ + CanQueryTxResponse, TxResponseQuerier, +}; +use crate::transaction::traits::components::tx_submitter::{CanSubmitTx, TxSubmitter}; +use crate::transaction::traits::event::CanParseTxResponseAsEvents; +use crate::transaction::traits::fee::HasFeeForSimulation; +use crate::transaction::traits::logs::nonce::CanLogNonce; +use crate::transaction::traits::nonce::guard::HasNonceGuard; +use crate::transaction::traits::nonce::mutex::HasMutexForNonceAllocation; +use crate::transaction::traits::signer::HasSigner; +use crate::transaction::traits::types::HasTxTypes; + +pub trait UseDefaultTxComponents: + CanSendMessages + + CanSendMessagesAsTx + + CanAllocateNonce + + CanPollTxResponse + + CanQueryNonce + + CanEncodeTx + + CanEstimateTxFee + + CanSubmitTx + + CanQueryTxResponse +{ +} + +impl UseDefaultTxComponents for Chain +where + Chain: HasErrorType + + HasTxTypes + + HasSigner + + HasNonceGuard + + HasChainId + + HasFeeForSimulation + + HasMutexForNonceAllocation + + HasPollTimeout + + HasLogger + + CanLogNonce + + CanParseTxResponseAsEvents + + CanRaiseNoTxResponseError + + HasComponents>, + Chain::Runtime: HasMutex + HasTime + CanSleep, + Chain::Logger: HasBaseLogLevels, + BaseComponents: TxEncoder + + TxFeeEstimator + + NonceQuerier + + TxSubmitter + + TxResponseQuerier, +{ +} diff --git a/crates/relayer-components/src/components/default/mod.rs b/crates/relayer-components/src/components/default/mod.rs new file mode 100644 index 0000000000..b0a50173ea --- /dev/null +++ b/crates/relayer-components/src/components/default/mod.rs @@ -0,0 +1,6 @@ +pub mod birelay; +pub mod build; +pub mod chain; +pub mod closures; +pub mod relay; +pub mod transaction; diff --git a/crates/relayer-components/src/components/default/relay.rs b/crates/relayer-components/src/components/default/relay.rs new file mode 100644 index 0000000000..22a27c8ca5 --- /dev/null +++ b/crates/relayer-components/src/components/default/relay.rs @@ -0,0 +1,99 @@ +use core::marker::PhantomData; + +use crate::relay::components::auto_relayers::concurrent_bidirectional::ConcurrentBidirectionalRelayer; +use crate::relay::components::auto_relayers::concurrent_event::ConcurrentEventSubscriptionRelayer; +use crate::relay::components::create_client::CreateClientWithChains; +use crate::relay::components::event_relayers::packet_event::PacketEventRelayer; +use crate::relay::components::message_senders::chain_sender::SendIbcMessagesToChain; +use crate::relay::components::message_senders::update_client::SendIbcMessagesWithUpdateClient; +use crate::relay::components::packet_clearers::receive_packet::ClearReceivePackets; +use crate::relay::components::packet_relayers::ack::base_ack_packet::BaseAckPacketRelayer; +use crate::relay::components::packet_relayers::general::filter_relayer::FilterRelayer; +use crate::relay::components::packet_relayers::general::full_relay::FullCycleRelayer; +use crate::relay::components::packet_relayers::general::lock::LockPacketRelayer; +use crate::relay::components::packet_relayers::general::log::LoggerRelayer; +use crate::relay::components::packet_relayers::receive::base_receive_packet::BaseReceivePacketRelayer; +use crate::relay::components::packet_relayers::receive::skip_received_packet::SkipReceivedPacketRelayer; +use crate::relay::components::packet_relayers::timeout_unordered::timeout_unordered_packet::BaseTimeoutUnorderedPacketRelayer; +use crate::relay::components::update_client::build::BuildUpdateClientMessages; +use crate::relay::components::update_client::skip::SkipUpdateClient; +use crate::relay::components::update_client::wait::WaitUpdateClient; +use crate::relay::traits::components::auto_relayer::AutoRelayerComponent; +use crate::relay::traits::components::client_creator::ClientCreatorComponent; +use crate::relay::traits::components::event_relayer::EventRelayerComponent; +use crate::relay::traits::components::ibc_message_sender::{IbcMessageSenderComponent, MainSink}; +use crate::relay::traits::components::packet_clearer::PacketClearerComponent; +use crate::relay::traits::components::packet_filter::PacketFilterComponent; +use crate::relay::traits::components::packet_relayer::PacketRelayerComponent; +use crate::relay::traits::components::packet_relayers::ack_packet::AckPacketRelayerComponent; +use crate::relay::traits::components::packet_relayers::receive_packet::ReceivePacketRelayerComponnent; +use crate::relay::traits::components::packet_relayers::timeout_unordered_packet::TimeoutUnorderedPacketRelayerComponent; +use crate::relay::traits::components::update_client_message_builder::UpdateClientMessageBuilderComponent; + +pub struct DefaultRelayComponents(pub PhantomData); + +crate::delegate_component!( + IbcMessageSenderComponent, + DefaultRelayComponents, + SendIbcMessagesWithUpdateClient, +); + +crate::delegate_component!( + UpdateClientMessageBuilderComponent, + DefaultRelayComponents, + SkipUpdateClient>, +); + +crate::delegate_component!( + PacketRelayerComponent, + DefaultRelayComponents, + LockPacketRelayer>>, +); + +crate::delegate_component!( + PacketFilterComponent, + DefaultRelayComponents, + BaseComponents, +); + +crate::delegate_component!( + ReceivePacketRelayerComponnent, + DefaultRelayComponents, + SkipReceivedPacketRelayer, +); + +crate::delegate_component!( + AckPacketRelayerComponent, + DefaultRelayComponents, + BaseAckPacketRelayer, +); + +crate::delegate_component!( + TimeoutUnorderedPacketRelayerComponent, + DefaultRelayComponents, + BaseTimeoutUnorderedPacketRelayer, +); + +crate::delegate_component!( + EventRelayerComponent, + DefaultRelayComponents, + PacketEventRelayer, +); + +crate::delegate_component!( + AutoRelayerComponent, + DefaultRelayComponents, + ConcurrentBidirectionalRelayer, +); + +crate::delegate_component!( + ClientCreatorComponent, + DefaultRelayComponents, + CreateClientWithChains, +); + +crate::delegate_component!( + PacketClearerComponent, + DefaultRelayComponents, + ClearReceivePackets, +); diff --git a/crates/relayer-components/src/components/default/transaction.rs b/crates/relayer-components/src/components/default/transaction.rs new file mode 100644 index 0000000000..2a57a6ae46 --- /dev/null +++ b/crates/relayer-components/src/components/default/transaction.rs @@ -0,0 +1,53 @@ +use core::marker::PhantomData; + +use crate::chain::traits::components::message_sender::MessageSenderComponent; +use crate::transaction::components::message_as_tx::EstimateFeesAndSendTx; +use crate::transaction::components::message_sender::send_as_tx::SendMessagesAsTx; +use crate::transaction::components::nonce::mutex::AllocateNonceWithMutex; +use crate::transaction::components::poll::PollTxResponse; +use crate::transaction::traits::components::message_as_tx_sender::MessageAsTxSenderComponent; +use crate::transaction::traits::components::nonce_allocater::NonceAllocatorComponent; +use crate::transaction::traits::components::nonce_querier::NonceQuerierComponent; +use crate::transaction::traits::components::tx_encoder::TxEncoderComponent; +use crate::transaction::traits::components::tx_fee_estimater::TxFeeEstimatorComponent; +use crate::transaction::traits::components::tx_response_poller::TxResponsePollerComponent; +use crate::transaction::traits::components::tx_response_querier::TxResponseQuerierComponent; +use crate::transaction::traits::components::tx_submitter::TxSubmitterComponent; + +pub struct DefaultTxComponents(pub PhantomData); + +crate::delegate_component!( + MessageSenderComponent, + DefaultTxComponents, + SendMessagesAsTx, +); + +crate::delegate_component!( + MessageAsTxSenderComponent, + DefaultTxComponents, + EstimateFeesAndSendTx, +); + +crate::delegate_component!( + NonceAllocatorComponent, + DefaultTxComponents, + AllocateNonceWithMutex, +); + +crate::delegate_component!( + TxResponsePollerComponent, + DefaultTxComponents, + PollTxResponse, +); + +crate::delegate_components!( + [ + NonceQuerierComponent, + TxEncoderComponent, + TxFeeEstimatorComponent, + TxSubmitterComponent, + TxResponseQuerierComponent, + ], + DefaultTxComponents, + BaseComponents, +); diff --git a/crates/relayer-components/src/components/mod.rs b/crates/relayer-components/src/components/mod.rs new file mode 100644 index 0000000000..1be8d340b8 --- /dev/null +++ b/crates/relayer-components/src/components/mod.rs @@ -0,0 +1 @@ +pub mod default; diff --git a/crates/relayer-components/src/core/mod.rs b/crates/relayer-components/src/core/mod.rs new file mode 100644 index 0000000000..b9f462e589 --- /dev/null +++ b/crates/relayer-components/src/core/mod.rs @@ -0,0 +1,15 @@ +/*! + Core constructs common for all contexts. + + This module contains the common constructs that are usable across all + contexts. This includes: + + - The [`Async`](traits::sync::Async) trait is used to constraint + abstract types so that they are safe to use inside async functions. + + - The [`HasErrorType`](traits::error::HasErrorType) trait is used for contexts + to declare a single abstract `Error` type. +*/ + +pub mod traits; +pub mod types; diff --git a/crates/relayer-components/src/core/traits/component.rs b/crates/relayer-components/src/core/traits/component.rs new file mode 100644 index 0000000000..e6205ffac5 --- /dev/null +++ b/crates/relayer-components/src/core/traits/component.rs @@ -0,0 +1,33 @@ +use crate::core::traits::sync::Async; + +pub trait HasComponents: Async { + type Components: Async; +} +pub trait DelegateComponent: Async { + type Delegate; +} + +#[macro_export] +macro_rules! delegate_component { + ( $key:ty, $target:ident $( < $( $param:ident ),* $(,)? > )?, $forwarded:ty $(,)? ) => { + impl< $( $( $param ),* )* > + $crate::core::traits::component::DelegateComponent<$key> + for $target $( < $( $param ),* > )* + where + Self: $crate::core::traits::sync::Async, + { + type Delegate = $forwarded; + } + }; +} + +#[macro_export] +macro_rules! delegate_components { + ( [$(,)?], $target:ident $( < $( $param:ident ),* $(,)? > )?, $forwarded:ty $(,)? ) => { + + }; + ( [$name:ty $(, $($rest:tt)* )?], $target:ident $( < $( $param:ident ),* $(,)? > )?, $forwarded:ty $(,)? ) => { + $crate::delegate_component!($name, $target $( < $( $param ),* > )*, $forwarded); + $crate::delegate_components!([ $( $($rest)* )? ], $target $( < $( $param ),* > )*, $forwarded); + }; +} diff --git a/crates/relayer-components/src/core/traits/error.rs b/crates/relayer-components/src/core/traits/error.rs new file mode 100644 index 0000000000..04e27ce60f --- /dev/null +++ b/crates/relayer-components/src/core/traits/error.rs @@ -0,0 +1,36 @@ +use core::fmt::Debug; + +use crate::core::traits::sync::Async; + +/** + This is used for contexts to declare that they have a _unique_ `Self::Error` type. + + Although it is possible for each context to declare their own associated + `Error` type, doing so may result in having multiple ambiguous `Self::Error` types, + if there are multiple associated types with the same name in different traits. + + As a result, it is better for context traits to include `HasError` as their + parent traits, so that multiple traits can all refer to the same abstract + `Self::Error` type. +*/ +pub trait HasErrorType: Async { + /** + The `Error` associated type is also required to implement [`Debug`]. + + This is to allow `Self::Error` to be used in calls like `.unwrap()`, + as well as for simpler error logging. + */ + type Error: Async + Debug; +} + +/** + Used for injecting external error types into [`Self::Error`](HasErrorType::Error). + + As an example, if `Context: InjectError`, then we would be + able to call `Context::inject_error(err)` for an error value + [`err: ParseIntError`](core::num::ParseIntError) and get back + a [`Context::Error`](HasErrorType::Error) value. +*/ +pub trait InjectError: HasErrorType { + fn inject_error(err: E) -> Self::Error; +} diff --git a/crates/relayer-components/src/core/traits/mod.rs b/crates/relayer-components/src/core/traits/mod.rs new file mode 100644 index 0000000000..cce358faaa --- /dev/null +++ b/crates/relayer-components/src/core/traits/mod.rs @@ -0,0 +1,3 @@ +pub mod component; +pub mod error; +pub mod sync; diff --git a/crates/relayer-components/src/core/traits/sync.rs b/crates/relayer-components/src/core/traits/sync.rs new file mode 100644 index 0000000000..50c9e67964 --- /dev/null +++ b/crates/relayer-components/src/core/traits/sync.rs @@ -0,0 +1,27 @@ +/** + This is defined as a convenient constraint alias to + `Sized + Send + Sync + 'static`. + + This constraint is commonly required to be present in almost all associated + types. The `Sized` constraint is commonly required for associated types to be + used as generic parameters. The `Send + Sync + 'static` constraints are + important for the use of async functions inside traits. + + Because Rust do not currently natively support the use of async functions + in traits, we use the [`async_trait`] crate to desugar async functions + inside traits into functions returning + `Pin>`. Due to the additional `Send` and lifetime + trait bounds inside the returned boxed future, almost all values that are + used inside the async functions are required to have types that implement + `Send` and `Sync`. + + It is also common to require the associated types to have the `'static` + lifetime for them to be used inside async functions, because Rust would + otherwise infer a more restrictive lifetime that does not outlive the + async functions. The `'static` lifetime constraint here really means + that the types implementing `Async` must not contain any lifetime + parameter. +*/ +pub trait Async: Sized + Send + Sync + 'static {} + +impl Async for A where A: Sized + Send + Sync + 'static {} diff --git a/crates/relayer-components/src/core/types/alias.rs b/crates/relayer-components/src/core/types/alias.rs new file mode 100644 index 0000000000..ef60100d2c --- /dev/null +++ b/crates/relayer-components/src/core/types/alias.rs @@ -0,0 +1 @@ +pub struct Alias; diff --git a/crates/relayer-components/src/core/types/mod.rs b/crates/relayer-components/src/core/types/mod.rs new file mode 100644 index 0000000000..0c19518258 --- /dev/null +++ b/crates/relayer-components/src/core/types/mod.rs @@ -0,0 +1 @@ +pub mod alias; diff --git a/crates/relayer-components/src/lib.rs b/crates/relayer-components/src/lib.rs new file mode 100644 index 0000000000..abb7ae6c9a --- /dev/null +++ b/crates/relayer-components/src/lib.rs @@ -0,0 +1,17 @@ +#![no_std] +#![allow(clippy::type_complexity)] +#![allow(clippy::too_many_arguments)] +#![allow(clippy::needless_lifetimes)] + +mod std_prelude; +extern crate alloc; + +pub mod build; +pub mod chain; +pub mod components; +pub mod core; +pub mod logger; +pub mod relay; +pub mod runtime; +pub mod transaction; +pub mod vendor; diff --git a/crates/relayer-components/src/logger/mod.rs b/crates/relayer-components/src/logger/mod.rs new file mode 100644 index 0000000000..d28c42369f --- /dev/null +++ b/crates/relayer-components/src/logger/mod.rs @@ -0,0 +1,2 @@ +pub mod traits; +pub mod types; diff --git a/crates/relayer-components/src/logger/traits/has_logger.rs b/crates/relayer-components/src/logger/traits/has_logger.rs new file mode 100644 index 0000000000..73f7fc2436 --- /dev/null +++ b/crates/relayer-components/src/logger/traits/has_logger.rs @@ -0,0 +1,10 @@ +use crate::core::traits::sync::Async; +use crate::logger::traits::logger::BaseLogger; + +pub trait HasLoggerType: Async { + type Logger: BaseLogger; +} + +pub trait HasLogger: HasLoggerType { + fn logger(&self) -> &Self::Logger; +} diff --git a/crates/relayer-components/src/logger/traits/level.rs b/crates/relayer-components/src/logger/traits/level.rs new file mode 100644 index 0000000000..1cbd3d3b07 --- /dev/null +++ b/crates/relayer-components/src/logger/traits/level.rs @@ -0,0 +1,65 @@ +use crate::logger::traits::has_logger::HasLogger; +use crate::logger::traits::logger::BaseLogger; + +pub struct LevelTrace; + +pub struct LevelDebug; + +pub struct LevelInfo; + +pub struct LevelWarn; + +pub struct LevelError; + +pub trait HasLogLevel: BaseLogger { + const LEVEL: Self::LogLevel; +} + +pub trait HasBaseLogLevels: + HasLogLevel + + HasLogLevel + + HasLogLevel + + HasLogLevel + + HasLogLevel +{ + const LEVEL_TRACE: Self::LogLevel; + + const LEVEL_DEBUG: Self::LogLevel; + + const LEVEL_INFO: Self::LogLevel; + + const LEVEL_WARN: Self::LogLevel; + + const LEVEL_ERROR: Self::LogLevel; +} + +impl HasBaseLogLevels for Logger +where + Logger: HasLogLevel + + HasLogLevel + + HasLogLevel + + HasLogLevel + + HasLogLevel, +{ + const LEVEL_TRACE: Self::LogLevel = >::LEVEL; + + const LEVEL_DEBUG: Self::LogLevel = >::LEVEL; + + const LEVEL_INFO: Self::LogLevel = >::LEVEL; + + const LEVEL_WARN: Self::LogLevel = >::LEVEL; + + const LEVEL_ERROR: Self::LogLevel = >::LEVEL; +} + +pub trait HasLoggerWithBaseLevels: HasLogger { + type LoggerWithBaseLevels: HasBaseLogLevels; +} + +impl HasLoggerWithBaseLevels for Context +where + Context: HasLogger, + Logger: HasBaseLogLevels, +{ + type LoggerWithBaseLevels = Logger; +} diff --git a/crates/relayer-components/src/logger/traits/log.rs b/crates/relayer-components/src/logger/traits/log.rs new file mode 100644 index 0000000000..b0fe96387e --- /dev/null +++ b/crates/relayer-components/src/logger/traits/log.rs @@ -0,0 +1,36 @@ +use crate::logger::traits::has_logger::HasLogger; +use crate::logger::traits::level::{HasBaseLogLevels, HasLoggerWithBaseLevels}; +use crate::logger::traits::logger::BaseLogger; +use crate::logger::types::wrapper::LogWrapper; + +pub trait CanLog: HasLoggerWithBaseLevels { + fn log<'a>( + &'a self, + level: ::LogLevel, + message: &str, + build_log: impl for<'r> FnOnce(LogWrapper<'a, 'r, Self::Logger>), + ); + + fn log_message(&self, level: ::LogLevel, message: &str); +} + +impl CanLog for Context +where + Context: HasLogger, + Logger: HasBaseLogLevels, +{ + fn log<'a>( + &'a self, + level: Logger::LogLevel, + message: &str, + build_log: impl for<'r> FnOnce(LogWrapper<'a, 'r, Self::Logger>), + ) { + self.logger().new_log(level, message, |log| { + build_log(LogWrapper { log }); + }); + } + + fn log_message(&self, level: ::LogLevel, message: &str) { + self.log(level, message, |_| {}) + } +} diff --git a/crates/relayer-components/src/logger/traits/logger.rs b/crates/relayer-components/src/logger/traits/logger.rs new file mode 100644 index 0000000000..a28aa435c3 --- /dev/null +++ b/crates/relayer-components/src/logger/traits/logger.rs @@ -0,0 +1,34 @@ +use core::fmt::{Debug, Display}; + +use crate::core::traits::sync::Async; + +pub trait BaseLogger: Async { + type Log<'a, 'r>; + + type LogValue<'a>; + + type LogLevel: Default + Clone + Async; + + fn new_log<'a>( + &'a self, + level: Self::LogLevel, + message: &str, + build_log: impl for<'r> FnOnce(&'r Self::Log<'a, 'r>), + ); + + fn log_field<'a, 'b, 'c, 'r>(log: &Self::Log<'a, 'r>, key: &'b str, value: Self::LogValue<'b>) + where + 'b: 'a; + + fn display_value<'a, T>(value: &'a T) -> Self::LogValue<'a> + where + T: Display; + + fn debug_value<'a, T>(value: &'a T) -> Self::LogValue<'a> + where + T: Debug; + + fn list_values<'a>(values: &'a [Self::LogValue<'a>]) -> Self::LogValue<'a>; + + fn map_values<'a>(build_log: impl for<'s> FnOnce(&'s Self::Log<'a, 's>)) -> Self::LogValue<'a>; +} diff --git a/crates/relayer-components/src/logger/traits/mod.rs b/crates/relayer-components/src/logger/traits/mod.rs new file mode 100644 index 0000000000..087e2c8850 --- /dev/null +++ b/crates/relayer-components/src/logger/traits/mod.rs @@ -0,0 +1,4 @@ +pub mod has_logger; +pub mod level; +pub mod log; +pub mod logger; diff --git a/crates/relayer-components/src/logger/types/mod.rs b/crates/relayer-components/src/logger/types/mod.rs new file mode 100644 index 0000000000..e4ea5ca0e8 --- /dev/null +++ b/crates/relayer-components/src/logger/types/mod.rs @@ -0,0 +1 @@ +pub mod wrapper; diff --git a/crates/relayer-components/src/logger/types/wrapper.rs b/crates/relayer-components/src/logger/types/wrapper.rs new file mode 100644 index 0000000000..35dd662205 --- /dev/null +++ b/crates/relayer-components/src/logger/types/wrapper.rs @@ -0,0 +1,53 @@ +use core::fmt::{Debug, Display}; + +use crate::logger::traits::logger::BaseLogger; + +pub struct LogWrapper<'a, 'r, Logger> +where + Logger: BaseLogger, +{ + pub log: &'r Logger::Log<'a, 'r>, +} + +impl<'a, 'r, Logger> LogWrapper<'a, 'r, Logger> +where + Logger: BaseLogger, +{ + pub fn field<'b>(&self, key: &'b str, value: Logger::LogValue<'b>) -> &Self + where + 'b: 'a, + { + Logger::log_field(self.log, key, value); + + self + } + + pub fn display<'b, T>(&self, key: &'b str, value: &'b T) -> &Self + where + 'b: 'a, + T: Display, + { + self.field(key, Logger::display_value(value)) + } + + pub fn debug<'b, T>(&self, key: &'b str, value: &'b T) -> &Self + where + 'b: 'a, + T: Debug, + { + self.field(key, Logger::debug_value(value)) + } + + pub fn nested<'b>( + &self, + key: &'b str, + build_log: impl for<'s> FnOnce(LogWrapper<'b, 's, Logger>), + ) where + 'b: 'a, + { + let value = Logger::map_values(|log| { + build_log(LogWrapper { log }); + }); + Logger::log_field(self.log, key, value); + } +} diff --git a/crates/relayer-components/src/relay/components/auto_relayers/concurrent_bidirectional.rs b/crates/relayer-components/src/relay/components/auto_relayers/concurrent_bidirectional.rs new file mode 100644 index 0000000000..413fe5c710 --- /dev/null +++ b/crates/relayer-components/src/relay/components/auto_relayers/concurrent_bidirectional.rs @@ -0,0 +1,56 @@ +use core::future::Future; +use core::marker::PhantomData; +use core::pin::Pin; + +use async_trait::async_trait; +use futures_util::stream::{self, StreamExt}; + +use crate::relay::traits::chains::HasRelayChains; +use crate::relay::traits::components::auto_relayer::{AutoRelayer, AutoRelayerWithTarget}; +use crate::relay::traits::target::{DestinationTarget, SourceTarget}; +use crate::std_prelude::*; + +/// A concurrent bidirectional relay context that supports auto-relaying between two +/// targets, a `SourceTarget` and a `DestinationTarget`. It is composed of two +/// unidirectional relay contexts. +/// +/// Note that the `InRelayer` parameter is constrained such that the two sub-relay +/// contexts must relay between the same two connected chains, the `SourceTarget` +/// chain and the `DestinationTarget` chain, except in different directions. +/// +/// This relayer type aggregates the relay tasks bound for the source chain and the tasks +/// bound for the target chain, collects them into a single stream, and then relays the +/// stream of tasks in cooperative multi-tasking fashion, which is how it achieves +/// concurrency. +pub struct ConcurrentBidirectionalRelayer(pub PhantomData); + +#[async_trait] +impl AutoRelayer for ConcurrentBidirectionalRelayer +where + Relay: HasRelayChains, + InRelayer: AutoRelayerWithTarget, + InRelayer: AutoRelayerWithTarget, +{ + async fn auto_relay(relay: &Relay) -> Result<(), Relay::Error> { + let src_task: Pin + Send>> = Box::pin(async move { + let _ = + >::auto_relay_with_target( + relay, + ) + .await; + }); + + let dst_task: Pin + Send>> = Box::pin(async move { + let _ = >::auto_relay_with_target( + relay, + ) + .await; + }); + + stream::iter([src_task, dst_task]) + .for_each_concurrent(None, |task| task) + .await; + + Ok(()) + } +} diff --git a/crates/relayer-components/src/relay/components/auto_relayers/concurrent_event.rs b/crates/relayer-components/src/relay/components/auto_relayers/concurrent_event.rs new file mode 100644 index 0000000000..99096957ca --- /dev/null +++ b/crates/relayer-components/src/relay/components/auto_relayers/concurrent_event.rs @@ -0,0 +1,41 @@ +use async_trait::async_trait; +use futures_util::stream::StreamExt; + +use crate::chain::traits::event_subscription::HasEventSubscription; +use crate::relay::traits::components::auto_relayer::AutoRelayerWithTarget; +use crate::relay::traits::components::event_relayer::CanRelayEvent; +use crate::relay::traits::target::ChainTarget; +use crate::std_prelude::*; + +/// A one-way auto-relayer type that is responsible for listening for a +/// particular event subscription and relaying messages to a target +/// chain in response to those events in a concurrent fashion. +pub struct ConcurrentEventSubscriptionRelayer; + +#[async_trait] +impl AutoRelayerWithTarget for ConcurrentEventSubscriptionRelayer +where + Relay: CanRelayEvent, + Target: ChainTarget, + Target::TargetChain: HasEventSubscription, +{ + async fn auto_relay_with_target(relay: &Relay) -> Result<(), Relay::Error> { + let subscription = Target::target_chain(relay).event_subscription(); + + loop { + if let Some(event_stream) = subscription.subscribe().await { + event_stream + .for_each_concurrent(None, |item| async move { + let (height, event) = item; + + // Ignore any relaying errors, as the relayer still needs to proceed + // relaying the next event regardless. + let _ = relay.relay_chain_event(&height, &event).await; + }) + .await; + } else { + return Ok(()); + } + } + } +} diff --git a/crates/relayer-components/src/relay/components/auto_relayers/concurrent_two_way.rs b/crates/relayer-components/src/relay/components/auto_relayers/concurrent_two_way.rs new file mode 100644 index 0000000000..8c881d3137 --- /dev/null +++ b/crates/relayer-components/src/relay/components/auto_relayers/concurrent_two_way.rs @@ -0,0 +1,40 @@ +use core::future::Future; +use core::pin::Pin; + +use async_trait::async_trait; +use futures_util::stream::{self, StreamExt}; + +use crate::relay::traits::components::auto_relayer::{AutoRelayer, CanAutoRelay}; +use crate::relay::traits::two_way::HasTwoWayRelay; +use crate::std_prelude::*; + +/// A concurrent two-way relay context that is composed of a `BiRelay` type that +/// can auto-relay between two connected targets. +/// +/// As opposed to the `ParallelTwoWayAutoRelay` variant, this concurrent variant +/// runs in a single thread and achieves concurrency via cooperative multi-tasking. +pub struct ConcurrentTwoWayAutoRelay; + +#[async_trait] +impl AutoRelayer for ConcurrentTwoWayAutoRelay +where + BiRelay: HasTwoWayRelay, + BiRelay::RelayAToB: CanAutoRelay, + BiRelay::RelayBToA: CanAutoRelay, +{ + async fn auto_relay(birelay: &BiRelay) -> Result<(), BiRelay::Error> { + let a_to_b_task: Pin + Send>> = Box::pin(async move { + let _ = birelay.relay_a_to_b().auto_relay().await; + }); + + let b_to_a_task: Pin + Send>> = Box::pin(async move { + let _ = birelay.relay_b_to_a().auto_relay().await; + }); + + stream::iter([a_to_b_task, b_to_a_task]) + .for_each_concurrent(None, |task| task) + .await; + + Ok(()) + } +} diff --git a/crates/relayer-components/src/relay/components/auto_relayers/mod.rs b/crates/relayer-components/src/relay/components/auto_relayers/mod.rs new file mode 100644 index 0000000000..b181b36792 --- /dev/null +++ b/crates/relayer-components/src/relay/components/auto_relayers/mod.rs @@ -0,0 +1,4 @@ +pub mod concurrent_bidirectional; +pub mod concurrent_event; +pub mod concurrent_two_way; +pub mod sequential_event; diff --git a/crates/relayer-components/src/relay/components/auto_relayers/sequential_event.rs b/crates/relayer-components/src/relay/components/auto_relayers/sequential_event.rs new file mode 100644 index 0000000000..38db301acc --- /dev/null +++ b/crates/relayer-components/src/relay/components/auto_relayers/sequential_event.rs @@ -0,0 +1,39 @@ +use async_trait::async_trait; +use futures_util::stream::StreamExt; + +use crate::chain::traits::event_subscription::HasEventSubscription; +use crate::relay::traits::components::auto_relayer::AutoRelayerWithTarget; +use crate::relay::traits::components::event_relayer::CanRelayEvent; +use crate::relay::traits::target::ChainTarget; +use crate::std_prelude::*; + +pub struct SequentialEventSubscriptionRelayer; + +#[async_trait] +impl AutoRelayerWithTarget for SequentialEventSubscriptionRelayer +where + Relay: CanRelayEvent, + Target: ChainTarget, + Target::TargetChain: HasEventSubscription, +{ + async fn auto_relay_with_target(relay: &Relay) -> Result<(), Relay::Error> { + let subscription = Target::target_chain(relay).event_subscription(); + + loop { + if let Some(event_stream) = subscription.subscribe().await { + // Use [`StreamExt::foreach`] to process the events sequentially. + event_stream + .for_each(|item| async move { + let (height, event) = item; + + // Ignore any relaying errors, as the relayer still needs to proceed + // relaying the next event regardless. + let _ = relay.relay_chain_event(&height, &event).await; + }) + .await; + } else { + return Ok(()); + } + } + } +} diff --git a/crates/relayer-components/src/relay/components/create_client.rs b/crates/relayer-components/src/relay/components/create_client.rs new file mode 100644 index 0000000000..3d7c89726e --- /dev/null +++ b/crates/relayer-components/src/relay/components/create_client.rs @@ -0,0 +1,69 @@ +use async_trait::async_trait; + +use crate::chain::traits::client::create::{ + CanBuildCreateClientMessage, CanBuildCreateClientPayload, HasCreateClientEvent, + HasCreateClientPayload, +}; +use crate::chain::traits::components::message_sender::CanSendSingleMessage; +use crate::relay::traits::chains::HasRelayChains; +use crate::relay::traits::components::client_creator::ClientCreator; +use crate::relay::traits::target::ChainTarget; +use crate::std_prelude::*; + +pub struct CreateClientWithChains; + +pub trait InjectMissingCreateClientEventError: HasRelayChains +where + Target: ChainTarget, +{ + fn missing_create_client_event_error( + target_chain: &Target::TargetChain, + counterparty_chain: &Target::CounterpartyChain, + ) -> Self::Error; +} + +#[async_trait] +impl ClientCreator + for CreateClientWithChains +where + Relay: InjectMissingCreateClientEventError, + Target: ChainTarget, + TargetChain: CanSendSingleMessage + + CanBuildCreateClientMessage + + HasCreateClientEvent, + CounterpartyChain: + CanBuildCreateClientPayload + HasCreateClientPayload, + TargetChain::ClientId: Clone, +{ + async fn create_client( + target_chain: &TargetChain, + counterparty_chain: &CounterpartyChain, + create_client_options: &CounterpartyChain::CreateClientPayloadOptions, + ) -> Result { + let payload = counterparty_chain + .build_create_client_payload(create_client_options) + .await + .map_err(Target::counterparty_chain_error)?; + + let message = target_chain + .build_create_client_message(payload) + .await + .map_err(Target::target_chain_error)?; + + let events = target_chain + .send_message(message) + .await + .map_err(Target::target_chain_error)?; + + let create_client_event = events + .into_iter() + .find_map(|event| TargetChain::try_extract_create_client_event(event)) + .ok_or_else(|| { + Relay::missing_create_client_event_error(target_chain, counterparty_chain) + })?; + + let client_id = TargetChain::create_client_event_client_id(&create_client_event); + + Ok(client_id.clone()) + } +} diff --git a/crates/relayer-components/src/relay/components/event_relayers/mod.rs b/crates/relayer-components/src/relay/components/event_relayers/mod.rs new file mode 100644 index 0000000000..c4e3356ca6 --- /dev/null +++ b/crates/relayer-components/src/relay/components/event_relayers/mod.rs @@ -0,0 +1 @@ +pub mod packet_event; diff --git a/crates/relayer-components/src/relay/components/event_relayers/packet_event.rs b/crates/relayer-components/src/relay/components/event_relayers/packet_event.rs new file mode 100644 index 0000000000..1585ef27c4 --- /dev/null +++ b/crates/relayer-components/src/relay/components/event_relayers/packet_event.rs @@ -0,0 +1,129 @@ +use async_trait::async_trait; + +use crate::chain::traits::types::ibc_events::send_packet::HasSendPacketEvent; +use crate::chain::traits::types::ibc_events::write_ack::{ + CanBuildPacketFromWriteAckEvent, HasWriteAcknowledgementEvent, +}; +use crate::chain::types::aliases::{Event, Height}; +use crate::logger::traits::level::HasBaseLogLevels; +use crate::relay::components::packet_filters::chain::{ + MatchPacketDestinationChain, MatchPacketSourceChain, +}; +use crate::relay::traits::components::event_relayer::EventRelayer; +use crate::relay::traits::components::packet_filter::{CanFilterPackets, PacketFilter}; +use crate::relay::traits::components::packet_relayer::CanRelayPacket; +use crate::relay::traits::components::packet_relayers::ack_packet::CanRelayAckPacket; +use crate::relay::traits::logs::logger::CanLogRelay; +use crate::relay::traits::logs::packet::CanLogRelayPacket; +use crate::relay::traits::packet_lock::HasPacketLock; +use crate::relay::traits::target::{DestinationTarget, SourceTarget}; +use crate::std_prelude::*; + +/** + A packet event relayer that performs packet relaying based on chain events + related to IBC packets. + + The implementation of `PacketEventRelayer` for the [`SourceTarget`] is + different from the [`DestinationTarget`]. This is because the packet + relaying operations from the source chain is different from the target chain. + + When relaying events from the source chain, the packet event relayer is + mostly interested in the `SendPacket` event, so that it can relay a + `RecvPacket` message to the destination chain, or a `TimeoutPacket` message + to the source chain. + + When relaying events from the destination chain, the packet event relayer + is mostly interested in the `WriteAcknowledgement` event, so that it can + relay a `AckPacket` message to the source chain. +*/ +pub struct PacketEventRelayer; + +#[async_trait] +impl EventRelayer for PacketEventRelayer +where + Relay: CanRelayPacket, + Relay::SrcChain: HasSendPacketEvent, + MatchPacketDestinationChain: PacketFilter, +{ + async fn relay_chain_event( + relay: &Relay, + _height: &Height, + event: &Event, + ) -> Result<(), Relay::Error> { + if let Some(send_packet_event) = Relay::SrcChain::try_extract_send_packet_event(event) { + let packet = Relay::SrcChain::extract_packet_from_send_packet_event(&send_packet_event); + + if MatchPacketDestinationChain::should_relay_packet(relay, &packet).await? { + relay.relay_packet(&packet).await?; + } + } + + Ok(()) + } +} + +#[async_trait] +impl EventRelayer for PacketEventRelayer +where + Relay: CanRelayAckPacket + CanFilterPackets + HasPacketLock + CanLogRelay + CanLogRelayPacket, + Relay::DstChain: CanBuildPacketFromWriteAckEvent, + MatchPacketSourceChain: PacketFilter, +{ + async fn relay_chain_event( + relay: &Relay, + height: &Height, + event: &Event, + ) -> Result<(), Relay::Error> { + let m_ack_event = Relay::DstChain::try_extract_write_acknowledgement_event(event); + + if let Some(ack_event) = m_ack_event { + let packet = Relay::DstChain::build_packet_from_write_acknowledgement_event(&ack_event); + + /* + First check whether the packet is targetted for the destination chain, + then use the packet filter in the relay context, as we skip `CanRelayPacket` + which would have done the packet filtering. + */ + if MatchPacketSourceChain::should_relay_packet(relay, packet).await? + && relay.should_relay_packet(packet).await? + { + let m_lock = relay.try_acquire_packet_lock(packet).await; + + /* + Only relay the ack packet if there isn't another packet relayer + trying to relay the same packet. This may happen because packet + relayers like `FullCycleRelayer` also relay the ack packet right + after it relays the recv packet. + + On the other hand, this event relayer relays based on the ack + event that is fired, which is independent of the main packet + relayer. Hence it has to use the packet lock to synchronize + with the other packet worker. + + Note that it is still necessary to handle event-based ack + relaying here, as we cannot just rely on the main packet + worker to relay the ack packet. It is also possible that the + relayer missed the send packet event, which gets relayed by + another relayer. In that case, we can still relay the ack + packet here based on the ack event. + */ + match m_lock { + Some(_lock) => { + relay.relay_ack_packet(height, packet, &ack_event).await?; + } + None => { + relay.log_relay( + Relay::Logger::LEVEL_TRACE, + "skip relaying ack packet, as another packet relayer has acquired the packet lock", + |log| { + log.field("packet", Relay::log_packet(packet)); + }, + ); + } + } + } + } + + Ok(()) + } +} diff --git a/crates/relayer-components/src/relay/components/message_senders/chain_sender.rs b/crates/relayer-components/src/relay/components/message_senders/chain_sender.rs new file mode 100644 index 0000000000..6d0a45d821 --- /dev/null +++ b/crates/relayer-components/src/relay/components/message_senders/chain_sender.rs @@ -0,0 +1,32 @@ +use async_trait::async_trait; + +use crate::chain::traits::components::message_sender::CanSendMessages; +use crate::chain::traits::types::ibc::HasIbcChainTypes; +use crate::relay::traits::chains::HasRelayChains; +use crate::relay::traits::components::ibc_message_sender::IbcMessageSender; +use crate::relay::traits::target::ChainTarget; +use crate::std_prelude::*; + +pub struct SendIbcMessagesToChain; + +#[async_trait] +impl IbcMessageSender + for SendIbcMessagesToChain +where + Relay: HasRelayChains, + Target: ChainTarget, + TargetChain: CanSendMessages, + TargetChain: HasIbcChainTypes, +{ + async fn send_messages( + relay: &Relay, + messages: Vec, + ) -> Result>, Relay::Error> { + let events = Target::target_chain(relay) + .send_messages(messages) + .await + .map_err(Target::target_chain_error)?; + + Ok(events) + } +} diff --git a/crates/relayer-components/src/relay/components/message_senders/mod.rs b/crates/relayer-components/src/relay/components/message_senders/mod.rs new file mode 100644 index 0000000000..41383d1be1 --- /dev/null +++ b/crates/relayer-components/src/relay/components/message_senders/mod.rs @@ -0,0 +1,2 @@ +pub mod chain_sender; +pub mod update_client; diff --git a/crates/relayer-components/src/relay/components/message_senders/update_client.rs b/crates/relayer-components/src/relay/components/message_senders/update_client.rs new file mode 100644 index 0000000000..aeeaaa0945 --- /dev/null +++ b/crates/relayer-components/src/relay/components/message_senders/update_client.rs @@ -0,0 +1,70 @@ +use alloc::collections::BTreeSet; + +use async_trait::async_trait; + +use crate::chain::traits::types::height::CanIncrementHeight; +use crate::chain::traits::types::ibc::{HasCounterpartyMessageHeight, HasIbcChainTypes}; +use crate::logger::traits::level::HasBaseLogLevels; +use crate::relay::traits::components::ibc_message_sender::IbcMessageSender; +use crate::relay::traits::components::update_client_message_builder::CanBuildUpdateClientMessage; +use crate::relay::traits::logs::logger::CanLogRelayTarget; +use crate::relay::traits::target::ChainTarget; +use crate::std_prelude::*; + +pub struct SendIbcMessagesWithUpdateClient(pub Sender); + +#[async_trait] +impl + IbcMessageSender for SendIbcMessagesWithUpdateClient +where + Relay: CanLogRelayTarget, + Target: ChainTarget, + InSender: IbcMessageSender, + TargetChain: HasIbcChainTypes, + TargetChain: HasCounterpartyMessageHeight, + CounterpartyChain: HasIbcChainTypes + CanIncrementHeight, + Relay: CanBuildUpdateClientMessage, +{ + async fn send_messages( + relay: &Relay, + messages: Vec, + ) -> Result>, Relay::Error> { + let update_heights: BTreeSet = messages + .iter() + .flat_map(|message| { + TargetChain::counterparty_message_height_for_update_client(message).into_iter() + }) + .collect(); + + let mut in_messages = Vec::new(); + + for update_height in update_heights { + let messages = relay + .build_update_client_messages(Target::default(), &update_height) + .await?; + + let messages_count = messages.len(); + + relay.log_relay_target( + Relay::Logger::LEVEL_TRACE, + "built update client messages for sending message at height", + |log| { + log.display("height", &update_height) + .display("messages_count", &messages_count); + }, + ); + + in_messages.extend(messages); + } + + let update_messages_count = in_messages.len(); + + in_messages.extend(messages); + + let in_events = InSender::send_messages(relay, in_messages).await?; + + let events = in_events.into_iter().skip(update_messages_count).collect(); + + Ok(events) + } +} diff --git a/crates/relayer-components/src/relay/components/mod.rs b/crates/relayer-components/src/relay/components/mod.rs new file mode 100644 index 0000000000..4a8d7c4d5e --- /dev/null +++ b/crates/relayer-components/src/relay/components/mod.rs @@ -0,0 +1,8 @@ +pub mod auto_relayers; +pub mod create_client; +pub mod event_relayers; +pub mod message_senders; +pub mod packet_clearers; +pub mod packet_filters; +pub mod packet_relayers; +pub mod update_client; diff --git a/crates/relayer-components/src/relay/components/packet_clearers/mod.rs b/crates/relayer-components/src/relay/components/packet_clearers/mod.rs new file mode 100644 index 0000000000..9281f09edf --- /dev/null +++ b/crates/relayer-components/src/relay/components/packet_clearers/mod.rs @@ -0,0 +1 @@ +pub mod receive_packet; diff --git a/crates/relayer-components/src/relay/components/packet_clearers/receive_packet.rs b/crates/relayer-components/src/relay/components/packet_clearers/receive_packet.rs new file mode 100644 index 0000000000..b4cd110d89 --- /dev/null +++ b/crates/relayer-components/src/relay/components/packet_clearers/receive_packet.rs @@ -0,0 +1,65 @@ +use async_trait::async_trait; +use futures_util::stream; +use futures_util::StreamExt; + +use crate::chain::traits::queries::packet_commitments::CanQueryPacketCommitments; +use crate::chain::traits::queries::send_packet::CanQuerySendPacketsFromSequences; +use crate::chain::traits::queries::unreceived_packets::CanQueryUnreceivedPacketSequences; +use crate::chain::types::aliases::{ChannelId, PortId}; +use crate::relay::traits::components::packet_clearer::PacketClearer; +use crate::relay::traits::components::packet_relayer::CanRelayPacket; +use crate::std_prelude::*; + +pub struct ClearReceivePackets; + +#[async_trait] +impl PacketClearer for ClearReceivePackets +where + Relay: CanRelayPacket, + Relay::DstChain: CanQueryUnreceivedPacketSequences, + Relay::SrcChain: CanQueryPacketCommitments + + CanQuerySendPacketsFromSequences, +{ + async fn clear_packets( + relay: &Relay, + src_channel_id: &ChannelId, + src_port_id: &PortId, + dst_channel_id: &ChannelId, + dst_port_id: &PortId, + ) -> Result<(), Relay::Error> { + let dst_chain = relay.dst_chain(); + let src_chain = relay.src_chain(); + + let (commitment_sequences, height) = src_chain + .query_packet_commitments(src_channel_id, src_port_id) + .await + .map_err(Relay::src_chain_error)?; + + let unreceived_sequences = dst_chain + .query_unreceived_packet_sequences(dst_channel_id, dst_port_id, &commitment_sequences) + .await + .map_err(Relay::dst_chain_error)?; + + let send_packets = src_chain + .query_send_packets_from_sequences( + src_channel_id, + src_port_id, + dst_channel_id, + dst_port_id, + &unreceived_sequences, + &height, + ) + .await + .map_err(Relay::src_chain_error)?; + + stream::iter(send_packets) + .for_each_concurrent(None, |t| async move { + // Ignore any relaying errors, as the relayer still needs to proceed + // relaying the next event regardless. + let _ = relay.relay_packet(&t).await; + }) + .await; + + Ok(()) + } +} diff --git a/crates/relayer-components/src/relay/components/packet_filters/allow_all.rs b/crates/relayer-components/src/relay/components/packet_filters/allow_all.rs new file mode 100644 index 0000000000..1d0b27ed3b --- /dev/null +++ b/crates/relayer-components/src/relay/components/packet_filters/allow_all.rs @@ -0,0 +1,20 @@ +use async_trait::async_trait; + +use crate::relay::traits::chains::HasRelayChains; +use crate::relay::traits::components::packet_filter::PacketFilter; +use crate::std_prelude::*; + +pub struct AllowAll; + +#[async_trait] +impl PacketFilter for AllowAll +where + Relay: HasRelayChains, +{ + async fn should_relay_packet( + _relay: &Relay, + _packet: &Relay::Packet, + ) -> Result { + Ok(true) + } +} diff --git a/crates/relayer-components/src/relay/components/packet_filters/and.rs b/crates/relayer-components/src/relay/components/packet_filters/and.rs new file mode 100644 index 0000000000..ca05fd9bac --- /dev/null +++ b/crates/relayer-components/src/relay/components/packet_filters/and.rs @@ -0,0 +1,28 @@ +use core::marker::PhantomData; + +use async_trait::async_trait; + +use crate::relay::traits::chains::HasRelayChains; +use crate::relay::traits::components::packet_filter::PacketFilter; +use crate::std_prelude::*; + +pub struct And(pub PhantomData<(FilterA, FilterB)>); + +#[async_trait] +impl PacketFilter for And +where + Relay: HasRelayChains, + FilterA: PacketFilter, + FilterB: PacketFilter, +{ + async fn should_relay_packet( + relay: &Relay, + packet: &Relay::Packet, + ) -> Result { + if FilterA::should_relay_packet(relay, packet).await? { + FilterB::should_relay_packet(relay, packet).await + } else { + Ok(false) + } + } +} diff --git a/crates/relayer-components/src/relay/components/packet_filters/chain.rs b/crates/relayer-components/src/relay/components/packet_filters/chain.rs new file mode 100644 index 0000000000..6ca0ee23f1 --- /dev/null +++ b/crates/relayer-components/src/relay/components/packet_filters/chain.rs @@ -0,0 +1,63 @@ +use async_trait::async_trait; + +use crate::chain::traits::queries::channel::CanQueryCounterpartyChainIdFromChannel; +use crate::chain::traits::types::chain_id::HasChainId; +use crate::relay::traits::components::packet_filter::PacketFilter; +use crate::relay::traits::packet::HasRelayPacketFields; +use crate::std_prelude::*; + +pub struct MatchPacketSourceChain; + +pub struct MatchPacketDestinationChain; + +#[async_trait] +impl PacketFilter for MatchPacketSourceChain +where + Relay: HasRelayPacketFields, + Relay::DstChain: CanQueryCounterpartyChainIdFromChannel, + Relay::SrcChain: HasChainId, +{ + async fn should_relay_packet( + relay: &Relay, + packet: &Relay::Packet, + ) -> Result { + let dst_channel_id = Relay::packet_dst_channel_id(packet); + let dst_port = Relay::packet_dst_port(packet); + + let src_chain_id = relay + .dst_chain() + .query_chain_id_from_channel_id(dst_channel_id, dst_port) + .await + .map_err(Relay::dst_chain_error)?; + + let same_chain = &src_chain_id == relay.src_chain().chain_id(); + + Ok(same_chain) + } +} + +#[async_trait] +impl PacketFilter for MatchPacketDestinationChain +where + Relay: HasRelayPacketFields, + Relay::SrcChain: CanQueryCounterpartyChainIdFromChannel, + Relay::DstChain: HasChainId, +{ + async fn should_relay_packet( + relay: &Relay, + packet: &Relay::Packet, + ) -> Result { + let src_channel_id = Relay::packet_src_channel_id(packet); + let src_port = Relay::packet_src_port(packet); + + let dst_chain_id = relay + .src_chain() + .query_chain_id_from_channel_id(src_channel_id, src_port) + .await + .map_err(Relay::src_chain_error)?; + + let same_chain = &dst_chain_id == relay.dst_chain().chain_id(); + + Ok(same_chain) + } +} diff --git a/crates/relayer-components/src/relay/components/packet_filters/mod.rs b/crates/relayer-components/src/relay/components/packet_filters/mod.rs new file mode 100644 index 0000000000..ba34697071 --- /dev/null +++ b/crates/relayer-components/src/relay/components/packet_filters/mod.rs @@ -0,0 +1,3 @@ +pub mod allow_all; +pub mod and; +pub mod chain; diff --git a/crates/relayer-components/src/relay/components/packet_relayers/ack/base_ack_packet.rs b/crates/relayer-components/src/relay/components/packet_relayers/ack/base_ack_packet.rs new file mode 100644 index 0000000000..f78976ec17 --- /dev/null +++ b/crates/relayer-components/src/relay/components/packet_relayers/ack/base_ack_packet.rs @@ -0,0 +1,62 @@ +use async_trait::async_trait; + +use crate::chain::traits::client::client_state::CanQueryClientState; +use crate::chain::traits::message_builders::ack_packet::{ + CanBuildAckPacketMessage, CanBuildAckPacketPayload, +}; +use crate::chain::traits::types::client_state::HasClientStateType; +use crate::chain::traits::types::packet::HasIbcPacketTypes; +use crate::core::traits::sync::Async; +use crate::relay::traits::chains::HasRelayChains; +use crate::relay::traits::components::ibc_message_sender::{CanSendSingleIbcMessage, MainSink}; +use crate::relay::traits::components::packet_relayers::ack_packet::AckPacketRelayer; +use crate::relay::traits::target::SourceTarget; +use crate::std_prelude::*; + +/// The minimal component that can send an acknowledgement packet. +/// Ack packet relayers with more capabilities can be implemented +/// on top of this base type. +pub struct BaseAckPacketRelayer; + +#[async_trait] +impl AckPacketRelayer for BaseAckPacketRelayer +where + Relay: HasRelayChains, + Relay: CanSendSingleIbcMessage, + SrcChain: CanQueryClientState + + CanBuildAckPacketMessage + + HasIbcPacketTypes, + DstChain: HasClientStateType + + CanBuildAckPacketPayload + + HasIbcPacketTypes, + Packet: Async, +{ + async fn relay_ack_packet( + relay: &Relay, + destination_height: &DstChain::Height, + packet: &Packet, + ack: &DstChain::WriteAcknowledgementEvent, + ) -> Result<(), Relay::Error> { + let src_client_state = relay + .src_chain() + .query_client_state(relay.src_client_id()) + .await + .map_err(Relay::src_chain_error)?; + + let payload = relay + .dst_chain() + .build_ack_packet_payload(&src_client_state, destination_height, packet, ack) + .await + .map_err(Relay::dst_chain_error)?; + + let message = relay + .src_chain() + .build_ack_packet_message(packet, payload) + .await + .map_err(Relay::src_chain_error)?; + + relay.send_message(SourceTarget, message).await?; + + Ok(()) + } +} diff --git a/crates/relayer-components/src/relay/components/packet_relayers/ack/mod.rs b/crates/relayer-components/src/relay/components/packet_relayers/ack/mod.rs new file mode 100644 index 0000000000..a2936a4f04 --- /dev/null +++ b/crates/relayer-components/src/relay/components/packet_relayers/ack/mod.rs @@ -0,0 +1 @@ +pub mod base_ack_packet; diff --git a/crates/relayer-components/src/relay/components/packet_relayers/general/filter_relayer.rs b/crates/relayer-components/src/relay/components/packet_relayers/general/filter_relayer.rs new file mode 100644 index 0000000000..4bf96a1c8b --- /dev/null +++ b/crates/relayer-components/src/relay/components/packet_relayers/general/filter_relayer.rs @@ -0,0 +1,26 @@ +use core::marker::PhantomData; + +use async_trait::async_trait; + +use crate::relay::traits::components::packet_filter::CanFilterPackets; +use crate::relay::traits::components::packet_relayer::PacketRelayer; +use crate::std_prelude::*; + +pub struct FilterRelayer { + pub phantom: PhantomData, +} + +#[async_trait] +impl PacketRelayer for FilterRelayer +where + Relay: CanFilterPackets, + InRelayer: PacketRelayer, +{ + async fn relay_packet(relay: &Relay, packet: &Relay::Packet) -> Result<(), Relay::Error> { + if relay.should_relay_packet(packet).await? { + InRelayer::relay_packet(relay, packet).await + } else { + Ok(()) + } + } +} diff --git a/crates/relayer-components/src/relay/components/packet_relayers/general/full_relay.rs b/crates/relayer-components/src/relay/components/packet_relayers/general/full_relay.rs new file mode 100644 index 0000000000..ec06f4e3e8 --- /dev/null +++ b/crates/relayer-components/src/relay/components/packet_relayers/general/full_relay.rs @@ -0,0 +1,109 @@ +use async_trait::async_trait; + +use crate::chain::traits::components::chain_status_querier::CanQueryChainStatus; +use crate::chain::traits::types::ibc_events::write_ack::HasWriteAcknowledgementEvent; +use crate::chain::traits::types::status::HasChainStatusType; +use crate::relay::traits::components::packet_relayer::PacketRelayer; +use crate::relay::traits::components::packet_relayers::ack_packet::CanRelayAckPacket; +use crate::relay::traits::components::packet_relayers::receive_packet::CanRelayReceivePacket; +use crate::relay::traits::components::packet_relayers::timeout_unordered_packet::CanRelayTimeoutUnorderedPacket; +use crate::relay::traits::logs::logger::CanLogRelay; +use crate::relay::traits::logs::packet::CanLogRelayPacket; +use crate::relay::traits::packet::HasRelayPacketFields; +use crate::relay::types::aliases::Packet; +use crate::std_prelude::*; + +pub struct FullCycleRelayer; + +#[async_trait] +impl PacketRelayer for FullCycleRelayer +where + Relay: CanLogRelay + + CanLogRelayPacket + + CanRelayAckPacket + + CanRelayReceivePacket + + CanRelayTimeoutUnorderedPacket + + HasRelayPacketFields, + Relay::SrcChain: CanQueryChainStatus, + Relay::DstChain: CanQueryChainStatus + HasWriteAcknowledgementEvent, +{ + async fn relay_packet(relay: &Relay, packet: &Packet) -> Result<(), Relay::Error> { + let destination_status = relay + .dst_chain() + .query_chain_status() + .await + .map_err(Relay::dst_chain_error)?; + + let destination_height = Relay::DstChain::chain_status_height(&destination_status); + let destination_timestamp = Relay::DstChain::chain_status_timestamp(&destination_status); + + let packet_timeout_height = Relay::packet_timeout_height(packet); + let packet_timeout_timestamp = Relay::packet_timeout_timestamp(packet); + + let has_packet_timed_out = if let Some(packet_timeout_height) = packet_timeout_height { + destination_height > packet_timeout_height + || destination_timestamp > packet_timeout_timestamp + } else { + destination_timestamp > packet_timeout_timestamp + }; + + if has_packet_timed_out { + relay.log_relay( + Default::default(), + "relaying timeout unordered packet", + |log| { + log.field("packet", Relay::log_packet(packet)); + }, + ); + + relay + .relay_timeout_unordered_packet(destination_height, packet) + .await?; + } else { + let src_chain_status = relay + .src_chain() + .query_chain_status() + .await + .map_err(Relay::src_chain_error)?; + + relay.log_relay(Default::default(), "relaying receive packet", |log| { + log.field("packet", Relay::log_packet(packet)); + }); + + let write_ack = relay + .relay_receive_packet( + Relay::SrcChain::chain_status_height(&src_chain_status), + packet, + ) + .await?; + + let destination_status = relay + .dst_chain() + .query_chain_status() + .await + .map_err(Relay::dst_chain_error)?; + + let destination_height = Relay::DstChain::chain_status_height(&destination_status); + + if let Some(ack) = write_ack { + relay.log_relay(Default::default(), "relaying ack packet", |log| { + log.field("packet", Relay::log_packet(packet)); + }); + + relay + .relay_ack_packet(destination_height, packet, &ack) + .await?; + } else { + relay.log_relay( + Default::default(), + "skip relaying ack packet due to lack of ack event", + |log| { + log.field("packet", Relay::log_packet(packet)); + }, + ); + } + } + + Ok(()) + } +} diff --git a/crates/relayer-components/src/relay/components/packet_relayers/general/lock.rs b/crates/relayer-components/src/relay/components/packet_relayers/general/lock.rs new file mode 100644 index 0000000000..484c44cd61 --- /dev/null +++ b/crates/relayer-components/src/relay/components/packet_relayers/general/lock.rs @@ -0,0 +1,46 @@ +use core::marker::PhantomData; + +use async_trait::async_trait; + +use crate::logger::traits::level::HasBaseLogLevels; +use crate::relay::traits::chains::HasRelayChains; +use crate::relay::traits::components::packet_relayer::PacketRelayer; +use crate::relay::traits::logs::logger::CanLogRelay; +use crate::relay::traits::logs::packet::CanLogRelayPacket; +use crate::relay::traits::packet_lock::HasPacketLock; +use crate::std_prelude::*; + +/** + Call the inner relayer only if the packet lock provided by [`HasPacketLock`] + is acquired. + + This is to avoid race condition where multiple packet relayers try to + relay the same packet at the same time. +*/ +pub struct LockPacketRelayer(pub PhantomData); + +#[async_trait] +impl PacketRelayer for LockPacketRelayer +where + Relay: HasRelayChains + HasPacketLock + CanLogRelay + CanLogRelayPacket, + InRelayer: PacketRelayer, +{ + async fn relay_packet(relay: &Relay, packet: &Relay::Packet) -> Result<(), Relay::Error> { + let m_lock = relay.try_acquire_packet_lock(packet).await; + + match m_lock { + Some(_lock) => InRelayer::relay_packet(relay, packet).await, + None => { + relay.log_relay( + Relay::Logger::LEVEL_TRACE, + "skip relaying packet, as another packet relayer has acquired the packet lock", + |log| { + log.field("packet", Relay::log_packet(packet)); + }, + ); + + Ok(()) + } + } + } +} diff --git a/crates/relayer-components/src/relay/components/packet_relayers/general/log.rs b/crates/relayer-components/src/relay/components/packet_relayers/general/log.rs new file mode 100644 index 0000000000..b44b5c259a --- /dev/null +++ b/crates/relayer-components/src/relay/components/packet_relayers/general/log.rs @@ -0,0 +1,44 @@ +use core::marker::PhantomData; + +use async_trait::async_trait; + +use crate::logger::traits::level::HasBaseLogLevels; +use crate::relay::traits::chains::HasRelayChains; +use crate::relay::traits::components::packet_relayer::PacketRelayer; +use crate::relay::traits::logs::logger::CanLogRelay; +use crate::relay::traits::logs::packet::CanLogRelayPacket; +use crate::std_prelude::*; + +pub struct LoggerRelayer(pub PhantomData); + +#[async_trait] +impl PacketRelayer for LoggerRelayer +where + Relay: HasRelayChains + CanLogRelay + CanLogRelayPacket, + InRelayer: PacketRelayer, +{ + async fn relay_packet(relay: &Relay, packet: &Relay::Packet) -> Result<(), Relay::Error> { + relay.log_relay(Default::default(), "starting to relay packet", |log| { + log.field("packet", Relay::log_packet(packet)); + }); + + let res = InRelayer::relay_packet(relay, packet).await; + + if let Err(e) = &res { + relay.log_relay( + Relay::Logger::LEVEL_ERROR, + "failed to relay packet", + |log| { + log.field("packet", Relay::log_packet(packet)) + .debug("error", e); + }, + ); + } else { + relay.log_relay(Default::default(), "successfully relayed packet", |log| { + log.field("packet", Relay::log_packet(packet)); + }); + } + + res + } +} diff --git a/crates/relayer-components/src/relay/components/packet_relayers/general/mod.rs b/crates/relayer-components/src/relay/components/packet_relayers/general/mod.rs new file mode 100644 index 0000000000..838c43487b --- /dev/null +++ b/crates/relayer-components/src/relay/components/packet_relayers/general/mod.rs @@ -0,0 +1,4 @@ +pub mod filter_relayer; +pub mod full_relay; +pub mod lock; +pub mod log; diff --git a/crates/relayer-components/src/relay/components/packet_relayers/mod.rs b/crates/relayer-components/src/relay/components/packet_relayers/mod.rs new file mode 100644 index 0000000000..5b4d0cc0b7 --- /dev/null +++ b/crates/relayer-components/src/relay/components/packet_relayers/mod.rs @@ -0,0 +1,4 @@ +pub mod ack; +pub mod general; +pub mod receive; +pub mod timeout_unordered; diff --git a/crates/relayer-components/src/relay/components/packet_relayers/receive/base_receive_packet.rs b/crates/relayer-components/src/relay/components/packet_relayers/receive/base_receive_packet.rs new file mode 100644 index 0000000000..edc8fa8857 --- /dev/null +++ b/crates/relayer-components/src/relay/components/packet_relayers/receive/base_receive_packet.rs @@ -0,0 +1,58 @@ +use async_trait::async_trait; + +use crate::chain::traits::client::client_state::CanQueryClientState; +use crate::chain::traits::message_builders::receive_packet::{ + CanBuildReceivePacketMessage, CanBuildReceivePacketPayload, +}; +use crate::chain::traits::types::ibc_events::write_ack::HasWriteAcknowledgementEvent; +use crate::chain::types::aliases::Height; +use crate::relay::traits::chains::HasRelayChains; +use crate::relay::traits::components::ibc_message_sender::{CanSendSingleIbcMessage, MainSink}; +use crate::relay::traits::components::packet_relayers::receive_packet::ReceivePacketRelayer; +use crate::relay::traits::target::DestinationTarget; +use crate::relay::types::aliases::Packet; +use crate::std_prelude::*; + +pub struct BaseReceivePacketRelayer; + +#[async_trait] +impl ReceivePacketRelayer for BaseReceivePacketRelayer +where + Relay: HasRelayChains + CanSendSingleIbcMessage, + Relay::SrcChain: CanBuildReceivePacketPayload, + Relay::DstChain: CanQueryClientState + + CanBuildReceivePacketMessage + + HasWriteAcknowledgementEvent, +{ + async fn relay_receive_packet( + relay: &Relay, + source_height: &Height, + packet: &Packet, + ) -> Result, Relay::Error> { + let src_client_state = relay + .dst_chain() + .query_client_state(relay.dst_client_id()) + .await + .map_err(Relay::dst_chain_error)?; + + let payload = relay + .src_chain() + .build_receive_packet_payload(&src_client_state, source_height, packet) + .await + .map_err(Relay::src_chain_error)?; + + let message = relay + .dst_chain() + .build_receive_packet_message(packet, payload) + .await + .map_err(Relay::dst_chain_error)?; + + let events = relay.send_message(DestinationTarget, message).await?; + + let ack_event = events + .iter() + .find_map(Relay::DstChain::try_extract_write_acknowledgement_event); + + Ok(ack_event) + } +} diff --git a/crates/relayer-components/src/relay/components/packet_relayers/receive/mod.rs b/crates/relayer-components/src/relay/components/packet_relayers/receive/mod.rs new file mode 100644 index 0000000000..3356715180 --- /dev/null +++ b/crates/relayer-components/src/relay/components/packet_relayers/receive/mod.rs @@ -0,0 +1,2 @@ +pub mod base_receive_packet; +pub mod skip_received_packet; diff --git a/crates/relayer-components/src/relay/components/packet_relayers/receive/skip_received_packet.rs b/crates/relayer-components/src/relay/components/packet_relayers/receive/skip_received_packet.rs new file mode 100644 index 0000000000..dcdb41edfb --- /dev/null +++ b/crates/relayer-components/src/relay/components/packet_relayers/receive/skip_received_packet.rs @@ -0,0 +1,46 @@ +use core::marker::PhantomData; + +use async_trait::async_trait; + +use crate::chain::traits::queries::received_packet::CanQueryReceivedPacket; +use crate::chain::traits::types::ibc_events::write_ack::HasWriteAcknowledgementEvent; +use crate::chain::types::aliases::{Height, WriteAcknowledgementEvent}; +use crate::relay::traits::components::packet_relayers::receive_packet::ReceivePacketRelayer; +use crate::relay::traits::packet::HasRelayPacketFields; +use crate::std_prelude::*; + +pub struct SkipReceivedPacketRelayer { + pub phantom: PhantomData, +} + +#[async_trait] +impl ReceivePacketRelayer for SkipReceivedPacketRelayer +where + Relay: HasRelayPacketFields, + Relayer: ReceivePacketRelayer, + Relay::DstChain: HasWriteAcknowledgementEvent, + Relay::DstChain: CanQueryReceivedPacket, +{ + async fn relay_receive_packet( + relay: &Relay, + source_height: &Height, + packet: &Relay::Packet, + ) -> Result>, Relay::Error> + { + let is_packet_received = relay + .dst_chain() + .query_is_packet_received( + Relay::packet_dst_port(packet), + Relay::packet_dst_channel_id(packet), + Relay::packet_sequence(packet), + ) + .await + .map_err(Relay::dst_chain_error)?; + + if !is_packet_received { + Relayer::relay_receive_packet(relay, source_height, packet).await + } else { + Ok(None) + } + } +} diff --git a/crates/relayer-components/src/relay/components/packet_relayers/timeout_unordered/mod.rs b/crates/relayer-components/src/relay/components/packet_relayers/timeout_unordered/mod.rs new file mode 100644 index 0000000000..563a486874 --- /dev/null +++ b/crates/relayer-components/src/relay/components/packet_relayers/timeout_unordered/mod.rs @@ -0,0 +1 @@ +pub mod timeout_unordered_packet; diff --git a/crates/relayer-components/src/relay/components/packet_relayers/timeout_unordered/timeout_unordered_packet.rs b/crates/relayer-components/src/relay/components/packet_relayers/timeout_unordered/timeout_unordered_packet.rs new file mode 100644 index 0000000000..c037a94768 --- /dev/null +++ b/crates/relayer-components/src/relay/components/packet_relayers/timeout_unordered/timeout_unordered_packet.rs @@ -0,0 +1,56 @@ +use async_trait::async_trait; + +use crate::chain::traits::client::client_state::CanQueryClientState; +use crate::chain::traits::message_builders::timeout_unordered_packet::{ + CanBuildTimeoutUnorderedPacketMessage, CanBuildTimeoutUnorderedPacketPayload, +}; +use crate::chain::types::aliases::Height; +use crate::relay::traits::chains::HasRelayChains; +use crate::relay::traits::components::ibc_message_sender::{CanSendSingleIbcMessage, MainSink}; +use crate::relay::traits::components::packet_relayers::timeout_unordered_packet::TimeoutUnorderedPacketRelayer; +use crate::relay::traits::target::SourceTarget; +use crate::relay::types::aliases::Packet; +use crate::std_prelude::*; + +/// The minimal component that implements timeout packet relayer +/// capabilities. Timeout packet relayers with more capabilities can be +/// implemented on top of this base type. +pub struct BaseTimeoutUnorderedPacketRelayer; + +#[async_trait] +impl TimeoutUnorderedPacketRelayer for BaseTimeoutUnorderedPacketRelayer +where + Relay: HasRelayChains, + Relay: CanSendSingleIbcMessage, + Relay::SrcChain: CanQueryClientState + + CanBuildTimeoutUnorderedPacketMessage, + Relay::DstChain: CanBuildTimeoutUnorderedPacketPayload, +{ + async fn relay_timeout_unordered_packet( + relay: &Relay, + destination_height: &Height, + packet: &Packet, + ) -> Result<(), Relay::Error> { + let dst_client_state = relay + .src_chain() + .query_client_state(relay.src_client_id()) + .await + .map_err(Relay::src_chain_error)?; + + let payload = relay + .dst_chain() + .build_timeout_unordered_packet_payload(&dst_client_state, destination_height, packet) + .await + .map_err(Relay::dst_chain_error)?; + + let message = relay + .src_chain() + .build_timeout_unordered_packet_message(packet, payload) + .await + .map_err(Relay::src_chain_error)?; + + relay.send_message(SourceTarget, message).await?; + + Ok(()) + } +} diff --git a/crates/relayer-components/src/relay/components/update_client/build.rs b/crates/relayer-components/src/relay/components/update_client/build.rs new file mode 100644 index 0000000000..2d88425a6a --- /dev/null +++ b/crates/relayer-components/src/relay/components/update_client/build.rs @@ -0,0 +1,84 @@ +use async_trait::async_trait; + +use crate::chain::traits::client::client_state::CanQueryClientState; +use crate::chain::traits::client::consensus_state::CanFindConsensusStateHeight; +use crate::chain::traits::client::update::{ + CanBuildUpdateClientMessage, CanBuildUpdateClientPayload, +}; +use crate::chain::traits::types::client_state::HasClientStateFields; +use crate::relay::traits::chains::HasRelayChains; +use crate::relay::traits::components::update_client_message_builder::UpdateClientMessageBuilder; +use crate::relay::traits::target::ChainTarget; +use crate::std_prelude::*; + +pub struct BuildUpdateClientMessages; + +#[async_trait] +impl UpdateClientMessageBuilder + for BuildUpdateClientMessages +where + Relay: HasRelayChains, + Target: ChainTarget, + TargetChain: CanQueryClientState + + CanBuildUpdateClientMessage + + CanFindConsensusStateHeight, + CounterpartyChain: CanBuildUpdateClientPayload + HasClientStateFields, + CounterpartyChain::Height: Clone, +{ + async fn build_update_client_messages( + relay: &Relay, + _target: Target, + target_height: &CounterpartyChain::Height, + ) -> Result, Relay::Error> { + let target_client_id = Target::target_client_id(relay); + + let target_chain = Target::target_chain(relay); + let counterparty_chain = Target::counterparty_chain(relay); + + let client_state = target_chain + .query_client_state(target_client_id) + .await + .map_err(Target::target_chain_error)?; + + let client_state_height = CounterpartyChain::client_state_latest_height(&client_state); + + // If the client state height is already the same as target height, then there + // is no need to build any UpdateClient message + if client_state_height == target_height { + return Ok(Vec::new()); + } + + let trusted_height = if client_state_height < target_height { + // If the client state height is less than the target height, we can use that + // as a base trust height to build our UpdateClient headers. + client_state_height.clone() + } else { + // If the client state height is greater than the target height, it means we + // have to find a previous consensus height that is less than the target height. + let consensus_state_height = target_chain + .find_consensus_state_height_before(target_client_id, target_height) + .await + .map_err(Target::target_chain_error)?; + + // If we happen to find a consensus height that matches the target height, + // then there is no need to build any UpdateClient message. + if &consensus_state_height == target_height { + return Ok(Vec::new()); + } + + consensus_state_height + }; + + let update_payload = counterparty_chain + .build_update_client_payload(&trusted_height, target_height, client_state) + .await + .map_err(Target::counterparty_chain_error)?; + + let messages = target_chain + .build_update_client_message(target_client_id, update_payload) + .await + .map_err(Target::target_chain_error)?; + + Ok(messages) + } +} diff --git a/crates/relayer-components/src/relay/components/update_client/mod.rs b/crates/relayer-components/src/relay/components/update_client/mod.rs new file mode 100644 index 0000000000..9a20ef44f3 --- /dev/null +++ b/crates/relayer-components/src/relay/components/update_client/mod.rs @@ -0,0 +1,3 @@ +pub mod build; +pub mod skip; +pub mod wait; diff --git a/crates/relayer-components/src/relay/components/update_client/skip.rs b/crates/relayer-components/src/relay/components/update_client/skip.rs new file mode 100644 index 0000000000..ecaf5fa8a5 --- /dev/null +++ b/crates/relayer-components/src/relay/components/update_client/skip.rs @@ -0,0 +1,55 @@ +use core::marker::PhantomData; + +use async_trait::async_trait; + +use crate::chain::traits::components::consensus_state_querier::CanQueryConsensusState; +use crate::chain::traits::types::consensus_state::HasConsensusStateType; +use crate::chain::traits::types::height::HasHeightType; +use crate::chain::types::aliases::Height; +use crate::logger::traits::level::HasBaseLogLevels; +use crate::relay::traits::chains::HasRelayChains; +use crate::relay::traits::components::update_client_message_builder::UpdateClientMessageBuilder; +use crate::relay::traits::logs::logger::CanLogRelayTarget; +use crate::relay::traits::target::ChainTarget; +use crate::std_prelude::*; + +pub struct SkipUpdateClient(PhantomData); + +#[async_trait] +impl + UpdateClientMessageBuilder for SkipUpdateClient +where + Relay: HasRelayChains + CanLogRelayTarget, + Target: ChainTarget, + InUpdateClient: UpdateClientMessageBuilder, + CounterpartyChain: HasConsensusStateType + HasHeightType, + TargetChain: CanQueryConsensusState, +{ + async fn build_update_client_messages( + relay: &Relay, + target: Target, + height: &Height, + ) -> Result, Relay::Error> { + let target_chain = Target::target_chain(relay); + let target_client_id = Target::target_client_id(relay); + + let consensus_state = target_chain + .query_consensus_state(target_client_id, height) + .await; + + match consensus_state { + Ok(_) => { + relay.log_relay_target( + Relay::Logger::LEVEL_TRACE, + "skip building update client message, as the target chain already has one at given height", + |log| { + log.display("target_height", height); + }, + ); + + Ok(Vec::new()) + } + Err(_) => InUpdateClient::build_update_client_messages(relay, target, height).await, + } + } +} diff --git a/crates/relayer-components/src/relay/components/update_client/wait.rs b/crates/relayer-components/src/relay/components/update_client/wait.rs new file mode 100644 index 0000000000..28af49b4ca --- /dev/null +++ b/crates/relayer-components/src/relay/components/update_client/wait.rs @@ -0,0 +1,65 @@ +use core::marker::PhantomData; + +use async_trait::async_trait; + +use crate::chain::traits::types::ibc::HasIbcChainTypes; +use crate::chain::traits::wait::CanWaitChainReachHeight; +use crate::logger::traits::level::HasBaseLogLevels; +use crate::relay::traits::chains::HasRelayChains; +use crate::relay::traits::components::update_client_message_builder::UpdateClientMessageBuilder; +use crate::relay::traits::logs::logger::CanLogRelayTarget; +use crate::relay::traits::target::ChainTarget; +use crate::std_prelude::*; + +/** + Wait for the chain to reach a height that is greater than or equal the required height, + so that the update client proof can be built. +*/ +pub struct WaitUpdateClient(PhantomData); + +#[async_trait] +impl + UpdateClientMessageBuilder for WaitUpdateClient +where + Relay: HasRelayChains + CanLogRelayTarget, + Target: ChainTarget, + InUpdateClient: UpdateClientMessageBuilder, + TargetChain: HasIbcChainTypes, + CounterpartyChain: CanWaitChainReachHeight + HasIbcChainTypes, +{ + async fn build_update_client_messages( + relay: &Relay, + target: Target, + height: &CounterpartyChain::Height, + ) -> Result, Relay::Error> { + let counterparty_chain = Target::counterparty_chain(relay); + + relay.log_relay_target( + Relay::Logger::LEVEL_TRACE, + "waiting for counterparty chain to reach height", + |log| { + log.display("target_height", height); + }, + ); + + // We wait for the chain to reach the target height, which may have not been reached + // when IBC messages are built. This is because proofs build at a latest height would + // require the chain to progress at least one more height before the update client + // message can be built. + let current_height = counterparty_chain + .wait_chain_reach_height(height) + .await + .map_err(Target::counterparty_chain_error)?; + + relay.log_relay_target( + Relay::Logger::LEVEL_TRACE, + "counterparty chain's height is now greater than or equal to target height", + |log| { + log.display("target_height", height) + .display("currrent_height", ¤t_height); + }, + ); + + return InUpdateClient::build_update_client_messages(relay, target, height).await; + } +} diff --git a/crates/relayer-components/src/relay/impls/channel/bootstrap.rs b/crates/relayer-components/src/relay/impls/channel/bootstrap.rs new file mode 100644 index 0000000000..2bcb86ce75 --- /dev/null +++ b/crates/relayer-components/src/relay/impls/channel/bootstrap.rs @@ -0,0 +1,64 @@ +use async_trait::async_trait; + +use crate::chain::traits::types::channel::HasInitChannelOptionsType; +use crate::chain::traits::types::ibc::HasIbcChainTypes; +use crate::relay::traits::chains::HasRelayChains; +use crate::relay::traits::channel::open_handshake::CanRelayChannelOpenHandshake; +use crate::relay::traits::channel::open_init::CanInitChannel; +use crate::relay::types::aliases::{DstChannelId, DstPortId, SrcChannelId, SrcPortId}; +use crate::std_prelude::*; + +/** + This is an autotrait implementation by the relay context to allow bootstrapping + of new IBC channels as initiated by the relayer. + + This can be used by the users of the relayer to create new channels. It can + also be used in integration tests to create new channels. + + Note that this should _not_ be used when relaying channel creation that + are initiated by external users. For that purpose, use + [`RelayChannelOpenHandshake`](crate::relay::impls::channel::open_handshake::RelayChannelOpenHandshake), + which would reuse the given channel ID instead of creating new ones. +*/ + +#[async_trait] +pub trait CanBootstrapChannel: HasRelayChains +where + Self::SrcChain: HasInitChannelOptionsType, +{ + async fn bootstrap_channel( + &self, + src_port_id: &SrcPortId, + dst_port_id: &DstPortId, + init_channel_options: &>::InitChannelOptions, + ) -> Result<(SrcChannelId, DstChannelId), Self::Error>; +} + +#[async_trait] +impl CanBootstrapChannel for Relay +where + Relay: HasRelayChains + + CanInitChannel + + CanRelayChannelOpenHandshake, + SrcChain: HasInitChannelOptionsType, + DstChain: HasIbcChainTypes, +{ + async fn bootstrap_channel( + &self, + src_port_id: &SrcPortId, + dst_port_id: &DstPortId, + init_channel_options: &SrcChain::InitChannelOptions, + ) -> Result<(SrcChain::ChannelId, DstChain::ChannelId), Self::Error> { + let src_channel_id = self + .init_channel(src_port_id, dst_port_id, init_channel_options) + .await?; + + let dst_channel_id = self + .relay_channel_open_handshake(&src_channel_id, src_port_id, dst_port_id) + .await?; + + Ok((src_channel_id, dst_channel_id)) + } +} diff --git a/crates/relayer-components/src/relay/impls/channel/mod.rs b/crates/relayer-components/src/relay/impls/channel/mod.rs new file mode 100644 index 0000000000..f0aa1d0667 --- /dev/null +++ b/crates/relayer-components/src/relay/impls/channel/mod.rs @@ -0,0 +1,6 @@ +pub mod bootstrap; +pub mod open_ack; +pub mod open_confirm; +pub mod open_handshake; +pub mod open_init; +pub mod open_try; diff --git a/crates/relayer-components/src/relay/impls/channel/open_ack.rs b/crates/relayer-components/src/relay/impls/channel/open_ack.rs new file mode 100644 index 0000000000..d6ae819d46 --- /dev/null +++ b/crates/relayer-components/src/relay/impls/channel/open_ack.rs @@ -0,0 +1,82 @@ +use async_trait::async_trait; + +use crate::chain::traits::client::client_state::CanQueryClientState; +use crate::chain::traits::components::chain_status_querier::CanQueryChainHeight; +use crate::chain::traits::message_builders::channel::{ + CanBuildChannelHandshakeMessages, CanBuildChannelHandshakePayloads, +}; +use crate::relay::traits::chains::HasRelayChains; +use crate::relay::traits::channel::open_ack::ChannelOpenAckRelayer; +use crate::relay::traits::components::ibc_message_sender::{CanSendSingleIbcMessage, MainSink}; +use crate::relay::traits::target::SourceTarget; +use crate::relay::types::aliases::{DstChannelId, DstPortId, SrcChannelId, SrcPortId}; +use crate::std_prelude::*; + +/** + A base implementation of [`ChannelOpenAckRelayer`] that relays a new channel + at the destination chain that is in `OPEN_TRY` state, and submits it as a + `ChannelOpenAck` message to the destination chain. + + This implements the `ChanOpenAck` step of the IBC channel handshake protocol. + + Note that this implementation does not check that the channel exists on + the destination chain. It also doesn't check that the channel end at the + destination chain is really in the `OPEN_TRY` state. This will be implemented + as a separate wrapper component. (TODO) +*/ + +pub struct RelayChannelOpenAck; + +#[async_trait] +impl ChannelOpenAckRelayer for RelayChannelOpenAck +where + Relay: HasRelayChains + + CanSendSingleIbcMessage, + SrcChain: CanQueryClientState + CanBuildChannelHandshakeMessages, + DstChain: CanQueryChainHeight + CanBuildChannelHandshakePayloads, +{ + async fn relay_channel_open_ack( + relay: &Relay, + src_port_id: &SrcPortId, + src_channel_id: &SrcChannelId, + dst_port_id: &DstPortId, + dst_channel_id: &DstChannelId, + ) -> Result<(), Relay::Error> { + let src_chain = relay.src_chain(); + let dst_chain = relay.dst_chain(); + + let dst_proof_height = dst_chain + .query_chain_height() + .await + .map_err(Relay::dst_chain_error)?; + + let dst_client_state = src_chain + .query_client_state(relay.src_client_id()) + .await + .map_err(Relay::src_chain_error)?; + + let open_ack_payload = dst_chain + .build_channel_open_ack_payload( + &dst_client_state, + &dst_proof_height, + dst_port_id, + dst_channel_id, + ) + .await + .map_err(Relay::dst_chain_error)?; + + let open_ack_message = src_chain + .build_channel_open_ack_message( + src_port_id, + src_channel_id, + dst_channel_id, + open_ack_payload, + ) + .await + .map_err(Relay::src_chain_error)?; + + relay.send_message(SourceTarget, open_ack_message).await?; + + Ok(()) + } +} diff --git a/crates/relayer-components/src/relay/impls/channel/open_confirm.rs b/crates/relayer-components/src/relay/impls/channel/open_confirm.rs new file mode 100644 index 0000000000..09f27f45e5 --- /dev/null +++ b/crates/relayer-components/src/relay/impls/channel/open_confirm.rs @@ -0,0 +1,79 @@ +use async_trait::async_trait; + +use crate::chain::traits::client::client_state::CanQueryClientState; +use crate::chain::traits::components::chain_status_querier::CanQueryChainHeight; +use crate::chain::traits::message_builders::channel::{ + CanBuildChannelHandshakeMessages, CanBuildChannelHandshakePayloads, +}; +use crate::relay::traits::chains::HasRelayChains; +use crate::relay::traits::channel::open_confirm::ChannelOpenConfirmRelayer; +use crate::relay::traits::components::ibc_message_sender::{CanSendSingleIbcMessage, MainSink}; +use crate::relay::traits::target::DestinationTarget; +use crate::relay::types::aliases::{DstChannelId, DstPortId, SrcChannelId, SrcPortId}; +use crate::std_prelude::*; + +/** + A base implementation of [`ChannelOpenConfirmRelayer`] that relays a new channel + at the source chain that is in `OPEN` state, and submits it as a + `ChannelOpenConfirm` message to the destination chain. + + This implements the `ChanOpenConfirm` step of the IBC channel handshake protocol. + + Note that this implementation does not check that the channel exists on + the destination chain, that a channel exists on the source chain, and that the + channel end at the source chain is really in the `OPEN` state. This will be implemented + as a separate wrapper component. (TODO) +*/ + +pub struct RelayChannelOpenConfirm; + +#[async_trait] +impl ChannelOpenConfirmRelayer for RelayChannelOpenConfirm +where + Relay: HasRelayChains + + CanSendSingleIbcMessage, + SrcChain: CanQueryChainHeight + CanBuildChannelHandshakePayloads, + DstChain: CanQueryClientState + CanBuildChannelHandshakeMessages, +{ + async fn relay_channel_open_confirm( + relay: &Relay, + dst_port_id: &DstPortId, + dst_channel_id: &DstChannelId, + src_port_id: &SrcPortId, + src_channel_id: &SrcChannelId, + ) -> Result<(), Relay::Error> { + let src_chain = relay.src_chain(); + let dst_chain = relay.dst_chain(); + + let src_proof_height = src_chain + .query_chain_height() + .await + .map_err(Relay::src_chain_error)?; + + let src_client_state = dst_chain + .query_client_state(relay.dst_client_id()) + .await + .map_err(Relay::dst_chain_error)?; + + let open_confirm_payload = src_chain + .build_channel_open_confirm_payload( + &src_client_state, + &src_proof_height, + src_port_id, + src_channel_id, + ) + .await + .map_err(Relay::src_chain_error)?; + + let open_confirm_message = dst_chain + .build_channel_open_confirm_message(dst_port_id, dst_channel_id, open_confirm_payload) + .await + .map_err(Relay::dst_chain_error)?; + + relay + .send_message(DestinationTarget, open_confirm_message) + .await?; + + Ok(()) + } +} diff --git a/crates/relayer-components/src/relay/impls/channel/open_handshake.rs b/crates/relayer-components/src/relay/impls/channel/open_handshake.rs new file mode 100644 index 0000000000..46365d7de1 --- /dev/null +++ b/crates/relayer-components/src/relay/impls/channel/open_handshake.rs @@ -0,0 +1,55 @@ +use async_trait::async_trait; + +use crate::chain::traits::types::ibc::HasIbcChainTypes; +use crate::relay::traits::chains::HasRelayChains; +use crate::relay::traits::channel::open_ack::CanRelayChannelOpenAck; +use crate::relay::traits::channel::open_confirm::CanRelayChannelOpenConfirm; +use crate::relay::traits::channel::open_handshake::ChannelOpenHandshakeRelayer; +use crate::relay::traits::channel::open_try::CanRelayChannelOpenTry; +use crate::std_prelude::*; + +/** + Relays a channel open handshake using a channel ID that has been + initialized at the source chain, and the port IDs used. + + Specifically, the `ChanOpenTry`, `ChanOpenAck`, and `ChanOpenConfirm` steps of + the handshake protocol are performed between both chains. Upon successful + completion of the handshake protocol, a channel will have been established + between both chains. + + This can be used for relaying of channels that are created by external + users. +*/ +pub struct RelayChannelOpenHandshake; + +#[async_trait] +impl ChannelOpenHandshakeRelayer for RelayChannelOpenHandshake +where + Relay: HasRelayChains + + CanRelayChannelOpenTry + + CanRelayChannelOpenAck + + CanRelayChannelOpenConfirm, + SrcChain: HasIbcChainTypes, + DstChain: HasIbcChainTypes, +{ + async fn relay_channel_open_handshake( + relay: &Relay, + src_channel_id: &SrcChain::ChannelId, + src_port_id: &SrcChain::PortId, + dst_port_id: &DstChain::PortId, + ) -> Result { + let dst_channel_id = relay + .relay_channel_open_try(dst_port_id, src_port_id, src_channel_id) + .await?; + + relay + .relay_channel_open_ack(src_port_id, src_channel_id, dst_port_id, &dst_channel_id) + .await?; + + relay + .relay_channel_open_confirm(dst_port_id, &dst_channel_id, src_port_id, src_channel_id) + .await?; + + Ok(dst_channel_id) + } +} diff --git a/crates/relayer-components/src/relay/impls/channel/open_init.rs b/crates/relayer-components/src/relay/impls/channel/open_init.rs new file mode 100644 index 0000000000..05ce11347c --- /dev/null +++ b/crates/relayer-components/src/relay/impls/channel/open_init.rs @@ -0,0 +1,65 @@ +use async_trait::async_trait; + +use crate::chain::traits::components::message_sender::CanSendSingleMessage; +use crate::chain::traits::message_builders::channel::{ + CanBuildChannelHandshakeMessages, CanBuildChannelHandshakePayloads, +}; +use crate::chain::traits::types::channel::HasInitChannelOptionsType; +use crate::chain::traits::types::ibc_events::channel::HasChannelOpenInitEvent; +use crate::relay::traits::chains::HasRelayChains; +use crate::relay::traits::channel::open_init::ChannelInitializer; +use crate::std_prelude::*; + +pub trait InjectMissingChannelInitEventError: HasRelayChains { + fn missing_channel_init_event_error(&self) -> Self::Error; +} + +/** + A base implementation for [`ChannelInitializer`] which submits a + `ChannelOpenInit` message to the source chain. + + This implements the `ChanOpenInit` step in the IBC channel handshake protocol. +*/ + +pub struct InitializeChannel; + +#[async_trait] +impl ChannelInitializer for InitializeChannel +where + Relay: HasRelayChains + + InjectMissingChannelInitEventError, + SrcChain: CanSendSingleMessage + + HasInitChannelOptionsType + + CanBuildChannelHandshakeMessages + + HasChannelOpenInitEvent, + DstChain: CanBuildChannelHandshakePayloads, + SrcChain::ChannelId: Clone, +{ + async fn init_channel( + relay: &Relay, + src_port_id: &SrcChain::PortId, + dst_port_id: &DstChain::PortId, + init_channel_options: &SrcChain::InitChannelOptions, + ) -> Result { + let src_chain = relay.src_chain(); + + let src_message = src_chain + .build_channel_open_init_message(src_port_id, dst_port_id, init_channel_options) + .await + .map_err(Relay::src_chain_error)?; + + let events = src_chain + .send_message(src_message) + .await + .map_err(Relay::src_chain_error)?; + + let open_init_event = events + .into_iter() + .find_map(|event| SrcChain::try_extract_channel_open_init_event(event)) + .ok_or_else(|| relay.missing_channel_init_event_error())?; + + let src_channel_id = SrcChain::channel_open_init_event_channel_id(&open_init_event); + + Ok(src_channel_id.clone()) + } +} diff --git a/crates/relayer-components/src/relay/impls/channel/open_try.rs b/crates/relayer-components/src/relay/impls/channel/open_try.rs new file mode 100644 index 0000000000..e5816f347b --- /dev/null +++ b/crates/relayer-components/src/relay/impls/channel/open_try.rs @@ -0,0 +1,98 @@ +use async_trait::async_trait; + +use crate::chain::traits::client::client_state::CanQueryClientState; +use crate::chain::traits::components::chain_status_querier::CanQueryChainHeight; +use crate::chain::traits::message_builders::channel::{ + CanBuildChannelHandshakeMessages, CanBuildChannelHandshakePayloads, +}; +use crate::chain::traits::types::ibc::HasIbcChainTypes; +use crate::chain::traits::types::ibc_events::channel::HasChannelOpenTryEvent; +use crate::relay::traits::chains::HasRelayChains; +use crate::relay::traits::channel::open_try::ChannelOpenTryRelayer; +use crate::relay::traits::components::ibc_message_sender::{CanSendSingleIbcMessage, MainSink}; +use crate::relay::traits::target::DestinationTarget; +use crate::relay::types::aliases::{DstChannelId, DstPortId, SrcChannelId, SrcPortId}; +use crate::std_prelude::*; + +pub trait InjectMissingChannelTryEventError: HasRelayChains { + fn missing_channel_try_event_error( + &self, + src_channel_id: &>::ChannelId, + ) -> Self::Error; +} + +/** + A base implementation of [`ChannelOpenTryRelayer`] that relays a new channel + at the source chain that is in `OPEN_INIT` state, and submits it as a + `ChannelOpenTry` message to the destination chain. + + This implements the `ChanOpenTry` step of the IBC channel handshake protocol. + + Note that this implementation does not check that the channel exists on + the destination chain. It also doesn't check that the channel end at the + source chain is really in the `OPEN_INIT` state. This will be implemented as + a separate wrapper component. (TODO) +*/ + +pub struct RelayChannelOpenTry; + +#[async_trait] +impl ChannelOpenTryRelayer for RelayChannelOpenTry +where + Relay: HasRelayChains + + CanSendSingleIbcMessage + + InjectMissingChannelTryEventError, + SrcChain: CanQueryChainHeight + CanBuildChannelHandshakePayloads, + DstChain: CanQueryClientState + + CanBuildChannelHandshakeMessages + + HasChannelOpenTryEvent, + DstChain::ChannelId: Clone, +{ + async fn relay_channel_open_try( + relay: &Relay, + dst_port: &DstPortId, + src_port_id: &SrcPortId, + src_channel_id: &SrcChannelId, + ) -> Result, Relay::Error> { + let src_chain = relay.src_chain(); + let dst_chain = relay.dst_chain(); + + let src_proof_height = src_chain + .query_chain_height() + .await + .map_err(Relay::src_chain_error)?; + + let src_client_state = dst_chain + .query_client_state(relay.dst_client_id()) + .await + .map_err(Relay::dst_chain_error)?; + + let open_try_payload = src_chain + .build_channel_open_try_payload( + &src_client_state, + &src_proof_height, + src_port_id, + src_channel_id, + ) + .await + .map_err(Relay::src_chain_error)?; + + let open_try_message = dst_chain + .build_channel_open_try_message(dst_port, src_port_id, src_channel_id, open_try_payload) + .await + .map_err(Relay::dst_chain_error)?; + + let events = relay + .send_message(DestinationTarget, open_try_message) + .await?; + + let open_try_event = events + .into_iter() + .find_map(|event| DstChain::try_extract_channel_open_try_event(event)) + .ok_or_else(|| relay.missing_channel_try_event_error(src_channel_id))?; + + let dst_channel_id = DstChain::channel_open_try_event_channel_id(&open_try_event); + + Ok(dst_channel_id.clone()) + } +} diff --git a/crates/relayer-components/src/relay/impls/connection/bootstrap.rs b/crates/relayer-components/src/relay/impls/connection/bootstrap.rs new file mode 100644 index 0000000000..ead5728c26 --- /dev/null +++ b/crates/relayer-components/src/relay/impls/connection/bootstrap.rs @@ -0,0 +1,57 @@ +use async_trait::async_trait; + +use crate::chain::traits::types::connection::HasInitConnectionOptionsType; +use crate::chain::traits::types::ibc::HasIbcChainTypes; +use crate::relay::traits::chains::HasRelayChains; +use crate::relay::traits::connection::open_handshake::CanRelayConnectionOpenHandshake; +use crate::relay::traits::connection::open_init::CanInitConnection; +use crate::relay::types::aliases::{DstConnectionId, SrcConnectionId}; +use crate::std_prelude::*; + +/** + This is an autotrait implementation by the relay context to allow bootstrapping + of new IBC connections as initiated by the relayer. + + This can be used by the users of the relayer to create new connections. It can + also be used in integration tests to create new connections. + + Note that this should _not_ be used when relaying connection creation that + are initiated by external users. For that purpose, use + [`RelayConnectionOpenHandshake`](crate::relay::impls::connection::open_handshake::RelayConnectionOpenHandshake), + which would reuse the given connection ID instead of creating new ones. +*/ +#[async_trait] +pub trait CanBootstrapConnection: HasRelayChains +where + Self::SrcChain: HasInitConnectionOptionsType, +{ + async fn bootstrap_connection( + &self, + init_connection_options: &>::InitConnectionOptions, + ) -> Result<(SrcConnectionId, DstConnectionId), Self::Error>; +} + +#[async_trait] +impl CanBootstrapConnection for Relay +where + Relay: HasRelayChains + + CanInitConnection + + CanRelayConnectionOpenHandshake, + SrcChain: HasInitConnectionOptionsType, + DstChain: HasIbcChainTypes, +{ + async fn bootstrap_connection( + &self, + init_connection_options: &SrcChain::InitConnectionOptions, + ) -> Result<(SrcChain::ConnectionId, DstChain::ConnectionId), Self::Error> { + let src_connection_id = self.init_connection(init_connection_options).await?; + + let dst_connection_id = self + .relay_connection_open_handshake(&src_connection_id) + .await?; + + Ok((src_connection_id, dst_connection_id)) + } +} diff --git a/crates/relayer-components/src/relay/impls/connection/mod.rs b/crates/relayer-components/src/relay/impls/connection/mod.rs new file mode 100644 index 0000000000..f0aa1d0667 --- /dev/null +++ b/crates/relayer-components/src/relay/impls/connection/mod.rs @@ -0,0 +1,6 @@ +pub mod bootstrap; +pub mod open_ack; +pub mod open_confirm; +pub mod open_handshake; +pub mod open_init; +pub mod open_try; diff --git a/crates/relayer-components/src/relay/impls/connection/open_ack.rs b/crates/relayer-components/src/relay/impls/connection/open_ack.rs new file mode 100644 index 0000000000..1a00769121 --- /dev/null +++ b/crates/relayer-components/src/relay/impls/connection/open_ack.rs @@ -0,0 +1,81 @@ +use async_trait::async_trait; + +use crate::chain::traits::client::client_state::CanQueryClientState; +use crate::chain::traits::components::chain_status_querier::CanQueryChainHeight; +use crate::chain::traits::message_builders::connection::{ + CanBuildConnectionHandshakeMessages, CanBuildConnectionHandshakePayloads, +}; +use crate::relay::traits::chains::HasRelayChains; +use crate::relay::traits::components::ibc_message_sender::{CanSendSingleIbcMessage, MainSink}; +use crate::relay::traits::components::update_client_message_builder::CanSendUpdateClientMessage; +use crate::relay::traits::connection::open_ack::ConnectionOpenAckRelayer; +use crate::relay::traits::target::{DestinationTarget, SourceTarget}; +use crate::std_prelude::*; + +/** + A base implementation of [`ConnectionOpenAckRelayer`] that relays a new connection + at the destination chain that is in `OPEN_TRY` state, and submits it as a + `ConnectionOpenAck` message to the destination chain. + + This implements the `ConnOpenAck` step of the IBC connection handshake protocol. + + Note that this implementation does not check that the connections at the + source and destination chain are really in the `OPEN_TRY` state. This will be + implemented as a separate wrapper component. (TODO) +*/ +pub struct RelayConnectionOpenAck; + +#[async_trait] +impl ConnectionOpenAckRelayer for RelayConnectionOpenAck +where + Relay: HasRelayChains + + CanSendUpdateClientMessage + + CanSendSingleIbcMessage, + SrcChain: CanBuildConnectionHandshakeMessages + CanQueryClientState, + DstChain: CanQueryChainHeight + CanBuildConnectionHandshakePayloads, + DstChain::ConnectionId: Clone, +{ + async fn relay_connection_open_ack( + relay: &Relay, + src_connection_id: &SrcChain::ConnectionId, + dst_connection_id: &DstChain::ConnectionId, + ) -> Result<(), Relay::Error> { + let src_chain = relay.src_chain(); + let dst_chain = relay.dst_chain(); + + let dst_client_id = relay.dst_client_id(); + + let dst_proof_height = dst_chain + .query_chain_height() + .await + .map_err(Relay::dst_chain_error)?; + + let dst_client_state = src_chain + .query_client_state(relay.src_client_id()) + .await + .map_err(Relay::src_chain_error)?; + + let open_ack_payload = dst_chain + .build_connection_open_ack_payload( + &dst_client_state, + &dst_proof_height, + dst_client_id, + dst_connection_id, + ) + .await + .map_err(Relay::dst_chain_error)?; + + let open_ack_message = src_chain + .build_connection_open_ack_message( + src_connection_id, + dst_connection_id, + open_ack_payload, + ) + .await + .map_err(Relay::src_chain_error)?; + + relay.send_message(SourceTarget, open_ack_message).await?; + + Ok(()) + } +} diff --git a/crates/relayer-components/src/relay/impls/connection/open_confirm.rs b/crates/relayer-components/src/relay/impls/connection/open_confirm.rs new file mode 100644 index 0000000000..1353e095bb --- /dev/null +++ b/crates/relayer-components/src/relay/impls/connection/open_confirm.rs @@ -0,0 +1,79 @@ +use async_trait::async_trait; + +use crate::chain::traits::client::client_state::CanQueryClientState; +use crate::chain::traits::components::chain_status_querier::CanQueryChainHeight; +use crate::chain::traits::message_builders::connection::{ + CanBuildConnectionHandshakeMessages, CanBuildConnectionHandshakePayloads, +}; +use crate::relay::traits::chains::HasRelayChains; +use crate::relay::traits::components::ibc_message_sender::{CanSendSingleIbcMessage, MainSink}; +use crate::relay::traits::components::update_client_message_builder::CanBuildUpdateClientMessage; +use crate::relay::traits::connection::open_confirm::ConnectionOpenConfirmRelayer; +use crate::relay::traits::target::DestinationTarget; +use crate::std_prelude::*; + +/** + A base implementation of [`ConnectionOpenConfirmRelayer`] that relays a new connection + at the source chain that is in `OPEN` state, and submits it as a + `ConnectionOpenConfirm` message to the destination chain. + + This implements the `ConnOpenConfirm` step of the IBC connection handshake protocol. + + Note that this implementation does not check that the connection at the source + chain is really in the `OPEN` state, and that the connection at the destination chain + is in the `OPEN_TRY` state. This will be implemented as a separate wrapper component. (TODO) +*/ +pub struct RelayConnectionOpenConfirm; + +#[async_trait] +impl ConnectionOpenConfirmRelayer for RelayConnectionOpenConfirm +where + Relay: HasRelayChains + + CanBuildUpdateClientMessage + + CanSendSingleIbcMessage, + SrcChain: CanQueryChainHeight + CanBuildConnectionHandshakePayloads, + DstChain: CanBuildConnectionHandshakeMessages + CanQueryClientState, + DstChain::ConnectionId: Clone, +{ + async fn relay_connection_open_confirm( + relay: &Relay, + src_connection_id: &SrcChain::ConnectionId, + dst_connection_id: &DstChain::ConnectionId, + ) -> Result<(), Relay::Error> { + let src_chain = relay.src_chain(); + let dst_chain = relay.dst_chain(); + + let src_client_id = relay.src_client_id(); + + let src_proof_height = src_chain + .query_chain_height() + .await + .map_err(Relay::src_chain_error)?; + + let src_client_state = dst_chain + .query_client_state(relay.dst_client_id()) + .await + .map_err(Relay::dst_chain_error)?; + + let open_confirm_payload = src_chain + .build_connection_open_confirm_payload( + &src_client_state, + &src_proof_height, + src_client_id, + src_connection_id, + ) + .await + .map_err(Relay::src_chain_error)?; + + let open_confirm_message = dst_chain + .build_connection_open_confirm_message(dst_connection_id, open_confirm_payload) + .await + .map_err(Relay::dst_chain_error)?; + + relay + .send_message(DestinationTarget, open_confirm_message) + .await?; + + Ok(()) + } +} diff --git a/crates/relayer-components/src/relay/impls/connection/open_handshake.rs b/crates/relayer-components/src/relay/impls/connection/open_handshake.rs new file mode 100644 index 0000000000..c2415d21f6 --- /dev/null +++ b/crates/relayer-components/src/relay/impls/connection/open_handshake.rs @@ -0,0 +1,52 @@ +use async_trait::async_trait; + +use crate::chain::traits::types::ibc::HasIbcChainTypes; +use crate::relay::traits::chains::HasRelayChains; +use crate::relay::traits::connection::open_ack::CanRelayConnectionOpenAck; +use crate::relay::traits::connection::open_confirm::CanRelayConnectionOpenConfirm; +use crate::relay::traits::connection::open_handshake::ConnectionOpenHandshakeRelayer; +use crate::relay::traits::connection::open_try::CanRelayConnectionOpenTry; +use crate::std_prelude::*; + +/** + Relays a connection open handshake using a connection ID that has been + initialized at the source chain. + + Specifically, the `ConnOpenTry`, `ConnOpenAck`, and `ConnOpenConfirm` steps of + the handshake protocol are performed between both chains. Upon successful + completion of the handshake protocol, a connection will have been established + between both chains. + + This can be used for relaying of connections that are created by external + users. +*/ +pub struct RelayConnectionOpenHandshake; + +#[async_trait] +impl ConnectionOpenHandshakeRelayer + for RelayConnectionOpenHandshake +where + Relay: HasRelayChains + + CanRelayConnectionOpenTry + + CanRelayConnectionOpenAck + + CanRelayConnectionOpenConfirm, + SrcChain: HasIbcChainTypes, + DstChain: HasIbcChainTypes, +{ + async fn relay_connection_open_handshake( + relay: &Relay, + src_connection_id: &SrcChain::ConnectionId, + ) -> Result { + let dst_connection_id = relay.relay_connection_open_try(src_connection_id).await?; + + relay + .relay_connection_open_ack(src_connection_id, &dst_connection_id) + .await?; + + relay + .relay_connection_open_confirm(src_connection_id, &dst_connection_id) + .await?; + + Ok(dst_connection_id) + } +} diff --git a/crates/relayer-components/src/relay/impls/connection/open_init.rs b/crates/relayer-components/src/relay/impls/connection/open_init.rs new file mode 100644 index 0000000000..48f9f34e3f --- /dev/null +++ b/crates/relayer-components/src/relay/impls/connection/open_init.rs @@ -0,0 +1,85 @@ +use async_trait::async_trait; +use core::iter::Iterator; + +use crate::chain::traits::client::client_state::CanQueryClientState; +use crate::chain::traits::components::message_sender::CanSendSingleMessage; +use crate::chain::traits::message_builders::connection::{ + CanBuildConnectionHandshakeMessages, CanBuildConnectionHandshakePayloads, +}; +use crate::chain::traits::types::connection::HasInitConnectionOptionsType; +use crate::chain::traits::types::ibc_events::connection::HasConnectionOpenInitEvent; +use crate::relay::traits::chains::HasRelayChains; +use crate::relay::traits::connection::open_init::ConnectionInitializer; +use crate::std_prelude::*; + +pub trait InjectMissingConnectionInitEventError: HasRelayChains { + fn missing_connection_init_event_error(&self) -> Self::Error; +} + +/** + A base implementation for [`ConnectionInitializer`] which submits a + `ConnectionOpenInit` message to the source chain. + + This implements the `ConnInit` step in the IBC connection handshake protocol. +*/ +pub struct InitializeConnection; + +#[async_trait] +impl ConnectionInitializer for InitializeConnection +where + Relay: HasRelayChains + + InjectMissingConnectionInitEventError, + SrcChain: CanSendSingleMessage + + HasInitConnectionOptionsType + + CanBuildConnectionHandshakeMessages + + CanQueryClientState + + HasConnectionOpenInitEvent, + DstChain: CanBuildConnectionHandshakePayloads, + SrcChain::ConnectionId: Clone, +{ + async fn init_connection( + relay: &Relay, + init_connection_options: &SrcChain::InitConnectionOptions, + ) -> Result { + let src_chain = relay.src_chain(); + let dst_chain = relay.dst_chain(); + + let src_client_id = relay.src_client_id(); + let dst_client_id = relay.dst_client_id(); + + let dst_client_state = src_chain + .query_client_state(src_client_id) + .await + .map_err(Relay::src_chain_error)?; + + let open_init_payload = dst_chain + .build_connection_open_init_payload(&dst_client_state) + .await + .map_err(Relay::dst_chain_error)?; + + let src_message = src_chain + .build_connection_open_init_message( + src_client_id, + dst_client_id, + init_connection_options, + open_init_payload, + ) + .await + .map_err(Relay::src_chain_error)?; + + let events = src_chain + .send_message(src_message) + .await + .map_err(Relay::src_chain_error)?; + + let open_init_event = events + .into_iter() + .find_map(|event| SrcChain::try_extract_connection_open_init_event(event)) + .ok_or_else(|| relay.missing_connection_init_event_error())?; + + let src_connection_id = + SrcChain::connection_open_init_event_connection_id(&open_init_event); + + Ok(src_connection_id.clone()) + } +} diff --git a/crates/relayer-components/src/relay/impls/connection/open_try.rs b/crates/relayer-components/src/relay/impls/connection/open_try.rs new file mode 100644 index 0000000000..69adf37543 --- /dev/null +++ b/crates/relayer-components/src/relay/impls/connection/open_try.rs @@ -0,0 +1,104 @@ +use async_trait::async_trait; +use core::iter::Iterator; + +use crate::chain::traits::client::client_state::CanQueryClientState; +use crate::chain::traits::components::chain_status_querier::CanQueryChainHeight; +use crate::chain::traits::message_builders::connection::{ + CanBuildConnectionHandshakeMessages, CanBuildConnectionHandshakePayloads, +}; +use crate::chain::traits::types::ibc::HasIbcChainTypes; +use crate::chain::traits::types::ibc_events::connection::HasConnectionOpenTryEvent; +use crate::relay::traits::chains::HasRelayChains; +use crate::relay::traits::components::ibc_message_sender::{CanSendSingleIbcMessage, MainSink}; +use crate::relay::traits::components::update_client_message_builder::CanSendUpdateClientMessage; +use crate::relay::traits::connection::open_try::ConnectionOpenTryRelayer; +use crate::relay::traits::target::{DestinationTarget, SourceTarget}; +use crate::std_prelude::*; + +pub trait InjectMissingConnectionTryEventError: HasRelayChains { + fn missing_connection_try_event_error( + &self, + src_connection_id: &>::ConnectionId, + ) -> Self::Error; +} + +/** + A base implementation of [`ConnectionOpenTryRelayer`] that relays a new connection + at the source chain that is in `OPEN_INIT` state, and submits it as a + `ConnectionOpenTry` message to the destination chain. + + This implements the `ConnOpenTry` step of the IBC connection handshake protocol. + + Note that this implementation does not check that the connection at the source + chain is really in the `OPEN_INIT` state. This will be implemented as a separate + wrapper component. (TODO) +*/ +pub struct RelayConnectionOpenTry; + +#[async_trait] +impl ConnectionOpenTryRelayer for RelayConnectionOpenTry +where + Relay: HasRelayChains + + CanSendUpdateClientMessage + + CanSendSingleIbcMessage + + InjectMissingConnectionTryEventError, + SrcChain: CanQueryChainHeight + CanBuildConnectionHandshakePayloads, + DstChain: CanQueryClientState + + CanBuildConnectionHandshakeMessages + + HasConnectionOpenTryEvent, + DstChain::ConnectionId: Clone, +{ + async fn relay_connection_open_try( + relay: &Relay, + src_connection_id: &SrcChain::ConnectionId, + ) -> Result { + let src_chain = relay.src_chain(); + let dst_chain = relay.dst_chain(); + + let src_client_id = relay.src_client_id(); + let dst_client_id = relay.dst_client_id(); + + let src_proof_height = src_chain + .query_chain_height() + .await + .map_err(Relay::src_chain_error)?; + + let src_client_state = dst_chain + .query_client_state(relay.dst_client_id()) + .await + .map_err(Relay::dst_chain_error)?; + + let open_try_payload = src_chain + .build_connection_open_try_payload( + &src_client_state, + &src_proof_height, + src_client_id, + src_connection_id, + ) + .await + .map_err(Relay::src_chain_error)?; + + let open_try_message = dst_chain + .build_connection_open_try_message( + dst_client_id, + src_client_id, + src_connection_id, + open_try_payload, + ) + .await + .map_err(Relay::dst_chain_error)?; + + let events = relay + .send_message(DestinationTarget, open_try_message) + .await?; + + let open_try_event = events + .into_iter() + .find_map(|event| DstChain::try_extract_connection_open_try_event(event)) + .ok_or_else(|| relay.missing_connection_try_event_error(src_connection_id))?; + + let dst_connection_id = DstChain::connection_open_try_event_connection_id(&open_try_event); + + Ok(dst_connection_id.clone()) + } +} diff --git a/crates/relayer-components/src/relay/impls/mod.rs b/crates/relayer-components/src/relay/impls/mod.rs new file mode 100644 index 0000000000..56e1b92d88 --- /dev/null +++ b/crates/relayer-components/src/relay/impls/mod.rs @@ -0,0 +1,2 @@ +pub mod channel; +pub mod connection; diff --git a/crates/relayer-components/src/relay/mod.rs b/crates/relayer-components/src/relay/mod.rs new file mode 100644 index 0000000000..69a42f92ea --- /dev/null +++ b/crates/relayer-components/src/relay/mod.rs @@ -0,0 +1,35 @@ +/*! + Constructs for the relay context. + + A relay context corresponds to the context that the relayer uses to relay + IBC packets from a source chain to a destination chain in _one_ direction. + It is made of two chain sub-contexts that act as the source and destination + chain. + + At its core, a relay context should implement + [`HasRelayChains`](traits::chains::HasRelayChains), which declares the chain + sub-contexts as well as the abstract types that are used when relaying. + + Since the relay context only supports relaying in one direction, two relay + contexts are needed for the relayer to perform bi-directional relaying. + To support relaying between different chain types, such as Cosmos to + non-Cosmos chain relaying, the relay context implementation in the reverse + direction may differ from the relay context in the original direction. + + A key feature the relay context provides is on performing relaying of + a single packet, as defined by + [`CanRelayPacket`](traits::packet_relayer::CanRelayPacket). + The relay context also provides the important functionality of building + the update client messages using + [`CanBuildUpdateClientMessage`](traits::messages::update_client::CanBuildUpdateClientMessage) + to update the client state of one chain to its counterparty chain. + + Compared to a single chain context, the relay context holds access to two + chains, allowing it to perform operations that require access to both + chains. +*/ + +pub mod components; +pub mod impls; +pub mod traits; +pub mod types; diff --git a/crates/relayer-components/src/relay/traits/chains.rs b/crates/relayer-components/src/relay/traits/chains.rs new file mode 100644 index 0000000000..24ae522251 --- /dev/null +++ b/crates/relayer-components/src/relay/traits/chains.rs @@ -0,0 +1,84 @@ +use crate::chain::traits::types::ibc::HasIbcChainTypes; +use crate::chain::traits::types::packet::HasIbcPacketTypes; +use crate::chain::types::aliases::ClientId; +use crate::core::traits::error::HasErrorType; +use crate::core::traits::sync::Async; + +/** + This covers the minimal abstract types that are used inside a relay context. + + A relay context is made of two chain sub-contexts, + [`SrcChain`](Self::SrcChain) and [`DstChain`](Self::DstChain), that are + connected to each others. This is reflected by both types being required + to implement [`HasIbcPacketTypes`] with each others being the counterparty. + + The relay context also has an abstract [`Packet`](crate::relay::traits::packet::HasRelayPacket::Packet) type, which + represents the IBC packet sent from the source chain to the destination + chain. In other words, the relay context only covers relaying of IBC packets + in one direction. To support bi-directional relaying between two chains, + the relayer will use two relay contexts with the source and destination + chains flipped in the second relay context. + + A relay context also implicitly assumes that the two chains are connected + by a pair of IBC clients. This is required so that the relay context + can build the UpdateClient messages without requiring to explicitly + specify the client IDs. If two chains are connected through more than one + IBC client pairs, they should be handled by separate relay contexts. + + On the other hand, it is possible to relay packets from multiple channels + and connections, given that they are from the same IBC clients. This is + ok because IBC does not differentiate messages from different channels + and connections. Note however that concrete relay contexts may impose + additional constraints such as restricting a relay context to handle + only a single channel or connection. +*/ +pub trait HasRelayChains: HasErrorType { + type Packet: Async; + + /** + A source chain context that has the IBC chain types that are correspond + to the destination chain. + */ + type SrcChain: HasErrorType + + HasIbcChainTypes + + HasIbcPacketTypes; + + /** + A destination chain context that has the IBC chain types that are correspond + to the source chain. + */ + type DstChain: HasErrorType + + HasIbcChainTypes + + HasIbcPacketTypes; + + /** + Get a reference to the source chain context from the relay context. + */ + fn src_chain(&self) -> &Self::SrcChain; + + /** + Get a reference to the destination chain context from the relay context. + */ + fn dst_chain(&self) -> &Self::DstChain; + + fn src_chain_error(e: ::Error) -> Self::Error; + + fn dst_chain_error(e: ::Error) -> Self::Error; + + /** + Get the client ID on the source chain that corresponds to the destination + chain. + + This shows that the relay context is required to have an implicit IBC + client. On the other hand, there are no accessor methods for getting + the connection and channel IDs, because a relay context may handle + more than one of them. + */ + fn src_client_id(&self) -> &ClientId; + + /** + Get the client ID on the destination chain that corresponds to the source + chain. + */ + fn dst_client_id(&self) -> &ClientId; +} diff --git a/crates/relayer-components/src/relay/traits/channel/mod.rs b/crates/relayer-components/src/relay/traits/channel/mod.rs new file mode 100644 index 0000000000..54500efab7 --- /dev/null +++ b/crates/relayer-components/src/relay/traits/channel/mod.rs @@ -0,0 +1,5 @@ +pub mod open_ack; +pub mod open_confirm; +pub mod open_handshake; +pub mod open_init; +pub mod open_try; diff --git a/crates/relayer-components/src/relay/traits/channel/open_ack.rs b/crates/relayer-components/src/relay/traits/channel/open_ack.rs new file mode 100644 index 0000000000..27de58ef51 --- /dev/null +++ b/crates/relayer-components/src/relay/traits/channel/open_ack.rs @@ -0,0 +1,30 @@ +use async_trait::async_trait; + +use crate::relay::traits::chains::HasRelayChains; +use crate::relay::types::aliases::{DstChannelId, DstPortId, SrcChannelId, SrcPortId}; +use crate::std_prelude::*; + +#[async_trait] +pub trait CanRelayChannelOpenAck: HasRelayChains { + async fn relay_channel_open_ack( + &self, + src_port_id: &SrcPortId, + src_channel_id: &SrcChannelId, + dst_port_id: &DstPortId, + dst_channel_id: &DstChannelId, + ) -> Result<(), Self::Error>; +} + +#[async_trait] +pub trait ChannelOpenAckRelayer +where + Relay: HasRelayChains, +{ + async fn relay_channel_open_ack( + relay: &Relay, + src_port_id: &SrcPortId, + src_channel_id: &SrcChannelId, + dst_port_id: &DstPortId, + dst_channel_id: &DstChannelId, + ) -> Result<(), Relay::Error>; +} diff --git a/crates/relayer-components/src/relay/traits/channel/open_confirm.rs b/crates/relayer-components/src/relay/traits/channel/open_confirm.rs new file mode 100644 index 0000000000..6aac489a06 --- /dev/null +++ b/crates/relayer-components/src/relay/traits/channel/open_confirm.rs @@ -0,0 +1,30 @@ +use async_trait::async_trait; + +use crate::relay::traits::chains::HasRelayChains; +use crate::relay::types::aliases::{DstChannelId, DstPortId, SrcChannelId, SrcPortId}; +use crate::std_prelude::*; + +#[async_trait] +pub trait CanRelayChannelOpenConfirm: HasRelayChains { + async fn relay_channel_open_confirm( + &self, + dst_port: &DstPortId, + dst_channel_id: &DstChannelId, + src_port_id: &SrcPortId, + src_channel_id: &SrcChannelId, + ) -> Result<(), Self::Error>; +} + +#[async_trait] +pub trait ChannelOpenConfirmRelayer +where + Relay: HasRelayChains, +{ + async fn relay_channel_open_confirm( + relay: &Relay, + dst_port: &DstPortId, + dst_channel_id: &DstChannelId, + src_port_id: &SrcPortId, + src_channel_id: &SrcChannelId, + ) -> Result<(), Relay::Error>; +} diff --git a/crates/relayer-components/src/relay/traits/channel/open_handshake.rs b/crates/relayer-components/src/relay/traits/channel/open_handshake.rs new file mode 100644 index 0000000000..76c384369a --- /dev/null +++ b/crates/relayer-components/src/relay/traits/channel/open_handshake.rs @@ -0,0 +1,28 @@ +use async_trait::async_trait; + +use crate::relay::traits::chains::HasRelayChains; +use crate::relay::types::aliases::{DstChannelId, DstPortId, SrcChannelId, SrcPortId}; +use crate::std_prelude::*; + +#[async_trait] +pub trait CanRelayChannelOpenHandshake: HasRelayChains { + async fn relay_channel_open_handshake( + &self, + src_channel_id: &SrcChannelId, + src_port_id: &SrcPortId, + dst_port_id: &DstPortId, + ) -> Result, Self::Error>; +} + +#[async_trait] +pub trait ChannelOpenHandshakeRelayer +where + Relay: HasRelayChains, +{ + async fn relay_channel_open_handshake( + relay: &Relay, + src_channel_id: &SrcChannelId, + src_port_id: &SrcPortId, + dst_port_id: &DstPortId, + ) -> Result, Relay::Error>; +} diff --git a/crates/relayer-components/src/relay/traits/channel/open_init.rs b/crates/relayer-components/src/relay/traits/channel/open_init.rs new file mode 100644 index 0000000000..5fea12b756 --- /dev/null +++ b/crates/relayer-components/src/relay/traits/channel/open_init.rs @@ -0,0 +1,37 @@ +use async_trait::async_trait; + +use crate::chain::traits::types::channel::HasInitChannelOptionsType; +use crate::relay::traits::chains::HasRelayChains; +use crate::relay::types::aliases::{DstPortId, SrcChannelId, SrcPortId}; +use crate::std_prelude::*; + +#[async_trait] +pub trait CanInitChannel: HasRelayChains +where + Self::SrcChain: HasInitChannelOptionsType, +{ + async fn init_channel( + &self, + src_port_id: &SrcPortId, + dst_port_id: &DstPortId, + init_connection_options: &>::InitChannelOptions, + ) -> Result, Self::Error>; +} + +#[async_trait] +pub trait ChannelInitializer +where + Relay: HasRelayChains, + Relay::SrcChain: HasInitChannelOptionsType, +{ + async fn init_channel( + relay: &Relay, + src_port_id: &SrcPortId, + dst_port_id: &DstPortId, + init_channel_options: &>::InitChannelOptions, + ) -> Result, Relay::Error>; +} diff --git a/crates/relayer-components/src/relay/traits/channel/open_try.rs b/crates/relayer-components/src/relay/traits/channel/open_try.rs new file mode 100644 index 0000000000..311d348305 --- /dev/null +++ b/crates/relayer-components/src/relay/traits/channel/open_try.rs @@ -0,0 +1,28 @@ +use async_trait::async_trait; + +use crate::relay::traits::chains::HasRelayChains; +use crate::relay::types::aliases::{DstChannelId, DstPortId, SrcChannelId, SrcPortId}; +use crate::std_prelude::*; + +#[async_trait] +pub trait CanRelayChannelOpenTry: HasRelayChains { + async fn relay_channel_open_try( + &self, + dst_port_id: &DstPortId, + src_port_id: &SrcPortId, + src_channel_id: &SrcChannelId, + ) -> Result, Self::Error>; +} + +#[async_trait] +pub trait ChannelOpenTryRelayer +where + Relay: HasRelayChains, +{ + async fn relay_channel_open_try( + relay: &Relay, + dst_port_id: &DstPortId, + src_port_id: &SrcPortId, + src_channel_id: &SrcChannelId, + ) -> Result, Relay::Error>; +} diff --git a/crates/relayer-components/src/relay/traits/clear_interval.rs b/crates/relayer-components/src/relay/traits/clear_interval.rs new file mode 100644 index 0000000000..bed2b67097 --- /dev/null +++ b/crates/relayer-components/src/relay/traits/clear_interval.rs @@ -0,0 +1,7 @@ +use crate::core::traits::sync::Async; + +pub trait HasClearInterval { + type ClearInterval: Async + Clone + Into; + + fn clear_interval(&self) -> Self::ClearInterval; +} diff --git a/crates/relayer-components/src/relay/traits/components/auto_relayer.rs b/crates/relayer-components/src/relay/traits/components/auto_relayer.rs new file mode 100644 index 0000000000..075d047a27 --- /dev/null +++ b/crates/relayer-components/src/relay/traits/components/auto_relayer.rs @@ -0,0 +1,74 @@ +use async_trait::async_trait; + +use crate::core::traits::component::{DelegateComponent, HasComponents}; +use crate::core::traits::error::HasErrorType; +use crate::core::traits::sync::Async; +use crate::relay::traits::chains::HasRelayChains; +use crate::relay::traits::target::ChainTarget; +use crate::std_prelude::*; + +pub struct AutoRelayerComponent; + +/// Provider trait for the `CanAutoRelay` trait. +#[async_trait] +pub trait AutoRelayer: Async +where + Relay: HasErrorType, +{ + /// Starts the auto-relaying process for the given `Relay`. + async fn auto_relay(relay: &Relay) -> Result<(), Relay::Error>; +} + +#[async_trait] +impl AutoRelayer for Component +where + Relay: HasErrorType, + Component: DelegateComponent, + Component::Delegate: AutoRelayer, +{ + async fn auto_relay(relay: &Relay) -> Result<(), Relay::Error> { + Component::Delegate::auto_relay(relay).await + } +} + +/// Trait that encodes the capability of a relayer to relay +/// in a "set it and forget it" manner. This trait is agnostic +/// as far as the provided context is concerned, i.e., it doesn't +/// require an implementing type to be of any particular context. +/// +/// For example, if this trait is implemented for a two-way relay +/// context, then starting the auto-relay process will handle relaying +/// between both connected chains in a bi-directional manner. If it is +/// instead implemented for a one-way relay context, then starting the +/// auto-relay process will relay in one direction as appropriate for +/// the implementing context. +#[async_trait] +pub trait CanAutoRelay: HasErrorType { + /// Starts the auto-relaying process. + async fn auto_relay(&self) -> Result<(), Self::Error>; +} + +#[async_trait] +impl CanAutoRelay for Relay +where + Relay: HasErrorType + HasComponents, + Relay::Components: AutoRelayer, +{ + async fn auto_relay(&self) -> Result<(), Self::Error> { + Relay::Components::auto_relay(self).await + } +} + +/// Similar to the `CanAutoRelay` trait, the main differences are that this +/// trait only relays to a specific target, i.e., in one direction, as well +/// as the fact that it is specific to the `Relay` context. +#[async_trait] +pub trait AutoRelayerWithTarget: Async +where + Relay: HasRelayChains, + Target: ChainTarget, +{ + /// Starts the auto-relaying process of relaying to the given `Relay` context's + /// target. + async fn auto_relay_with_target(relay: &Relay) -> Result<(), Relay::Error>; +} diff --git a/crates/relayer-components/src/relay/traits/components/client_creator.rs b/crates/relayer-components/src/relay/traits/components/client_creator.rs new file mode 100644 index 0000000000..6af8a8e50e --- /dev/null +++ b/crates/relayer-components/src/relay/traits/components/client_creator.rs @@ -0,0 +1,96 @@ +use async_trait::async_trait; + +use crate::chain::traits::client::create::HasCreateClientOptions; +use crate::chain::types::aliases::ClientId; +use crate::core::traits::component::{DelegateComponent, HasComponents}; +use crate::relay::traits::chains::HasRelayChains; +use crate::relay::traits::target::ChainTarget; +use crate::std_prelude::*; + +pub struct ClientCreatorComponent; + +#[async_trait] +pub trait ClientCreator +where + Relay: HasRelayChains, + Target: ChainTarget, + Target::CounterpartyChain: HasCreateClientOptions, +{ + async fn create_client( + target_chain: &Target::TargetChain, + counterparty_chain: &Target::CounterpartyChain, + create_client_options: &>::CreateClientPayloadOptions, + ) -> Result, Relay::Error>; +} + +#[async_trait] +impl ClientCreator for Component +where + Relay: HasRelayChains, + Target: ChainTarget, + Target::CounterpartyChain: HasCreateClientOptions, + Component: DelegateComponent, + Component::Delegate: ClientCreator, +{ + async fn create_client( + target_chain: &Target::TargetChain, + counterparty_chain: &Target::CounterpartyChain, + create_client_options: &>::CreateClientPayloadOptions, + ) -> Result, Relay::Error> { + Component::Delegate::create_client(target_chain, counterparty_chain, create_client_options) + .await + } +} + +#[async_trait] +pub trait CanCreateClient: HasRelayChains +where + Target: ChainTarget, + Target::CounterpartyChain: HasCreateClientOptions, +{ + /** + Create a new IBC client on the target chain. + + Notice that this function does not take in `&self` as argument. + This is because the relay context is required to have fixed client IDs already. + Since the relay context can't be built yet without the client IDs, + we pass in the target and counterparty chains as argument directly. + + We define this as a static method for the relay context to reuse the + existing infrastructure, particularly in handling errors from two chains + which may be of different types. + */ + async fn create_client( + target: Target, + target_chain: &Target::TargetChain, + counterparty_chain: &Target::CounterpartyChain, + create_client_options: &>::CreateClientPayloadOptions, + ) -> Result, Self::Error>; +} + +#[async_trait] +impl CanCreateClient for Relay +where + Relay: HasRelayChains + HasComponents, + Target: ChainTarget, + Target::CounterpartyChain: HasCreateClientOptions, + Relay::Components: ClientCreator, +{ + async fn create_client( + _target: Target, + target_chain: &Target::TargetChain, + counterparty_chain: &Target::CounterpartyChain, + create_client_options: &>::CreateClientPayloadOptions, + ) -> Result, Relay::Error> { + Relay::Components::create_client(target_chain, counterparty_chain, create_client_options) + .await + } +} diff --git a/crates/relayer-components/src/relay/traits/components/event_relayer.rs b/crates/relayer-components/src/relay/traits/components/event_relayer.rs new file mode 100644 index 0000000000..a22e156708 --- /dev/null +++ b/crates/relayer-components/src/relay/traits/components/event_relayer.rs @@ -0,0 +1,89 @@ +use async_trait::async_trait; + +use crate::chain::traits::types::event::HasEventType; +use crate::chain::types::aliases::{Event, Height}; +use crate::core::traits::component::{DelegateComponent, HasComponents}; +use crate::core::traits::sync::Async; +use crate::relay::traits::chains::HasRelayChains; +use crate::relay::traits::target::ChainTarget; +use crate::std_prelude::*; + +pub struct EventRelayerComponent; + +/** + An event relayer performs relay actions based on one event at a time from + the target chain. + + The event relayer is a general abstraction over other relayer types that + need to be reactive to chain events. This includes the + [packet relayer]( crate::relay::traits::components::packet_relayer::CanRelayPacket), + but also future relayers such as connection and channel handshake relayers. +*/ +#[async_trait] +pub trait EventRelayer: Async +where + Relay: HasRelayChains, + Target: ChainTarget, + Target::TargetChain: HasEventType, +{ + /** + Relay a chain event which is emitted from the target chain at a given + height. + + The chain event could be anything. If the given event is not related to + IBC, the relayer should do nothing and return `Ok(())`. + */ + async fn relay_chain_event( + relay: &Relay, + height: &Height, + event: &Event, + ) -> Result<(), Relay::Error>; +} + +#[async_trait] +impl EventRelayer for Component +where + Relay: HasRelayChains, + Target: ChainTarget, + Target::TargetChain: HasEventType, + Component: DelegateComponent, + Component::Delegate: EventRelayer, +{ + async fn relay_chain_event( + relay: &Relay, + height: &Height, + event: &Event, + ) -> Result<(), Relay::Error> { + Component::Delegate::relay_chain_event(relay, height, event).await + } +} + +#[async_trait] +pub trait CanRelayEvent: HasRelayChains +where + Target: ChainTarget, + Target::TargetChain: HasEventType, +{ + async fn relay_chain_event( + &self, + height: &Height, + event: &Event, + ) -> Result<(), Self::Error>; +} + +#[async_trait] +impl CanRelayEvent for Relay +where + Relay: HasRelayChains + HasComponents, + Target: ChainTarget, + Target::TargetChain: HasEventType, + Relay::Components: EventRelayer, +{ + async fn relay_chain_event( + &self, + height: &Height, + event: &Event, + ) -> Result<(), Self::Error> { + Relay::Components::relay_chain_event(self, height, event).await + } +} diff --git a/crates/relayer-components/src/relay/traits/components/ibc_message_sender.rs b/crates/relayer-components/src/relay/traits/components/ibc_message_sender.rs new file mode 100644 index 0000000000..adbf7db445 --- /dev/null +++ b/crates/relayer-components/src/relay/traits/components/ibc_message_sender.rs @@ -0,0 +1,145 @@ +use core::marker::PhantomData; + +use async_trait::async_trait; + +use crate::chain::traits::components::message_sender::InjectMismatchIbcEventsCountError; +use crate::chain::traits::types::ibc::HasIbcChainTypes; +use crate::chain::types::aliases::{Event, Message}; +use crate::core::traits::component::{DelegateComponent, HasComponents}; +use crate::core::traits::sync::Async; +use crate::relay::traits::chains::HasRelayChains; +use crate::relay::traits::target::ChainTarget; +use crate::std_prelude::*; + +pub struct MainSink; + +pub struct IbcMessageSenderComponent(pub PhantomData); + +#[async_trait] +pub trait IbcMessageSender: Async +where + Relay: HasRelayChains, + Target: ChainTarget, +{ + async fn send_messages( + relay: &Relay, + messages: Vec>, + ) -> Result>>, Relay::Error>; +} + +#[async_trait] +impl IbcMessageSender for Component +where + Relay: HasRelayChains, + Target: ChainTarget, + Component: DelegateComponent>, + Component::Delegate: IbcMessageSender, +{ + async fn send_messages( + relay: &Relay, + messages: Vec>, + ) -> Result>>, Relay::Error> { + Component::Delegate::send_messages(relay, messages).await + } +} + +#[async_trait] +pub trait CanSendIbcMessages: HasRelayChains +where + Target: ChainTarget, +{ + async fn send_messages( + &self, + target: Target, + messages: Vec>, + ) -> Result>>, Self::Error>; +} + +#[async_trait] +impl CanSendIbcMessages for Relay +where + Relay: HasRelayChains + HasComponents, + Target: ChainTarget, + Relay::Components: IbcMessageSender, +{ + async fn send_messages( + &self, + _target: Target, + messages: Vec>, + ) -> Result>>, Self::Error> { + Relay::Components::send_messages(self, messages).await + } +} + +#[async_trait] +pub trait CanSendFixSizedIbcMessages: HasRelayChains +where + Target: ChainTarget, +{ + async fn send_messages_fixed( + &self, + target: Target, + messages: [Message; COUNT], + ) -> Result<[Vec>; COUNT], Self::Error>; +} + +#[async_trait] +pub trait CanSendSingleIbcMessage: HasRelayChains +where + Target: ChainTarget, +{ + async fn send_message( + &self, + target: Target, + message: Message, + ) -> Result>, Self::Error>; +} + +#[async_trait] +impl CanSendFixSizedIbcMessages + for Relay +where + Relay: CanSendIbcMessages + InjectMismatchIbcEventsCountError, + Target: ChainTarget, + TargetChain: HasIbcChainTypes, + Message: Async, +{ + async fn send_messages_fixed( + &self, + target: Target, + messages: [Message; COUNT], + ) -> Result<[Vec; COUNT], Relay::Error> { + let events_vec = self.send_messages(target, messages.into()).await?; + + let events = events_vec + .try_into() + .map_err(|e: Vec<_>| Relay::mismatch_ibc_events_count_error(COUNT, e.len()))?; + + Ok(events) + } +} + +#[async_trait] +impl CanSendSingleIbcMessage + for Relay +where + Relay: CanSendIbcMessages, + Target: ChainTarget, + TargetChain: HasIbcChainTypes, + Message: Async, +{ + async fn send_message( + &self, + target: Target, + message: Message, + ) -> Result, Relay::Error> { + let events = self + .send_messages(target, vec![message]) + .await? + .into_iter() + .flatten() + .collect(); + + Ok(events) + } +} diff --git a/crates/relayer-components/src/relay/traits/components/mod.rs b/crates/relayer-components/src/relay/traits/components/mod.rs new file mode 100644 index 0000000000..8fbe47a88a --- /dev/null +++ b/crates/relayer-components/src/relay/traits/components/mod.rs @@ -0,0 +1,9 @@ +pub mod auto_relayer; +pub mod client_creator; +pub mod event_relayer; +pub mod ibc_message_sender; +pub mod packet_clearer; +pub mod packet_filter; +pub mod packet_relayer; +pub mod packet_relayers; +pub mod update_client_message_builder; diff --git a/crates/relayer-components/src/relay/traits/components/packet_clearer.rs b/crates/relayer-components/src/relay/traits/components/packet_clearer.rs new file mode 100644 index 0000000000..2fad51d491 --- /dev/null +++ b/crates/relayer-components/src/relay/traits/components/packet_clearer.rs @@ -0,0 +1,84 @@ +use async_trait::async_trait; + +use crate::chain::types::aliases::{ChannelId, PortId}; +use crate::core::traits::component::{DelegateComponent, HasComponents}; +use crate::core::traits::sync::Async; + +use crate::relay::traits::chains::HasRelayChains; +use crate::std_prelude::*; + +pub struct PacketClearerComponent; + +#[async_trait] +pub trait PacketClearer: Async +where + Relay: HasRelayChains, +{ + async fn clear_packets( + relay: &Relay, + src_channel_id: &ChannelId, + src_port_id: &PortId, + dst_channel_id: &ChannelId, + dst_port_id: &PortId, + ) -> Result<(), Relay::Error>; +} + +#[async_trait] +impl PacketClearer for Component +where + Relay: HasRelayChains, + Component: DelegateComponent, + Component::Delegate: PacketClearer, +{ + async fn clear_packets( + relay: &Relay, + src_channel_id: &ChannelId, + src_port_id: &PortId, + dst_channel_id: &ChannelId, + dst_port_id: &PortId, + ) -> Result<(), Relay::Error> { + Component::Delegate::clear_packets( + relay, + src_channel_id, + src_port_id, + dst_channel_id, + dst_port_id, + ) + .await + } +} + +#[async_trait] +pub trait CanClearPackets: HasRelayChains { + async fn clear_packets( + &self, + src_channel_id: &ChannelId, + src_port_id: &PortId, + dst_channel_id: &ChannelId, + dst_port_id: &PortId, + ) -> Result<(), Self::Error>; +} + +#[async_trait] +impl CanClearPackets for Relay +where + Relay: HasRelayChains + HasComponents, + Relay::Components: PacketClearer, +{ + async fn clear_packets( + &self, + src_channel_id: &ChannelId, + src_port_id: &PortId, + dst_channel_id: &ChannelId, + dst_port_id: &PortId, + ) -> Result<(), Self::Error> { + Relay::Components::clear_packets( + self, + src_channel_id, + src_port_id, + dst_channel_id, + dst_port_id, + ) + .await + } +} diff --git a/crates/relayer-components/src/relay/traits/components/packet_filter.rs b/crates/relayer-components/src/relay/traits/components/packet_filter.rs new file mode 100644 index 0000000000..9bf5fb0553 --- /dev/null +++ b/crates/relayer-components/src/relay/traits/components/packet_filter.rs @@ -0,0 +1,50 @@ +use async_trait::async_trait; + +use crate::core::traits::component::{DelegateComponent, HasComponents}; +use crate::core::traits::sync::Async; +use crate::relay::traits::chains::HasRelayChains; +use crate::std_prelude::*; + +pub struct PacketFilterComponent; + +#[async_trait] +pub trait PacketFilter: Async +where + Relay: HasRelayChains, +{ + async fn should_relay_packet( + relay: &Relay, + packet: &Relay::Packet, + ) -> Result; +} + +#[async_trait] +impl PacketFilter for Component +where + Relay: HasRelayChains, + Component: DelegateComponent, + Component::Delegate: PacketFilter, +{ + async fn should_relay_packet( + relay: &Relay, + packet: &Relay::Packet, + ) -> Result { + Component::Delegate::should_relay_packet(relay, packet).await + } +} + +#[async_trait] +pub trait CanFilterPackets: HasRelayChains { + async fn should_relay_packet(&self, packet: &Self::Packet) -> Result; +} + +#[async_trait] +impl CanFilterPackets for Relay +where + Relay: HasRelayChains + HasComponents, + Relay::Components: PacketFilter, +{ + async fn should_relay_packet(&self, packet: &Self::Packet) -> Result { + Relay::Components::should_relay_packet(self, packet).await + } +} diff --git a/crates/relayer-components/src/relay/traits/components/packet_relayer.rs b/crates/relayer-components/src/relay/traits/components/packet_relayer.rs new file mode 100644 index 0000000000..2ee2a1bbaa --- /dev/null +++ b/crates/relayer-components/src/relay/traits/components/packet_relayer.rs @@ -0,0 +1,44 @@ +use async_trait::async_trait; + +use crate::core::traits::component::{DelegateComponent, HasComponents}; +use crate::core::traits::sync::Async; +use crate::relay::traits::chains::HasRelayChains; +use crate::std_prelude::*; + +pub struct PacketRelayerComponent; + +#[async_trait] +pub trait PacketRelayer: Async +where + Relay: HasRelayChains, +{ + async fn relay_packet(relay: &Relay, packet: &Relay::Packet) -> Result<(), Relay::Error>; +} + +#[async_trait] +impl PacketRelayer for Component +where + Relay: HasRelayChains, + Component: DelegateComponent, + Component::Delegate: PacketRelayer, +{ + async fn relay_packet(relay: &Relay, packet: &Relay::Packet) -> Result<(), Relay::Error> { + Component::Delegate::relay_packet(relay, packet).await + } +} + +#[async_trait] +pub trait CanRelayPacket: HasRelayChains { + async fn relay_packet(&self, packet: &Self::Packet) -> Result<(), Self::Error>; +} + +#[async_trait] +impl CanRelayPacket for Relay +where + Relay: HasRelayChains + HasComponents, + Relay::Components: PacketRelayer, +{ + async fn relay_packet(&self, packet: &Self::Packet) -> Result<(), Self::Error> { + Relay::Components::relay_packet(self, packet).await + } +} diff --git a/crates/relayer-components/src/relay/traits/components/packet_relayers/ack_packet.rs b/crates/relayer-components/src/relay/traits/components/packet_relayers/ack_packet.rs new file mode 100644 index 0000000000..3a14a3c3a0 --- /dev/null +++ b/crates/relayer-components/src/relay/traits/components/packet_relayers/ack_packet.rs @@ -0,0 +1,72 @@ +use async_trait::async_trait; + +use crate::chain::traits::types::ibc_events::write_ack::HasWriteAcknowledgementEvent; +use crate::chain::types::aliases::{Height, WriteAcknowledgementEvent}; +use crate::core::traits::component::{DelegateComponent, HasComponents}; +use crate::core::traits::sync::Async; +use crate::relay::traits::chains::HasRelayChains; +use crate::std_prelude::*; + +pub struct AckPacketRelayerComponent; + +#[async_trait] +pub trait AckPacketRelayer: Async +where + Relay: HasRelayChains, + Relay::DstChain: HasWriteAcknowledgementEvent, +{ + async fn relay_ack_packet( + relay: &Relay, + destination_height: &Height, + packet: &Relay::Packet, + ack: &WriteAcknowledgementEvent, + ) -> Result<(), Relay::Error>; +} + +#[async_trait] +impl AckPacketRelayer for Component +where + Relay: HasRelayChains, + Component: DelegateComponent, + Relay::DstChain: HasWriteAcknowledgementEvent, + Component::Delegate: AckPacketRelayer, +{ + async fn relay_ack_packet( + relay: &Relay, + destination_height: &Height, + packet: &Relay::Packet, + ack: &WriteAcknowledgementEvent, + ) -> Result<(), Relay::Error> { + Component::Delegate::relay_ack_packet(relay, destination_height, packet, ack).await + } +} + +#[async_trait] +pub trait CanRelayAckPacket: HasRelayChains +where + Self::DstChain: HasWriteAcknowledgementEvent, +{ + async fn relay_ack_packet( + &self, + destination_height: &Height, + packet: &Self::Packet, + ack: &WriteAcknowledgementEvent, + ) -> Result<(), Self::Error>; +} + +#[async_trait] +impl CanRelayAckPacket for Relay +where + Relay: HasRelayChains + HasComponents, + Relay::DstChain: HasWriteAcknowledgementEvent, + Relay::Components: AckPacketRelayer, +{ + async fn relay_ack_packet( + &self, + destination_height: &Height, + packet: &Self::Packet, + ack: &WriteAcknowledgementEvent, + ) -> Result<(), Self::Error> { + Relay::Components::relay_ack_packet(self, destination_height, packet, ack).await + } +} diff --git a/crates/relayer-components/src/relay/traits/components/packet_relayers/mod.rs b/crates/relayer-components/src/relay/traits/components/packet_relayers/mod.rs new file mode 100644 index 0000000000..999ab81f0c --- /dev/null +++ b/crates/relayer-components/src/relay/traits/components/packet_relayers/mod.rs @@ -0,0 +1,3 @@ +pub mod ack_packet; +pub mod receive_packet; +pub mod timeout_unordered_packet; diff --git a/crates/relayer-components/src/relay/traits/components/packet_relayers/receive_packet.rs b/crates/relayer-components/src/relay/traits/components/packet_relayers/receive_packet.rs new file mode 100644 index 0000000000..766217bcb9 --- /dev/null +++ b/crates/relayer-components/src/relay/traits/components/packet_relayers/receive_packet.rs @@ -0,0 +1,70 @@ +use async_trait::async_trait; + +use crate::chain::traits::types::ibc_events::write_ack::HasWriteAcknowledgementEvent; +use crate::chain::types::aliases::{Height, WriteAcknowledgementEvent}; +use crate::core::traits::component::{DelegateComponent, HasComponents}; +use crate::core::traits::sync::Async; +use crate::relay::traits::chains::HasRelayChains; +use crate::std_prelude::*; + +pub struct ReceivePacketRelayerComponnent; + +#[async_trait] +pub trait ReceivePacketRelayer: Async +where + Relay: HasRelayChains, + Relay::DstChain: HasWriteAcknowledgementEvent, +{ + async fn relay_receive_packet( + relay: &Relay, + source_height: &Height, + packet: &Relay::Packet, + ) -> Result>, Relay::Error>; +} + +#[async_trait] +impl ReceivePacketRelayer for Component +where + Relay: HasRelayChains, + Component: DelegateComponent, + Relay::DstChain: HasWriteAcknowledgementEvent, + Component::Delegate: ReceivePacketRelayer, +{ + async fn relay_receive_packet( + relay: &Relay, + source_height: &Height, + packet: &Relay::Packet, + ) -> Result>, Relay::Error> + { + Component::Delegate::relay_receive_packet(relay, source_height, packet).await + } +} + +#[async_trait] +pub trait CanRelayReceivePacket: HasRelayChains +where + Self::DstChain: HasWriteAcknowledgementEvent, +{ + async fn relay_receive_packet( + &self, + source_height: &Height, + packet: &Self::Packet, + ) -> Result>, Self::Error>; +} + +#[async_trait] +impl CanRelayReceivePacket for Relay +where + Relay: HasRelayChains + HasComponents, + Relay::DstChain: HasWriteAcknowledgementEvent, + Relay::Components: ReceivePacketRelayer, +{ + async fn relay_receive_packet( + &self, + source_height: &Height, + packet: &Relay::Packet, + ) -> Result>, Relay::Error> + { + Relay::Components::relay_receive_packet(self, source_height, packet).await + } +} diff --git a/crates/relayer-components/src/relay/traits/components/packet_relayers/timeout_unordered_packet.rs b/crates/relayer-components/src/relay/traits/components/packet_relayers/timeout_unordered_packet.rs new file mode 100644 index 0000000000..05b5484ce7 --- /dev/null +++ b/crates/relayer-components/src/relay/traits/components/packet_relayers/timeout_unordered_packet.rs @@ -0,0 +1,71 @@ +use async_trait::async_trait; + +use crate::chain::types::aliases::Height; +use crate::core::traits::component::{DelegateComponent, HasComponents}; +use crate::core::traits::sync::Async; +use crate::relay::traits::chains::HasRelayChains; +use crate::std_prelude::*; + +pub struct TimeoutUnorderedPacketRelayerComponent; + +#[async_trait] +pub trait TimeoutUnorderedPacketRelayer: Async +where + Relay: HasRelayChains, +{ + async fn relay_timeout_unordered_packet( + relay: &Relay, + destination_height: &Height, + packet: &Relay::Packet, + ) -> Result<(), Relay::Error>; +} + +#[async_trait] +impl TimeoutUnorderedPacketRelayer for Component +where + Relay: HasRelayChains, + Component: DelegateComponent, + Component::Delegate: TimeoutUnorderedPacketRelayer, +{ + async fn relay_timeout_unordered_packet( + relay: &Relay, + destination_height: &Height, + packet: &Relay::Packet, + ) -> Result<(), Relay::Error> { + Component::Delegate::relay_timeout_unordered_packet(relay, destination_height, packet).await + } +} + +/// Encapsulates the capability of a relayer to send timeout packets over +/// unordered channels. +/// +/// Timeout packets are sent from a destination chain to the source chain that +/// originated the timed out message. +/// +/// When a timeout packet is sent, a response is not expected to be received. +/// This is in contrast when sending e.g. receive packets, which expect to +/// receive back a `WriteAcknowledgementEvent` in response to the receive +/// packet. +#[async_trait] +pub trait CanRelayTimeoutUnorderedPacket: HasRelayChains { + async fn relay_timeout_unordered_packet( + &self, + destination_height: &Height, + packet: &Self::Packet, + ) -> Result<(), Self::Error>; +} + +#[async_trait] +impl CanRelayTimeoutUnorderedPacket for Relay +where + Relay: HasRelayChains + HasComponents, + Relay::Components: TimeoutUnorderedPacketRelayer, +{ + async fn relay_timeout_unordered_packet( + &self, + destination_height: &Height, + packet: &Self::Packet, + ) -> Result<(), Self::Error> { + Relay::Components::relay_timeout_unordered_packet(self, destination_height, packet).await + } +} diff --git a/crates/relayer-components/src/relay/traits/components/update_client_message_builder.rs b/crates/relayer-components/src/relay/traits/components/update_client_message_builder.rs new file mode 100644 index 0000000000..e4871a9f18 --- /dev/null +++ b/crates/relayer-components/src/relay/traits/components/update_client_message_builder.rs @@ -0,0 +1,107 @@ +use async_trait::async_trait; + +use crate::chain::traits::components::message_sender::CanSendMessages; +use crate::chain::types::aliases::{Height, Message}; +use crate::core::traits::component::{DelegateComponent, HasComponents}; +use crate::relay::traits::chains::HasRelayChains; +use crate::relay::traits::target::ChainTarget; +use crate::std_prelude::*; + +pub struct UpdateClientMessageBuilderComponent; + +#[async_trait] +pub trait UpdateClientMessageBuilder +where + Relay: HasRelayChains, + Target: ChainTarget, +{ + async fn build_update_client_messages( + relay: &Relay, + _target: Target, + height: &Height, + ) -> Result>, Relay::Error>; +} + +#[async_trait] +impl UpdateClientMessageBuilder for Component +where + Relay: HasRelayChains, + Target: ChainTarget, + Component: DelegateComponent, + Component::Delegate: UpdateClientMessageBuilder, +{ + async fn build_update_client_messages( + relay: &Relay, + target: Target, + height: &Height, + ) -> Result>, Relay::Error> { + Component::Delegate::build_update_client_messages(relay, target, height).await + } +} + +#[async_trait] +pub trait CanBuildUpdateClientMessage: HasRelayChains +where + Target: ChainTarget, +{ + async fn build_update_client_messages( + &self, + _target: Target, + height: &Height, + ) -> Result>, Self::Error>; +} + +#[async_trait] +impl CanBuildUpdateClientMessage for Relay +where + Relay: HasRelayChains + HasComponents, + Target: ChainTarget, + Relay::Components: UpdateClientMessageBuilder, +{ + async fn build_update_client_messages( + &self, + target: Target, + height: &Height, + ) -> Result>, Self::Error> { + Relay::Components::build_update_client_messages(self, target, height).await + } +} + +#[async_trait] +pub trait CanSendUpdateClientMessage: HasRelayChains +where + Target: ChainTarget, +{ + async fn send_update_client_messages( + &self, + target: Target, + height: &Height, + ) -> Result<(), Self::Error>; +} + +#[async_trait] +impl CanSendUpdateClientMessage for Relay +where + Relay: CanBuildUpdateClientMessage, + Target: ChainTarget, + Target::TargetChain: CanSendMessages, +{ + async fn send_update_client_messages( + &self, + target: Target, + height: &Height, + ) -> Result<(), Self::Error> { + let messages = self.build_update_client_messages(target, height).await?; + + // If there are no UpdateClient messages returned, it means that the IBC client is + // already up to date. + if !messages.is_empty() { + Target::target_chain(self) + .send_messages(messages) + .await + .map_err(Target::target_chain_error)?; + } + + Ok(()) + } +} diff --git a/crates/relayer-components/src/relay/traits/connection/mod.rs b/crates/relayer-components/src/relay/traits/connection/mod.rs new file mode 100644 index 0000000000..ab6b3cdeab --- /dev/null +++ b/crates/relayer-components/src/relay/traits/connection/mod.rs @@ -0,0 +1,25 @@ +/*! + This module contains the trait interfaces for implementing the + [IBC connection handshake protocol](https://github.com/cosmos/ibc/tree/main/spec/core/ics-003-connection-semantics). + The protocol establishes a connection between the two participating blockchains. + This connection between the two chains is necessary in order for channels to be established between them. + + Each of the different `open_*` modules, except for `open_handshake`, defines + a component responsible for a single step in the handshake protocol. + The `open_handshake` module wires together the steps that actually constitute + the handshake protocol proper. + The `open_init` step is not strictly part of the handshake, as it may be initiated + by external users; it merely signals the start of the handshake process. + + The relay context only handles the connection handshake initiated in one direction, + i.e. as initiated by the source chain and acknowledged by the destination chain. + If a connection is first initialized at the destination chain, it will not be + handled by this relay context. Instead, it will be handled by the counterpart + relay context that has the source and destination chains flipped. +*/ + +pub mod open_ack; +pub mod open_confirm; +pub mod open_handshake; +pub mod open_init; +pub mod open_try; diff --git a/crates/relayer-components/src/relay/traits/connection/open_ack.rs b/crates/relayer-components/src/relay/traits/connection/open_ack.rs new file mode 100644 index 0000000000..2f94e4b892 --- /dev/null +++ b/crates/relayer-components/src/relay/traits/connection/open_ack.rs @@ -0,0 +1,26 @@ +use async_trait::async_trait; + +use crate::relay::traits::chains::HasRelayChains; +use crate::relay::types::aliases::{DstConnectionId, SrcConnectionId}; +use crate::std_prelude::*; + +#[async_trait] +pub trait CanRelayConnectionOpenAck: HasRelayChains { + async fn relay_connection_open_ack( + &self, + src_connection_id: &SrcConnectionId, + dst_connection_id: &DstConnectionId, + ) -> Result<(), Self::Error>; +} + +#[async_trait] +pub trait ConnectionOpenAckRelayer +where + Relay: HasRelayChains, +{ + async fn relay_connection_open_ack( + relay: &Relay, + src_connection_id: &SrcConnectionId, + dst_connection_id: &DstConnectionId, + ) -> Result<(), Relay::Error>; +} diff --git a/crates/relayer-components/src/relay/traits/connection/open_confirm.rs b/crates/relayer-components/src/relay/traits/connection/open_confirm.rs new file mode 100644 index 0000000000..c0784425de --- /dev/null +++ b/crates/relayer-components/src/relay/traits/connection/open_confirm.rs @@ -0,0 +1,26 @@ +use async_trait::async_trait; + +use crate::relay::traits::chains::HasRelayChains; +use crate::relay::types::aliases::{DstConnectionId, SrcConnectionId}; +use crate::std_prelude::*; + +#[async_trait] +pub trait CanRelayConnectionOpenConfirm: HasRelayChains { + async fn relay_connection_open_confirm( + &self, + src_connection_id: &SrcConnectionId, + dst_connection_id: &DstConnectionId, + ) -> Result<(), Self::Error>; +} + +#[async_trait] +pub trait ConnectionOpenConfirmRelayer +where + Relay: HasRelayChains, +{ + async fn relay_connection_open_confirm( + relay: &Relay, + src_connection_id: &SrcConnectionId, + dst_connection_id: &DstConnectionId, + ) -> Result<(), Relay::Error>; +} diff --git a/crates/relayer-components/src/relay/traits/connection/open_handshake.rs b/crates/relayer-components/src/relay/traits/connection/open_handshake.rs new file mode 100644 index 0000000000..f8514f6cb3 --- /dev/null +++ b/crates/relayer-components/src/relay/traits/connection/open_handshake.rs @@ -0,0 +1,24 @@ +use async_trait::async_trait; + +use crate::relay::traits::chains::HasRelayChains; +use crate::relay::types::aliases::{DstConnectionId, SrcConnectionId}; +use crate::std_prelude::*; + +#[async_trait] +pub trait CanRelayConnectionOpenHandshake: HasRelayChains { + async fn relay_connection_open_handshake( + &self, + src_connection_id: &SrcConnectionId, + ) -> Result, Self::Error>; +} + +#[async_trait] +pub trait ConnectionOpenHandshakeRelayer +where + Relay: HasRelayChains, +{ + async fn relay_connection_open_handshake( + relay: &Relay, + connection_id: &SrcConnectionId, + ) -> Result, Relay::Error>; +} diff --git a/crates/relayer-components/src/relay/traits/connection/open_init.rs b/crates/relayer-components/src/relay/traits/connection/open_init.rs new file mode 100644 index 0000000000..797d1cdbb8 --- /dev/null +++ b/crates/relayer-components/src/relay/traits/connection/open_init.rs @@ -0,0 +1,33 @@ +use async_trait::async_trait; + +use crate::chain::traits::types::connection::HasInitConnectionOptionsType; +use crate::relay::traits::chains::HasRelayChains; +use crate::relay::types::aliases::SrcConnectionId; +use crate::std_prelude::*; + +#[async_trait] +pub trait CanInitConnection: HasRelayChains +where + Self::SrcChain: HasInitConnectionOptionsType, +{ + async fn init_connection( + &self, + init_connection_options: &>::InitConnectionOptions, + ) -> Result, Self::Error>; +} + +#[async_trait] +pub trait ConnectionInitializer +where + Relay: HasRelayChains, + Relay::SrcChain: HasInitConnectionOptionsType, +{ + async fn init_connection( + relay: &Relay, + init_connection_options: &>::InitConnectionOptions, + ) -> Result, Relay::Error>; +} diff --git a/crates/relayer-components/src/relay/traits/connection/open_try.rs b/crates/relayer-components/src/relay/traits/connection/open_try.rs new file mode 100644 index 0000000000..cd06fde26c --- /dev/null +++ b/crates/relayer-components/src/relay/traits/connection/open_try.rs @@ -0,0 +1,24 @@ +use async_trait::async_trait; + +use crate::relay::traits::chains::HasRelayChains; +use crate::relay::types::aliases::{DstConnectionId, SrcConnectionId}; +use crate::std_prelude::*; + +#[async_trait] +pub trait CanRelayConnectionOpenTry: HasRelayChains { + async fn relay_connection_open_try( + &self, + src_connection_id: &SrcConnectionId, + ) -> Result, Self::Error>; +} + +#[async_trait] +pub trait ConnectionOpenTryRelayer +where + Relay: HasRelayChains, +{ + async fn relay_connection_open_try( + relay: &Relay, + src_connection_id: &SrcConnectionId, + ) -> Result, Relay::Error>; +} diff --git a/crates/relayer-components/src/relay/traits/logs/event.rs b/crates/relayer-components/src/relay/traits/logs/event.rs new file mode 100644 index 0000000000..90d513b66c --- /dev/null +++ b/crates/relayer-components/src/relay/traits/logs/event.rs @@ -0,0 +1,59 @@ +use crate::chain::traits::logs::event::CanLogChainEvent; +use crate::chain::traits::types::event::HasEventType; +use crate::logger::traits::has_logger::HasLoggerType; +use crate::logger::traits::logger::BaseLogger; +use crate::relay::traits::chains::HasRelayChains; +use crate::relay::traits::target::ChainTarget; + +pub trait CanLogRelayEvent: HasRelayChains + HasLoggerType { + fn log_src_event<'a>( + event: &'a ::Event, + ) -> ::LogValue<'a>; + + fn log_dst_event<'a>( + event: &'a ::Event, + ) -> ::LogValue<'a>; +} + +impl CanLogRelayEvent for Relay +where + Logger: BaseLogger, + Relay: HasRelayChains + HasLoggerType, + Relay::SrcChain: CanLogChainEvent, + Relay::DstChain: CanLogChainEvent, +{ + fn log_src_event<'a>( + event: &'a ::Event, + ) -> ::LogValue<'a> { + Self::SrcChain::log_event(event) + } + + fn log_dst_event<'a>( + event: &'a ::Event, + ) -> ::LogValue<'a> { + Self::DstChain::log_event(event) + } +} + +pub trait CanLogTargetEvent: HasRelayChains + HasLoggerType +where + Target: ChainTarget, +{ + fn log_target_event<'a>( + event: &'a ::Event, + ) -> ::LogValue<'a>; +} + +impl CanLogTargetEvent for Relay +where + Logger: BaseLogger, + Relay: HasRelayChains + HasLoggerType, + Target: ChainTarget, + Target::TargetChain: CanLogChainEvent, +{ + fn log_target_event<'a>( + event: &'a ::Event, + ) -> ::LogValue<'a> { + Target::TargetChain::log_event(event) + } +} diff --git a/crates/relayer-components/src/relay/traits/logs/logger.rs b/crates/relayer-components/src/relay/traits/logs/logger.rs new file mode 100644 index 0000000000..fab914ee3f --- /dev/null +++ b/crates/relayer-components/src/relay/traits/logs/logger.rs @@ -0,0 +1,85 @@ +use crate::chain::traits::types::chain_id::HasChainId; +use crate::logger::traits::level::HasLoggerWithBaseLevels; +use crate::logger::traits::log::CanLog; +use crate::logger::traits::logger::BaseLogger; +use crate::logger::types::wrapper::LogWrapper; +use crate::relay::traits::chains::HasRelayChains; +use crate::relay::traits::target::ChainTarget; + +pub trait CanLogRelay: HasLoggerWithBaseLevels { + fn log_relay<'a>( + &'a self, + level: ::LogLevel, + message: &str, + build_log: impl for<'r> FnOnce(LogWrapper<'a, 'r, Self::Logger>), + ); +} + +impl CanLogRelay for Relay +where + Relay: CanLog + HasRelayChains, + Relay::SrcChain: HasChainId, + Relay::DstChain: HasChainId, +{ + fn log_relay<'a>( + &'a self, + level: ::LogLevel, + message: &str, + build_log: impl for<'r> FnOnce(LogWrapper<'a, 'r, Self::Logger>), + ) { + self.log(level, message, |log| { + log.nested("relay_context", |log| { + log.display("src_chain_id", self.src_chain().chain_id()); + log.display("dst_chain_id", self.dst_chain().chain_id()); + log.display("src_client_id", self.src_client_id()); + log.display("dst_client_id", self.dst_client_id()); + }); + + build_log(log); + }) + } +} + +pub trait CanLogRelayTarget: HasRelayChains + HasLoggerWithBaseLevels +where + Target: ChainTarget, +{ + fn log_relay_target<'a>( + &'a self, + level: ::LogLevel, + message: &str, + build_log: impl for<'r> FnOnce(LogWrapper<'a, 'r, Self::Logger>), + ); +} + +impl CanLogRelayTarget for Relay +where + Relay: CanLogRelay + HasRelayChains, + Target::TargetChain: HasChainId, + Target::CounterpartyChain: HasChainId, + Target: ChainTarget, +{ + fn log_relay_target<'a>( + &'a self, + level: ::LogLevel, + message: &str, + build_log: impl for<'r> FnOnce(LogWrapper<'a, 'r, Self::Logger>), + ) { + self.log_relay(level, message, |log| { + log.nested("target_relay_context", |log| { + log.display("target_chain_id", Target::target_chain(self).chain_id()); + log.display( + "counterparty_chain_id", + Target::counterparty_chain(self).chain_id(), + ); + log.display("target_client_id", Target::target_client_id(self)); + log.display( + "counterparty_client_id", + Target::counterparty_client_id(self), + ); + }); + + build_log(log); + }) + } +} diff --git a/crates/relayer-components/src/relay/traits/logs/mod.rs b/crates/relayer-components/src/relay/traits/logs/mod.rs new file mode 100644 index 0000000000..525762c470 --- /dev/null +++ b/crates/relayer-components/src/relay/traits/logs/mod.rs @@ -0,0 +1,3 @@ +pub mod event; +pub mod logger; +pub mod packet; diff --git a/crates/relayer-components/src/relay/traits/logs/packet.rs b/crates/relayer-components/src/relay/traits/logs/packet.rs new file mode 100644 index 0000000000..6aa0196f3f --- /dev/null +++ b/crates/relayer-components/src/relay/traits/logs/packet.rs @@ -0,0 +1,19 @@ +use crate::chain::traits::logs::packet::CanLogChainPacket; +use crate::logger::traits::has_logger::HasLoggerType; +use crate::logger::traits::logger::BaseLogger; +use crate::relay::traits::chains::HasRelayChains; + +pub trait CanLogRelayPacket: HasRelayChains + HasLoggerType { + fn log_packet<'a>(packet: &'a Self::Packet) -> ::LogValue<'a>; +} + +impl CanLogRelayPacket for Relay +where + Logger: BaseLogger, + Relay: HasRelayChains + HasLoggerType, + Relay::SrcChain: CanLogChainPacket, +{ + fn log_packet<'a>(packet: &'a Self::Packet) -> Logger::LogValue<'a> { + Relay::SrcChain::log_outgoing_packet(packet) + } +} diff --git a/crates/relayer-components/src/relay/traits/mod.rs b/crates/relayer-components/src/relay/traits/mod.rs new file mode 100644 index 0000000000..4e0004efc4 --- /dev/null +++ b/crates/relayer-components/src/relay/traits/mod.rs @@ -0,0 +1,10 @@ +pub mod chains; +pub mod channel; +pub mod clear_interval; +pub mod components; +pub mod connection; +pub mod logs; +pub mod packet; +pub mod packet_lock; +pub mod target; +pub mod two_way; diff --git a/crates/relayer-components/src/relay/traits/packet.rs b/crates/relayer-components/src/relay/traits/packet.rs new file mode 100644 index 0000000000..366d3aef59 --- /dev/null +++ b/crates/relayer-components/src/relay/traits/packet.rs @@ -0,0 +1,80 @@ +use crate::chain::traits::components::packet_fields_reader::CanReadPacketFields; +use crate::chain::types::aliases::{ChannelId, Height, PortId, Sequence, Timestamp}; +use crate::relay::traits::chains::HasRelayChains; + +pub trait HasRelayPacketFields: HasRelayChains { + /** + The source port of a packet, which is a port ID on the source chain + that corresponds to the destination chain. + */ + fn packet_src_port(packet: &Self::Packet) -> &PortId; + + /** + The source channel ID of a packet, which is a channel ID on the source chain + that corresponds to the destination chain. + */ + fn packet_src_channel_id(packet: &Self::Packet) -> &ChannelId; + + /** + The destination port of a packet, which is a port ID on the destination chain + that corresponds to the source chain. + */ + fn packet_dst_port(packet: &Self::Packet) -> &PortId; + + /** + The destination channel ID of a packet, which is a channel ID on the destination chain + that corresponds to the source chain. + */ + fn packet_dst_channel_id(packet: &Self::Packet) -> &ChannelId; + + /** + The sequence a packet, which is a sequence stored on the source chain + that corresponds to the destination chain. + */ + fn packet_sequence(packet: &Self::Packet) -> &Sequence; + + /** + The optional timeout height of a packet, which is a height on the destination chain. + */ + fn packet_timeout_height(packet: &Self::Packet) -> Option<&Height>; + + /** + The timeout timestamp of a packet, which is a timestamp on the destination chain. + */ + fn packet_timeout_timestamp(packet: &Self::Packet) -> &Timestamp; +} + +impl HasRelayPacketFields for Relay +where + Relay: HasRelayChains, + SrcChain: CanReadPacketFields, + DstChain: CanReadPacketFields, +{ + fn packet_src_port(packet: &Self::Packet) -> &PortId { + SrcChain::outgoing_packet_src_port(packet) + } + + fn packet_src_channel_id(packet: &Self::Packet) -> &ChannelId { + SrcChain::outgoing_packet_src_channel_id(packet) + } + + fn packet_dst_port(packet: &Self::Packet) -> &PortId { + SrcChain::outgoing_packet_dst_port(packet) + } + + fn packet_dst_channel_id(packet: &Self::Packet) -> &ChannelId { + SrcChain::outgoing_packet_dst_channel_id(packet) + } + + fn packet_sequence(packet: &Self::Packet) -> &Sequence { + SrcChain::outgoing_packet_sequence(packet) + } + + fn packet_timeout_height(packet: &Self::Packet) -> Option<&Height> { + SrcChain::outgoing_packet_timeout_height(packet) + } + + fn packet_timeout_timestamp(packet: &Self::Packet) -> &Timestamp { + SrcChain::outgoing_packet_timeout_timestamp(packet) + } +} diff --git a/crates/relayer-components/src/relay/traits/packet_lock.rs b/crates/relayer-components/src/relay/traits/packet_lock.rs new file mode 100644 index 0000000000..c5faf55c04 --- /dev/null +++ b/crates/relayer-components/src/relay/traits/packet_lock.rs @@ -0,0 +1,47 @@ +use async_trait::async_trait; + +use crate::relay::traits::chains::HasRelayChains; +use crate::std_prelude::*; + +/** + Provides a packet lock mutex for packet relayers to coordinate and avoid + relaying the same packet at the same time. + + Before a packet relayer starts relaying, it should try and acquire a + [`PacketLock`](Self::PacketLock) from the relay context. The packet lock + would act as a mutex guard and stays active for its lifetime duration. + During this period, acquiring the packet lock for another packet would fail, + and this would signal to other packet relayers to skip relaying the given + packet. +*/ +#[async_trait] +pub trait HasPacketLock: HasRelayChains { + /** + The mutex guard for a locked packet. This should be kept alive while + the packet relayer is relaying a packet. + + During the lifetime of an acquired packet lock, other calls to + [`try_acquire_packet_lock`](Self::try_acquire_packet_lock) on the + same packet should return `None`. + */ + type PacketLock<'a>: Send; + + /** + Try and acquire the lock for a given packet. Returns `Some` if the + acquire is success, and `None` if the packet lock has already been + acquired. + + When the method returns `None`, this signals that another packet relayer + is already relaying a given packet, and the caller should abort any + relaying operation for that packet. + + When the method returns `Some`, the caller should retain the + [`PacketLock`](Self::PacketLock) value throughout the relaying operation + for the given packet. This would cause other callers that try to lock + the same packet to get `None` returned. + */ + async fn try_acquire_packet_lock<'a>( + &'a self, + packet: &'a Self::Packet, + ) -> Option>; +} diff --git a/crates/relayer-components/src/relay/traits/target.rs b/crates/relayer-components/src/relay/traits/target.rs new file mode 100644 index 0000000000..bb67d74e66 --- /dev/null +++ b/crates/relayer-components/src/relay/traits/target.rs @@ -0,0 +1,108 @@ +use crate::chain::traits::types::ibc::HasIbcChainTypes; +use crate::chain::types::aliases::ClientId; +use crate::core::traits::error::HasErrorType; +use crate::core::traits::sync::Async; +use crate::relay::traits::chains::HasRelayChains; + +#[derive(Default, Clone, Copy)] +pub struct SourceTarget; + +#[derive(Default, Clone, Copy)] +pub struct DestinationTarget; + +pub trait ChainTarget: Async + Default + Copy + private::Sealed { + type TargetChain: HasIbcChainTypes + HasErrorType; + + type CounterpartyChain: HasIbcChainTypes + HasErrorType; + + fn target_chain_error(e: ::Error) -> Relay::Error; + + fn counterparty_chain_error( + e: ::Error, + ) -> Relay::Error; + + fn target_chain(relay: &Relay) -> &Self::TargetChain; + + fn counterparty_chain(relay: &Relay) -> &Self::CounterpartyChain; + + fn target_client_id(relay: &Relay) -> &ClientId; + + fn counterparty_client_id( + relay: &Relay, + ) -> &ClientId; +} + +impl private::Sealed for SourceTarget {} +impl private::Sealed for DestinationTarget {} + +impl ChainTarget for SourceTarget { + type TargetChain = Relay::SrcChain; + + type CounterpartyChain = Relay::DstChain; + + fn target_chain_error(e: ::Error) -> Relay::Error { + Relay::src_chain_error(e) + } + + fn counterparty_chain_error( + e: ::Error, + ) -> Relay::Error { + Relay::dst_chain_error(e) + } + + fn target_chain(context: &Relay) -> &Self::TargetChain { + context.src_chain() + } + + fn counterparty_chain(context: &Relay) -> &Self::CounterpartyChain { + context.dst_chain() + } + + fn target_client_id(context: &Relay) -> &ClientId { + context.src_client_id() + } + + fn counterparty_client_id( + context: &Relay, + ) -> &ClientId { + context.dst_client_id() + } +} + +impl ChainTarget for DestinationTarget { + type TargetChain = Relay::DstChain; + + type CounterpartyChain = Relay::SrcChain; + + fn target_chain_error(e: ::Error) -> Relay::Error { + Relay::dst_chain_error(e) + } + + fn counterparty_chain_error( + e: ::Error, + ) -> Relay::Error { + Relay::src_chain_error(e) + } + + fn target_chain(context: &Relay) -> &Self::TargetChain { + context.dst_chain() + } + + fn counterparty_chain(context: &Relay) -> &Self::CounterpartyChain { + context.src_chain() + } + + fn target_client_id(context: &Relay) -> &ClientId { + context.dst_client_id() + } + + fn counterparty_client_id( + context: &Relay, + ) -> &ClientId { + context.src_client_id() + } +} + +mod private { + pub trait Sealed {} +} diff --git a/crates/relayer-components/src/relay/traits/two_way.rs b/crates/relayer-components/src/relay/traits/two_way.rs new file mode 100644 index 0000000000..dfc62a5b4d --- /dev/null +++ b/crates/relayer-components/src/relay/traits/two_way.rs @@ -0,0 +1,40 @@ +use crate::core::traits::error::HasErrorType; +use crate::relay::traits::chains::HasRelayChains; + +/// Trait for types that have a two-way relay context, i.e., +/// those that can relay in both directions between two connected +/// chains. +/// +/// Two-way relay contexts are composed of two separate relay +/// contexts, one that relays from chain A to chain B, the +/// other that relays from chain B to chain A. +pub trait HasTwoWayRelay: HasErrorType { + /// The relay context that relays from chain A to chain B. + type RelayAToB: HasRelayChains; + + /// The relay context that relays from chain B to chain A. + /// + /// In order to ensure that this relay context is indeed + /// relaying between the same two chains as the `RelayAToB` + /// context, we assert that the `RelayBToA` context's source + /// chain is the `RelayAToB` context's destination chain and + /// vice versa. In addition, we also assert that both relay + /// context's have the same error type. + type RelayBToA: HasRelayChains< + SrcChain = ::DstChain, + DstChain = ::SrcChain, + Error = ::Error, + >; + + /// Returns a read-only reference to the relay context from chain A + /// to chain B. + fn relay_a_to_b(&self) -> &Self::RelayAToB; + + /// Returns a read-only reference to the relay context from chain B + /// to chain A. + fn relay_b_to_a(&self) -> &Self::RelayBToA; + + /// Converts an error from a one-way relay context into an error from + /// a two-way relay context. + fn relay_error(e: ::Error) -> Self::Error; +} diff --git a/crates/relayer-components/src/relay/types/aliases.rs b/crates/relayer-components/src/relay/types/aliases.rs new file mode 100644 index 0000000000..79b8e39fda --- /dev/null +++ b/crates/relayer-components/src/relay/types/aliases.rs @@ -0,0 +1,22 @@ +use crate::chain::traits::types::ibc::HasIbcChainTypes; +use crate::relay::traits::chains::HasRelayChains; + +pub type Packet = ::Packet; + +pub type SrcChain = ::SrcChain; + +pub type DstChain = ::DstChain; + +pub type SrcConnectionId = + as HasIbcChainTypes>>::ConnectionId; + +pub type DstConnectionId = + as HasIbcChainTypes>>::ConnectionId; + +pub type SrcPortId = as HasIbcChainTypes>>::PortId; + +pub type DstPortId = as HasIbcChainTypes>>::PortId; + +pub type SrcChannelId = as HasIbcChainTypes>>::ChannelId; + +pub type DstChannelId = as HasIbcChainTypes>>::ChannelId; diff --git a/crates/relayer-components/src/relay/types/mod.rs b/crates/relayer-components/src/relay/types/mod.rs new file mode 100644 index 0000000000..7f4204fa1d --- /dev/null +++ b/crates/relayer-components/src/relay/types/mod.rs @@ -0,0 +1,2 @@ +pub mod aliases; +pub mod relay_to_chain; diff --git a/crates/relayer-components/src/relay/types/relay_to_chain.rs b/crates/relayer-components/src/relay/types/relay_to_chain.rs new file mode 100644 index 0000000000..0c5b9770d8 --- /dev/null +++ b/crates/relayer-components/src/relay/types/relay_to_chain.rs @@ -0,0 +1,120 @@ +use core::marker::PhantomData; + +use async_trait::async_trait; + +use crate::chain::traits::components::message_sender::CanSendMessages; +use crate::chain::traits::types::chain_id::HasChainIdType; +use crate::chain::traits::types::event::HasEventType; +use crate::chain::traits::types::height::HasHeightType; +use crate::chain::traits::types::message::{CanEstimateMessageSize, HasMessageType}; +use crate::chain::traits::types::timestamp::HasTimestampType; +use crate::core::traits::error::HasErrorType; +use crate::relay::traits::chains::HasRelayChains; +use crate::relay::traits::target::ChainTarget; +use crate::std_prelude::*; + +/** + A wrapper type that wraps a relay context with a target into a chain context. + + This allows the relay context to be used on components that require a chain context. + The main use case is to wrap the relay context to implement [`CanSendMessages`]. + + The relay context on its own can implement + [`CanSendIbcMessages`](crate::relay::traits::components::ibc_message_sender::CanSendIbcMessages) + but not [`CanSendMessages`], as the former is parameterized by a relay target. + The two traits also have different charasteristics, as `CanSendIbcMessages` allows + middleware components such as + [`SendIbcMessagesWithUpdateClient`](crate::relay::impls::message_senders::update_client::SendIbcMessagesWithUpdateClient) + to access the relay context and build `UpdateClient` messages to append to the + front of incoming messages. On the other hand, [`CanSendMessages`] can only + access the chain context but not the counterparty chain context, thus not able + to build `UpdateClient` messages in the middle of the pipeline. + + With the `RelayToChain` wrapper, this allows the relay context to pretend that it + is a chain context while also holding the counterparty chain context. The wrapper + allows the relay context to be used with components that need to be polymorphic + over any context that supports sending of messages, such as the batch worker + component. +*/ + +pub struct RelayToChain { + pub relay: Relay, + pub phantom: PhantomData, +} + +impl HasErrorType for RelayToChain +where + Relay: HasRelayChains, + Target: ChainTarget, +{ + type Error = Relay::Error; +} + +impl HasMessageType for RelayToChain +where + Relay: HasRelayChains, + Target: ChainTarget, +{ + type Message = ::Message; +} + +impl CanEstimateMessageSize for RelayToChain +where + Relay: HasRelayChains, + Target: ChainTarget, + Target::TargetChain: CanEstimateMessageSize, +{ + fn estimate_message_size(message: &Self::Message) -> Result { + Target::TargetChain::estimate_message_size(message).map_err(Target::target_chain_error) + } +} + +impl HasEventType for RelayToChain +where + Relay: HasRelayChains, + Target: ChainTarget, +{ + type Event = ::Event; +} + +impl HasHeightType for RelayToChain +where + Relay: HasRelayChains, + Target: ChainTarget, +{ + type Height = ::Height; +} + +impl HasChainIdType for RelayToChain +where + Relay: HasRelayChains, + Target: ChainTarget, +{ + type ChainId = ::ChainId; +} + +impl HasTimestampType for RelayToChain +where + Relay: HasRelayChains, + Target: ChainTarget, +{ + type Timestamp = ::Timestamp; +} + +#[async_trait] +impl CanSendMessages for RelayToChain +where + Relay: HasRelayChains, + Target: ChainTarget, + Target::TargetChain: CanSendMessages, +{ + async fn send_messages( + &self, + messages: Vec, + ) -> Result>, Self::Error> { + Target::target_chain(&self.relay) + .send_messages(messages) + .await + .map_err(Target::target_chain_error) + } +} diff --git a/crates/relayer-components/src/runtime/impls/mod.rs b/crates/relayer-components/src/runtime/impls/mod.rs new file mode 100644 index 0000000000..3e515356fa --- /dev/null +++ b/crates/relayer-components/src/runtime/impls/mod.rs @@ -0,0 +1 @@ +pub mod subscription; diff --git a/crates/relayer-components/src/runtime/impls/subscription/closure.rs b/crates/relayer-components/src/runtime/impls/subscription/closure.rs new file mode 100644 index 0000000000..11ee999897 --- /dev/null +++ b/crates/relayer-components/src/runtime/impls/subscription/closure.rs @@ -0,0 +1,102 @@ +use alloc::sync::Arc; +use core::future::Future; +use core::pin::Pin; + +use async_trait::async_trait; +use futures_core::stream::Stream; + +use crate::core::traits::sync::Async; +use crate::runtime::traits::mutex::HasMutex; +use crate::runtime::traits::subscription::Subscription; +use crate::std_prelude::*; + +/** + An auto trait that is implemented by all runtime contexts that implement + [`HasMutex`]. This allows simple creation of [`Subscription`] values by + wrapping an async closure that returns the same thing as + [`subscribe`](Subscription::subscribe). + + The returned [`Subscription`] also implements guard to skip calling the + underlying closure once the closure returns `None`. +*/ +pub trait CanCreateClosureSubscription { + fn new_closure_subscription( + subscribe: impl Fn() -> Pin< + Box< + dyn Future + Send + 'static>>>> + + Send + + 'static, + >, + > + Send + + Sync + + 'static, + ) -> Arc>; +} + +impl CanCreateClosureSubscription for Runtime +where + Runtime: HasMutex, +{ + fn new_closure_subscription( + subscribe: impl Fn() -> Pin< + Box< + dyn Future + Send + 'static>>>> + + Send + + 'static, + >, + > + Send + + Sync + + 'static, + ) -> Arc> { + let subscription: SubscriptionClosure = SubscriptionClosure { + terminated: Runtime::new_mutex(false), + subscribe: Box::new(subscribe), + }; + + Arc::new(subscription) + } +} + +struct SubscriptionClosure +where + Runtime: HasMutex, +{ + terminated: ::Mutex, + subscribe: Box< + dyn Fn() -> Pin< + Box< + dyn Future + Send + 'static>>>> + + Send + + 'static, + >, + > + Send + + Sync + + 'static, + >, +} + +#[async_trait] +impl Subscription for SubscriptionClosure +where + Runtime: HasMutex, +{ + type Item = T; + + async fn subscribe(&self) -> Option + Send + 'static>>> { + let mut terminated = Runtime::acquire_mutex(&self.terminated).await; + + if *terminated { + // If a subscription is terminated, it always return `None` from + // that point onward. + None + } else { + let m_stream = (self.subscribe)().await; + + if m_stream.is_none() { + *terminated = true; + } + + m_stream + } + } +} diff --git a/crates/relayer-components/src/runtime/impls/subscription/empty.rs b/crates/relayer-components/src/runtime/impls/subscription/empty.rs new file mode 100644 index 0000000000..c1b0506b59 --- /dev/null +++ b/crates/relayer-components/src/runtime/impls/subscription/empty.rs @@ -0,0 +1,32 @@ +use core::marker::PhantomData; +use core::pin::Pin; + +use async_trait::async_trait; +use futures_core::stream::Stream; + +use crate::core::traits::sync::Async; +use crate::runtime::traits::subscription::Subscription; +use crate::std_prelude::*; + +pub struct EmptySubscription(pub PhantomData); + +impl Default for EmptySubscription { + fn default() -> Self { + Self(PhantomData) + } +} + +impl EmptySubscription { + pub fn new() -> Self { + Self(PhantomData) + } +} + +#[async_trait] +impl Subscription for EmptySubscription { + type Item = T; + + async fn subscribe(&self) -> Option + Send + 'static>>> { + None + } +} diff --git a/crates/relayer-components/src/runtime/impls/subscription/mod.rs b/crates/relayer-components/src/runtime/impls/subscription/mod.rs new file mode 100644 index 0000000000..7b8bc2f5b7 --- /dev/null +++ b/crates/relayer-components/src/runtime/impls/subscription/mod.rs @@ -0,0 +1,2 @@ +pub mod closure; +pub mod empty; diff --git a/crates/relayer-components/src/runtime/mod.rs b/crates/relayer-components/src/runtime/mod.rs new file mode 100644 index 0000000000..086f4987a0 --- /dev/null +++ b/crates/relayer-components/src/runtime/mod.rs @@ -0,0 +1,7 @@ +/*! + Constructs for the runtime context. +*/ + +pub mod impls; +pub mod traits; +pub mod types; diff --git a/crates/relayer-components/src/runtime/traits/mod.rs b/crates/relayer-components/src/runtime/traits/mod.rs new file mode 100644 index 0000000000..bedb4d97ae --- /dev/null +++ b/crates/relayer-components/src/runtime/traits/mod.rs @@ -0,0 +1,5 @@ +pub mod mutex; +pub mod runtime; +pub mod sleep; +pub mod subscription; +pub mod time; diff --git a/crates/relayer-components/src/runtime/traits/mutex.rs b/crates/relayer-components/src/runtime/traits/mutex.rs new file mode 100644 index 0000000000..6c6eb00931 --- /dev/null +++ b/crates/relayer-components/src/runtime/traits/mutex.rs @@ -0,0 +1,30 @@ +use core::ops::DerefMut; + +use async_trait::async_trait; + +use crate::core::traits::sync::Async; +use crate::runtime::traits::runtime::HasRuntime; +use crate::std_prelude::*; + +#[async_trait] +pub trait HasMutex: Async { + type Mutex: Async; + + type MutexGuard<'a, T: Async>: 'a + Send + Sync + DerefMut; + + fn new_mutex(item: T) -> Self::Mutex; + + async fn acquire_mutex<'a, T: Async>(mutex: &'a Self::Mutex) -> Self::MutexGuard<'a, T>; +} + +pub trait HasRuntimeWithMutex: HasRuntime { + type RuntimeWithMutex: HasMutex; +} + +impl HasRuntimeWithMutex for Context +where + Context: HasRuntime, + Context::Runtime: HasMutex, +{ + type RuntimeWithMutex = Context::Runtime; +} diff --git a/crates/relayer-components/src/runtime/traits/runtime.rs b/crates/relayer-components/src/runtime/traits/runtime.rs new file mode 100644 index 0000000000..7d3e58bb52 --- /dev/null +++ b/crates/relayer-components/src/runtime/traits/runtime.rs @@ -0,0 +1,9 @@ +use crate::core::traits::error::HasErrorType; + +pub trait HasRuntime: HasErrorType { + type Runtime: HasErrorType; + + fn runtime(&self) -> &Self::Runtime; + + fn runtime_error(e: ::Error) -> Self::Error; +} diff --git a/crates/relayer-components/src/runtime/traits/sleep.rs b/crates/relayer-components/src/runtime/traits/sleep.rs new file mode 100644 index 0000000000..d66d0dc02e --- /dev/null +++ b/crates/relayer-components/src/runtime/traits/sleep.rs @@ -0,0 +1,11 @@ +use core::time::Duration; + +use async_trait::async_trait; + +use crate::core::traits::sync::Async; +use crate::std_prelude::*; + +#[async_trait] +pub trait CanSleep: Async { + async fn sleep(&self, duration: Duration); +} diff --git a/crates/relayer-components/src/runtime/traits/subscription.rs b/crates/relayer-components/src/runtime/traits/subscription.rs new file mode 100644 index 0000000000..25b6efc79b --- /dev/null +++ b/crates/relayer-components/src/runtime/traits/subscription.rs @@ -0,0 +1,98 @@ +use alloc::sync::Arc; +use core::pin::Pin; + +use async_trait::async_trait; +use futures_core::stream::Stream; + +use crate::core::traits::sync::Async; +use crate::std_prelude::*; + +/** + A [`Subscription`] is a multi-consumer abstraction over a single-consumer + [`Stream`] construct. A [`Subscription`] value can be shared by wrapping + it inside an `Arc`. Each call to the + [`subscribe`](Self::subscribe) method would optionally return a [`Stream`] + that can be used by a single consumer. + + The expected behavior of a [`Subscription`] implementation is that the + [`Stream`]s returned from multiple calls to [`subscribe`](Self::subscribe) + should yield the same stream of items, modulo the race conditions between + each calls and errors from underlying sources. + + A naive implementation of [`Subscription`] would subscribe from multiple + underlying sources, such as a network connection, each time + [`subscribe`](Self::subscribe) is called. This may be inefficient as each + stream would have to open new network connections, but it is simpler and + more resilient to error conditions such as network disconnections. A simple + way to implement a naive subscription is to use + [`CanCreateClosureSubscription`](crate::runtime::impls::subscription::closure::CanCreateClosureSubscription) + to turn a closure into a [`Subscription`]. + + A [`Subscription`] implementation could be made efficient by sharing one + incoming [`Stream`] with multiple consumers, by multiplexing them to multiple + outgoing [`Stream`]s inside a background task. An example implementation of + this is `CanStreamSubscription`, which multiplexes a single stream into a + [`Subscription`]. A more advanced version of wrapping is provided by + `CanMultiplexSubscription`, which wraps around a naive [`Subscription`] and + perform both stream multiplexing and auto recovery from a background task by + calling the underlying `subscribe` function. + + A [`Subscription`] do not guarantee whether the returned [`Stream`] is + finite or infinite (long-running). As a result, the [`Stream`] returned + from [`subscribe`](Self::subscribe) may terminate, in case if there is + underlying source encounter errors such as network disconnection. However, + a long-running consumer may call [`subscribe`](Self::subscribe) again in + attempt to obtain a new [`Stream`]. + + A [`Subscription`] can be terminated by an underlying controller, such as + during program shutdown. When a subscription is terminated, it is expected + to return `None` for all subsequent calls to [`subscribe`](Self::subscribe). + A long-running consumer can treat the returned `None` as a signal that + the subscription is terminated, and in turns terminate itself. The + underlying controller is also expected to terminate all currently running + [`Stream`]s, so that the running consumers would receive the termination + signal. +*/ +#[async_trait] +pub trait Subscription: Send + Sync + 'static { + /** + The item that is yielded in the [`Stream`]s returned from + [`subscribe`](Self::subscribe). + */ + type Item: Async; + + /** + If the subscription is still active, returns a new single consumer + [`Stream`] which would produce a stream of items that are produced + _after_ the method is called. + + The items produced prior to the call to [`subscribe`](Self::subscribe) + are lost. This is to allow the underlying subscription implementation + to preserve memory and not store all items that are produced since the + subscription is created. + + If the subscription is terminated, the method would return `None`. + Callers that receive `None` should expect all subsequent calls to + [`subscribe`](Self::subscribe) to also return `None`, and perform + appropriate actions for termination. + */ + async fn subscribe(&self) -> Option + Send + 'static>>>; +} + +#[async_trait] +impl Subscription for Box> { + type Item = T; + + async fn subscribe(&self) -> Option + Send + 'static>>> { + self.as_ref().subscribe().await + } +} + +#[async_trait] +impl Subscription for Arc> { + type Item = T; + + async fn subscribe(&self) -> Option + Send + 'static>>> { + self.as_ref().subscribe().await + } +} diff --git a/crates/relayer-components/src/runtime/traits/time.rs b/crates/relayer-components/src/runtime/traits/time.rs new file mode 100644 index 0000000000..dcc9081ac6 --- /dev/null +++ b/crates/relayer-components/src/runtime/traits/time.rs @@ -0,0 +1,11 @@ +use core::time::Duration; + +use crate::core::traits::sync::Async; + +pub trait HasTime: Async { + type Time: Async; + + fn now(&self) -> Self::Time; + + fn duration_since(current_time: &Self::Time, other_time: &Self::Time) -> Duration; +} diff --git a/crates/relayer-components/src/runtime/types/aliases.rs b/crates/relayer-components/src/runtime/types/aliases.rs new file mode 100644 index 0000000000..46c76964bb --- /dev/null +++ b/crates/relayer-components/src/runtime/types/aliases.rs @@ -0,0 +1,6 @@ +use crate::runtime::traits::mutex::HasMutex; +use crate::runtime::traits::runtime::HasRuntime; + +pub type Runtime = ::Runtime; + +pub type Mutex = as HasMutex>::Mutex; diff --git a/crates/relayer-components/src/runtime/types/mod.rs b/crates/relayer-components/src/runtime/types/mod.rs new file mode 100644 index 0000000000..d293a01b6f --- /dev/null +++ b/crates/relayer-components/src/runtime/types/mod.rs @@ -0,0 +1 @@ +pub mod aliases; diff --git a/crates/relayer-components/src/std_prelude.rs b/crates/relayer-components/src/std_prelude.rs new file mode 100644 index 0000000000..ac791e1a54 --- /dev/null +++ b/crates/relayer-components/src/std_prelude.rs @@ -0,0 +1,12 @@ +// Re-export according to alloc::prelude::v1 because it is not yet stabilized +// https://doc.rust-lang.org/src/alloc/prelude/v1.rs.html +pub use alloc::borrow::ToOwned; +pub use alloc::boxed::Box; +pub use alloc::format; +pub use alloc::string::{String, ToString}; +pub use alloc::vec; +pub use alloc::vec::Vec; +// Those are exported by default in the std prelude in Rust 2021 +pub use core::convert::{TryFrom, TryInto}; +pub use core::iter::FromIterator; +pub use core::prelude::v1::*; diff --git a/crates/relayer-components/src/transaction/components/message_as_tx.rs b/crates/relayer-components/src/transaction/components/message_as_tx.rs new file mode 100644 index 0000000000..da98a159df --- /dev/null +++ b/crates/relayer-components/src/transaction/components/message_as_tx.rs @@ -0,0 +1,99 @@ +use async_trait::async_trait; + +use crate::logger::traits::level::HasBaseLogLevels; +use crate::std_prelude::*; +use crate::transaction::traits::components::message_as_tx_sender::MessageAsTxSender; +use crate::transaction::traits::components::tx_encoder::CanEncodeTx; +use crate::transaction::traits::components::tx_fee_estimater::CanEstimateTxFee; +use crate::transaction::traits::components::tx_response_poller::CanPollTxResponse; +use crate::transaction::traits::components::tx_submitter::CanSubmitTx; +use crate::transaction::traits::fee::HasFeeForSimulation; +use crate::transaction::traits::logs::logger::CanLogTx; +use crate::transaction::traits::logs::nonce::CanLogNonce; +use crate::transaction::traits::types::HasTxTypes; + +pub struct EstimateFeesAndSendTx; + +#[async_trait] +impl MessageAsTxSender for EstimateFeesAndSendTx +where + Context: HasTxTypes + + HasFeeForSimulation + + CanEncodeTx + + CanEstimateTxFee + + CanSubmitTx + + CanPollTxResponse + + CanLogTx + + CanLogNonce, +{ + async fn send_messages_as_tx( + context: &Context, + signer: &Context::Signer, + nonce: &Context::Nonce, + messages: &[Context::Message], + ) -> Result { + context.log_tx( + Context::Logger::LEVEL_TRACE, + "encoding tx for simulation", + |log| { + log.field("none", Context::log_nonce(nonce)); + }, + ); + + let fee_for_simulation = context.fee_for_simulation(); + + let simulate_tx = context + .encode_tx(signer, nonce, fee_for_simulation, messages) + .await?; + + context.log_tx( + Context::Logger::LEVEL_TRACE, + "estimating fee with tx for simulation", + |log| { + log.field("none", Context::log_nonce(nonce)); + }, + ); + + let tx_fee = context.estimate_tx_fee(&simulate_tx).await?; + + context.log_tx( + Context::Logger::LEVEL_TRACE, + "encoding tx for submission", + |log| { + log.field("none", Context::log_nonce(nonce)); + }, + ); + + let tx = context.encode_tx(signer, nonce, &tx_fee, messages).await?; + + context.log_tx( + Context::Logger::LEVEL_TRACE, + "submitting tx to chain", + |log| { + log.field("none", Context::log_nonce(nonce)); + }, + ); + + let tx_hash = context.submit_tx(&tx).await?; + + context.log_tx( + Context::Logger::LEVEL_TRACE, + "waiting for tx hash response", + |log| { + log.field("none", Context::log_nonce(nonce)); + }, + ); + + let response = context.poll_tx_response(&tx_hash).await?; + + context.log_tx( + Context::Logger::LEVEL_TRACE, + "received tx hash response", + |log| { + log.field("none", Context::log_nonce(nonce)); + }, + ); + + Ok(response) + } +} diff --git a/crates/relayer-components/src/transaction/components/message_sender/mod.rs b/crates/relayer-components/src/transaction/components/message_sender/mod.rs new file mode 100644 index 0000000000..f71763d26a --- /dev/null +++ b/crates/relayer-components/src/transaction/components/message_sender/mod.rs @@ -0,0 +1 @@ +pub mod send_as_tx; diff --git a/crates/relayer-components/src/transaction/components/message_sender/send_as_tx.rs b/crates/relayer-components/src/transaction/components/message_sender/send_as_tx.rs new file mode 100644 index 0000000000..97e00a328b --- /dev/null +++ b/crates/relayer-components/src/transaction/components/message_sender/send_as_tx.rs @@ -0,0 +1,37 @@ +use async_trait::async_trait; + +use crate::chain::traits::components::message_sender::MessageSender; +use crate::std_prelude::*; +use crate::transaction::traits::components::message_as_tx_sender::CanSendMessagesAsTx; +use crate::transaction::traits::components::nonce_allocater::CanAllocateNonce; +use crate::transaction::traits::event::CanParseTxResponseAsEvents; +use crate::transaction::traits::signer::HasSigner; +use crate::transaction::traits::types::HasTxTypes; + +pub struct SendMessagesAsTx; + +#[async_trait] +impl MessageSender for SendMessagesAsTx +where + Chain: HasTxTypes + + HasSigner + + CanAllocateNonce + + CanSendMessagesAsTx + + CanParseTxResponseAsEvents, +{ + async fn send_messages( + chain: &Chain, + messages: Vec, + ) -> Result>, Chain::Error> { + let signer = chain.get_signer(); + let nonce = chain.allocate_nonce(signer).await?; + + let response = chain + .send_messages_as_tx(signer, Chain::deref_nonce(&nonce), &messages) + .await?; + + let events = Chain::parse_tx_response_as_events(response)?; + + Ok(events) + } +} diff --git a/crates/relayer-components/src/transaction/components/mod.rs b/crates/relayer-components/src/transaction/components/mod.rs new file mode 100644 index 0000000000..2212938426 --- /dev/null +++ b/crates/relayer-components/src/transaction/components/mod.rs @@ -0,0 +1,4 @@ +pub mod message_as_tx; +pub mod message_sender; +pub mod nonce; +pub mod poll; diff --git a/crates/relayer-components/src/transaction/components/nonce/mod.rs b/crates/relayer-components/src/transaction/components/nonce/mod.rs new file mode 100644 index 0000000000..479802a095 --- /dev/null +++ b/crates/relayer-components/src/transaction/components/nonce/mod.rs @@ -0,0 +1 @@ +pub mod mutex; diff --git a/crates/relayer-components/src/transaction/components/nonce/mutex.rs b/crates/relayer-components/src/transaction/components/nonce/mutex.rs new file mode 100644 index 0000000000..81cb592643 --- /dev/null +++ b/crates/relayer-components/src/transaction/components/nonce/mutex.rs @@ -0,0 +1,46 @@ +use async_trait::async_trait; + +use crate::logger::traits::level::HasBaseLogLevels; +use crate::runtime::traits::mutex::HasMutex; +use crate::std_prelude::*; +use crate::transaction::traits::components::nonce_allocater::NonceAllocator; +use crate::transaction::traits::components::nonce_querier::CanQueryNonce; +use crate::transaction::traits::logs::logger::CanLogTx; +use crate::transaction::traits::logs::nonce::CanLogNonce; +use crate::transaction::traits::nonce::mutex::HasMutexForNonceAllocation; + +pub struct AllocateNonceWithMutex; + +#[async_trait] +impl NonceAllocator for AllocateNonceWithMutex +where + Context: CanLogTx + CanLogNonce + CanQueryNonce + HasMutexForNonceAllocation, + Context::Runtime: HasMutex, +{ + async fn allocate_nonce<'a>( + context: &'a Context, + signer: &'a Context::Signer, + ) -> Result, Context::Error> { + context.log_tx( + Context::Logger::LEVEL_TRACE, + "acquiring nonce mutex", + |_| {}, + ); + + let mutex = context.mutex_for_nonce_allocation(signer); + + let mutex_guard = Context::Runtime::acquire_mutex(mutex).await; + + context.log_tx(Context::Logger::LEVEL_TRACE, "acquired nonce mutex", |_| {}); + + let nonce = context.query_nonce(signer).await?; + + context.log_tx(Context::Logger::LEVEL_TRACE, "assigned nonce", |log| { + log.field("nonce", Context::log_nonce(&nonce)); + }); + + let nonce_guard = Context::mutex_to_nonce_guard(mutex_guard, nonce); + + Ok(nonce_guard) + } +} diff --git a/crates/relayer-components/src/transaction/components/poll.rs b/crates/relayer-components/src/transaction/components/poll.rs new file mode 100644 index 0000000000..084b8e2f6d --- /dev/null +++ b/crates/relayer-components/src/transaction/components/poll.rs @@ -0,0 +1,102 @@ +use core::time::Duration; + +use async_trait::async_trait; + +use crate::logger::traits::level::HasBaseLogLevels; +use crate::runtime::traits::runtime::HasRuntime; +use crate::runtime::traits::sleep::CanSleep; +use crate::runtime::traits::time::HasTime; +use crate::std_prelude::*; +use crate::transaction::traits::components::tx_response_poller::TxResponsePoller; +use crate::transaction::traits::components::tx_response_querier::CanQueryTxResponse; +use crate::transaction::traits::logs::logger::CanLogTx; +use crate::transaction::traits::types::HasTxTypes; + +pub trait CanRaiseNoTxResponseError: HasTxTypes { + fn tx_no_response_error(tx_hash: &Self::TxHash) -> Self::Error; +} + +pub trait HasPollTimeout { + fn poll_timeout(&self) -> Duration; + + fn poll_backoff(&self) -> Duration; +} + +pub struct PollTxResponse; + +#[async_trait] +impl TxResponsePoller for PollTxResponse +where + Context: + CanLogTx + CanQueryTxResponse + HasPollTimeout + HasRuntime + CanRaiseNoTxResponseError, + Context::Runtime: HasTime + CanSleep, +{ + async fn poll_tx_response( + context: &Context, + tx_hash: &Context::TxHash, + ) -> Result { + let runtime = context.runtime(); + let wait_timeout = context.poll_timeout(); + let wait_backoff = context.poll_backoff(); + + let start_time = runtime.now(); + + loop { + let response = context.query_tx_response(tx_hash).await; + + match response { + Ok(None) => { + let elapsed = Context::Runtime::duration_since(&start_time, &runtime.now()); + if elapsed > wait_timeout { + context.log_tx( + Context::Logger::LEVEL_ERROR, + "no tx response received, and poll timeout has recached. returning error", + |log| { + log.debug("elapsed", &elapsed).debug("wait_timeout", &wait_timeout); + } + ); + + return Err(Context::tx_no_response_error(tx_hash)); + } else { + runtime.sleep(wait_backoff).await; + } + } + Ok(Some(response)) => { + context.log_tx( + Context::Logger::LEVEL_TRACE, + "received tx response, finish polling", + |_| {}, + ); + + return Ok(response); + } + Err(e) => { + context.log_tx( + Context::Logger::LEVEL_ERROR, + "query_tx_response returned error", + |log| { + log.debug("error", &e); + }, + ); + + /* + If querying the TX response returns failure, it might be a temporary network + failure that can be recovered later on. Hence it would not be good if + we return error immediately, as we may still have the chance to get a + proper transaction response later on. + + However, if the query still returns error after the wait timeout exceeded, + we return the error we get from the query. + */ + + let elapsed = Context::Runtime::duration_since(&start_time, &runtime.now()); + if elapsed > wait_timeout { + return Err(e); + } else { + runtime.sleep(wait_backoff).await; + } + } + } + } + } +} diff --git a/crates/relayer-components/src/transaction/impls/encoders/max_tx_size.rs b/crates/relayer-components/src/transaction/impls/encoders/max_tx_size.rs new file mode 100644 index 0000000000..eb051cbc36 --- /dev/null +++ b/crates/relayer-components/src/transaction/impls/encoders/max_tx_size.rs @@ -0,0 +1,59 @@ +/*! + TODO: the max transaction size may not be checked within the transaction + encoder. Doing so may interfer with the nonce allocator, as it would + invalidate subsequent nonces that are allocated, since the currently + allocated nonce is not used. +*/ + +use core::marker::PhantomData; + +use async_trait::async_trait; + +use crate::core::traits::error::InjectError; +use crate::std_prelude::*; +use crate::transaction::traits::components::tx_encoder::TxEncoder; +use crate::transaction::traits::types::HasTxTypes; + +pub struct MaxTxSizeExceededError { + pub max_tx_size: usize, + pub given_tx_size: usize, +} + +pub trait HasMaxTxSizeExceededError: InjectError { + fn try_extract_max_tx_size_exceeded_error(e: Self::Error) -> Option; +} + +pub trait HasMaxTxSize { + fn max_tx_size(&self) -> usize; +} + +pub struct CheckEncodedTxSize(PhantomData); + +#[async_trait] +impl TxEncoder for CheckEncodedTxSize +where + Context: HasTxTypes + HasMaxTxSize + HasMaxTxSizeExceededError, + InEncoder: TxEncoder, +{ + async fn encode_tx( + context: &Context, + signer: &Context::Signer, + nonce: &Context::Nonce, + fee: &Context::Fee, + messages: &[Context::Message], + ) -> Result { + let tx = InEncoder::encode_tx(context, signer, nonce, fee, messages).await?; + + let given_tx_size = Context::tx_size(&tx); + let max_tx_size = context.max_tx_size(); + + if given_tx_size > max_tx_size { + Err(Context::inject_error(MaxTxSizeExceededError { + given_tx_size, + max_tx_size, + })) + } else { + Ok(tx) + } + } +} diff --git a/crates/relayer-components/src/transaction/impls/encoders/mod.rs b/crates/relayer-components/src/transaction/impls/encoders/mod.rs new file mode 100644 index 0000000000..5554ca73c0 --- /dev/null +++ b/crates/relayer-components/src/transaction/impls/encoders/mod.rs @@ -0,0 +1 @@ +pub mod max_tx_size; diff --git a/crates/relayer-components/src/transaction/impls/estimate_recovery.rs b/crates/relayer-components/src/transaction/impls/estimate_recovery.rs new file mode 100644 index 0000000000..b6f8878aff --- /dev/null +++ b/crates/relayer-components/src/transaction/impls/estimate_recovery.rs @@ -0,0 +1,32 @@ +use core::marker::PhantomData; + +use async_trait::async_trait; + +use crate::std_prelude::*; +use crate::transaction::traits::components::tx_fee_estimater::TxFeeEstimator; +use crate::transaction::traits::types::HasTxTypes; + +pub trait CanRecoverEstimateError: HasTxTypes { + fn try_recover_estimate_error(&self, e: Self::Error) -> Result; +} + +pub struct TryRecoverEstimateError(pub PhantomData); + +#[async_trait] +impl TxFeeEstimator for TryRecoverEstimateError +where + Context: CanRecoverEstimateError, + InEstimator: TxFeeEstimator, +{ + async fn estimate_tx_fee( + context: &Context, + tx: &Context::Transaction, + ) -> Result { + let res = InEstimator::estimate_tx_fee(context, tx).await; + + match res { + Ok(fee) => Ok(fee), + Err(e) => context.try_recover_estimate_error(e), + } + } +} diff --git a/crates/relayer-components/src/transaction/impls/mod.rs b/crates/relayer-components/src/transaction/impls/mod.rs new file mode 100644 index 0000000000..7f3f41ee69 --- /dev/null +++ b/crates/relayer-components/src/transaction/impls/mod.rs @@ -0,0 +1,2 @@ +pub mod encoders; +pub mod estimate_recovery; diff --git a/crates/relayer-components/src/transaction/mod.rs b/crates/relayer-components/src/transaction/mod.rs new file mode 100644 index 0000000000..0fe90c5ef5 --- /dev/null +++ b/crates/relayer-components/src/transaction/mod.rs @@ -0,0 +1,7 @@ +/*! + Constructs for the transaction context. +*/ + +pub mod components; +pub mod impls; +pub mod traits; diff --git a/crates/relayer-components/src/transaction/traits/components/message_as_tx_sender.rs b/crates/relayer-components/src/transaction/traits/components/message_as_tx_sender.rs new file mode 100644 index 0000000000..de04dc597e --- /dev/null +++ b/crates/relayer-components/src/transaction/traits/components/message_as_tx_sender.rs @@ -0,0 +1,63 @@ +use async_trait::async_trait; + +use crate::core::traits::component::{DelegateComponent, HasComponents}; +use crate::std_prelude::*; +use crate::transaction::traits::types::HasTxTypes; + +pub struct MessageAsTxSenderComponent; + +#[async_trait] +pub trait MessageAsTxSender +where + TxContext: HasTxTypes, +{ + async fn send_messages_as_tx( + context: &TxContext, + signer: &TxContext::Signer, + nonce: &TxContext::Nonce, + messages: &[TxContext::Message], + ) -> Result; +} + +#[async_trait] +impl MessageAsTxSender for Component +where + TxContext: HasTxTypes, + Component: DelegateComponent, + Component::Delegate: MessageAsTxSender, +{ + async fn send_messages_as_tx( + context: &TxContext, + signer: &TxContext::Signer, + nonce: &TxContext::Nonce, + messages: &[TxContext::Message], + ) -> Result { + Component::Delegate::send_messages_as_tx(context, signer, nonce, messages).await + } +} + +#[async_trait] +pub trait CanSendMessagesAsTx: HasTxTypes { + async fn send_messages_as_tx( + &self, + signer: &Self::Signer, + nonce: &Self::Nonce, + messages: &[Self::Message], + ) -> Result; +} + +#[async_trait] +impl CanSendMessagesAsTx for TxContext +where + TxContext: HasTxTypes + HasComponents, + TxContext::Components: MessageAsTxSender, +{ + async fn send_messages_as_tx( + &self, + signer: &Self::Signer, + nonce: &Self::Nonce, + messages: &[Self::Message], + ) -> Result { + TxContext::Components::send_messages_as_tx(self, signer, nonce, messages).await + } +} diff --git a/crates/relayer-components/src/transaction/traits/components/mod.rs b/crates/relayer-components/src/transaction/traits/components/mod.rs new file mode 100644 index 0000000000..ae96b4eca0 --- /dev/null +++ b/crates/relayer-components/src/transaction/traits/components/mod.rs @@ -0,0 +1,8 @@ +pub mod message_as_tx_sender; +pub mod nonce_allocater; +pub mod nonce_querier; +pub mod tx_encoder; +pub mod tx_fee_estimater; +pub mod tx_response_poller; +pub mod tx_response_querier; +pub mod tx_submitter; diff --git a/crates/relayer-components/src/transaction/traits/components/nonce_allocater.rs b/crates/relayer-components/src/transaction/traits/components/nonce_allocater.rs new file mode 100644 index 0000000000..6dc249220e --- /dev/null +++ b/crates/relayer-components/src/transaction/traits/components/nonce_allocater.rs @@ -0,0 +1,57 @@ +use async_trait::async_trait; + +use crate::core::traits::component::{DelegateComponent, HasComponents}; +use crate::core::traits::error::HasErrorType; +use crate::std_prelude::*; +use crate::transaction::traits::nonce::guard::HasNonceGuard; +use crate::transaction::traits::types::HasSignerType; + +pub struct NonceAllocatorComponent; + +#[async_trait] +pub trait NonceAllocator +where + TxContext: HasNonceGuard + HasSignerType + HasErrorType, +{ + async fn allocate_nonce<'a>( + context: &'a TxContext, + signer: &'a TxContext::Signer, + ) -> Result, TxContext::Error>; +} + +#[async_trait] +impl NonceAllocator for Component +where + TxContext: HasNonceGuard + HasSignerType + HasErrorType, + Component: DelegateComponent, + Component::Delegate: NonceAllocator, +{ + async fn allocate_nonce<'a>( + context: &'a TxContext, + signer: &'a TxContext::Signer, + ) -> Result, TxContext::Error> { + Component::Delegate::allocate_nonce(context, signer).await + } +} + +#[async_trait] +pub trait CanAllocateNonce: HasNonceGuard + HasSignerType + HasErrorType { + async fn allocate_nonce<'a>( + &'a self, + signer: &'a Self::Signer, + ) -> Result, Self::Error>; +} + +#[async_trait] +impl CanAllocateNonce for TxContext +where + TxContext: HasNonceGuard + HasSignerType + HasErrorType + HasComponents, + TxContext::Components: NonceAllocator, +{ + async fn allocate_nonce<'a>( + &'a self, + signer: &'a TxContext::Signer, + ) -> Result, TxContext::Error> { + TxContext::Components::allocate_nonce(self, signer).await + } +} diff --git a/crates/relayer-components/src/transaction/traits/components/nonce_querier.rs b/crates/relayer-components/src/transaction/traits/components/nonce_querier.rs new file mode 100644 index 0000000000..8a10ee420c --- /dev/null +++ b/crates/relayer-components/src/transaction/traits/components/nonce_querier.rs @@ -0,0 +1,49 @@ +use async_trait::async_trait; + +use crate::core::traits::component::{DelegateComponent, HasComponents}; +use crate::std_prelude::*; +use crate::transaction::traits::types::HasTxTypes; + +pub struct NonceQuerierComponent; + +#[async_trait] +pub trait NonceQuerier +where + TxContext: HasTxTypes, +{ + async fn query_nonce( + context: &TxContext, + signer: &TxContext::Signer, + ) -> Result; +} + +#[async_trait] +pub trait CanQueryNonce: HasTxTypes { + async fn query_nonce(&self, signer: &Self::Signer) -> Result; +} + +#[async_trait] +impl NonceQuerier for Component +where + TxContext: HasTxTypes, + Component: DelegateComponent, + Component::Delegate: NonceQuerier, +{ + async fn query_nonce( + context: &TxContext, + signer: &TxContext::Signer, + ) -> Result { + Component::Delegate::query_nonce(context, signer).await + } +} + +#[async_trait] +impl CanQueryNonce for TxContext +where + TxContext: HasTxTypes + HasComponents, + TxContext::Components: NonceQuerier, +{ + async fn query_nonce(&self, signer: &Self::Signer) -> Result { + TxContext::Components::query_nonce(self, signer).await + } +} diff --git a/crates/relayer-components/src/transaction/traits/components/tx_encoder.rs b/crates/relayer-components/src/transaction/traits/components/tx_encoder.rs new file mode 100644 index 0000000000..32515ef2ca --- /dev/null +++ b/crates/relayer-components/src/transaction/traits/components/tx_encoder.rs @@ -0,0 +1,68 @@ +use async_trait::async_trait; + +use crate::core::traits::component::{DelegateComponent, HasComponents}; +use crate::core::traits::sync::Async; +use crate::std_prelude::*; +use crate::transaction::traits::types::HasTxTypes; + +pub struct TxEncoderComponent; + +#[async_trait] +pub trait TxEncoder: Async +where + TxContext: HasTxTypes, +{ + async fn encode_tx( + context: &TxContext, + signer: &TxContext::Signer, + nonce: &TxContext::Nonce, + fee: &TxContext::Fee, + messages: &[TxContext::Message], + ) -> Result; +} + +#[async_trait] +pub trait CanEncodeTx: HasTxTypes { + async fn encode_tx( + &self, + signer: &Self::Signer, + nonce: &Self::Nonce, + fee: &Self::Fee, + messages: &[Self::Message], + ) -> Result; +} + +#[async_trait] +impl TxEncoder for Component +where + TxContext: HasTxTypes, + Component: DelegateComponent, + Component::Delegate: TxEncoder, +{ + async fn encode_tx( + context: &TxContext, + signer: &TxContext::Signer, + nonce: &TxContext::Nonce, + fee: &TxContext::Fee, + messages: &[TxContext::Message], + ) -> Result { + Component::Delegate::encode_tx(context, signer, nonce, fee, messages).await + } +} + +#[async_trait] +impl CanEncodeTx for TxContext +where + TxContext: HasTxTypes + HasComponents, + TxContext::Components: TxEncoder, +{ + async fn encode_tx( + &self, + signer: &Self::Signer, + nonce: &Self::Nonce, + fee: &Self::Fee, + messages: &[Self::Message], + ) -> Result { + TxContext::Components::encode_tx(self, signer, nonce, fee, messages).await + } +} diff --git a/crates/relayer-components/src/transaction/traits/components/tx_fee_estimater.rs b/crates/relayer-components/src/transaction/traits/components/tx_fee_estimater.rs new file mode 100644 index 0000000000..9b0b908889 --- /dev/null +++ b/crates/relayer-components/src/transaction/traits/components/tx_fee_estimater.rs @@ -0,0 +1,49 @@ +use async_trait::async_trait; + +use crate::core::traits::component::{DelegateComponent, HasComponents}; +use crate::std_prelude::*; +use crate::transaction::traits::types::HasTxTypes; + +pub struct TxFeeEstimatorComponent; + +#[async_trait] +pub trait TxFeeEstimator +where + TxContext: HasTxTypes, +{ + async fn estimate_tx_fee( + context: &TxContext, + tx: &TxContext::Transaction, + ) -> Result; +} + +#[async_trait] +pub trait CanEstimateTxFee: HasTxTypes { + async fn estimate_tx_fee(&self, tx: &Self::Transaction) -> Result; +} + +#[async_trait] +impl TxFeeEstimator for Component +where + TxContext: HasTxTypes, + Component: DelegateComponent, + Component::Delegate: TxFeeEstimator, +{ + async fn estimate_tx_fee( + context: &TxContext, + tx: &TxContext::Transaction, + ) -> Result { + Component::Delegate::estimate_tx_fee(context, tx).await + } +} + +#[async_trait] +impl CanEstimateTxFee for TxContext +where + TxContext: HasTxTypes + HasComponents, + TxContext::Components: TxFeeEstimator, +{ + async fn estimate_tx_fee(&self, tx: &Self::Transaction) -> Result { + TxContext::Components::estimate_tx_fee(self, tx).await + } +} diff --git a/crates/relayer-components/src/transaction/traits/components/tx_response_poller.rs b/crates/relayer-components/src/transaction/traits/components/tx_response_poller.rs new file mode 100644 index 0000000000..77d146c3f8 --- /dev/null +++ b/crates/relayer-components/src/transaction/traits/components/tx_response_poller.rs @@ -0,0 +1,55 @@ +use async_trait::async_trait; + +use crate::core::traits::component::{DelegateComponent, HasComponents}; +use crate::std_prelude::*; +use crate::transaction::traits::types::HasTxTypes; + +pub struct TxResponsePollerComponent; + +#[async_trait] +pub trait TxResponsePoller +where + TxContext: HasTxTypes, +{ + async fn poll_tx_response( + context: &TxContext, + tx_hash: &TxContext::TxHash, + ) -> Result; +} + +#[async_trait] +impl TxResponsePoller for Component +where + TxContext: HasTxTypes, + Component: DelegateComponent, + Component::Delegate: TxResponsePoller, +{ + async fn poll_tx_response( + context: &TxContext, + tx_hash: &TxContext::TxHash, + ) -> Result { + Component::Delegate::poll_tx_response(context, tx_hash).await + } +} + +#[async_trait] +pub trait CanPollTxResponse: HasTxTypes { + async fn poll_tx_response( + &self, + tx_hash: &Self::TxHash, + ) -> Result; +} + +#[async_trait] +impl CanPollTxResponse for TxContext +where + TxContext: HasTxTypes + HasComponents, + TxContext::Components: TxResponsePoller, +{ + async fn poll_tx_response( + &self, + tx_hash: &Self::TxHash, + ) -> Result { + TxContext::Components::poll_tx_response(self, tx_hash).await + } +} diff --git a/crates/relayer-components/src/transaction/traits/components/tx_response_querier.rs b/crates/relayer-components/src/transaction/traits/components/tx_response_querier.rs new file mode 100644 index 0000000000..80727d4f19 --- /dev/null +++ b/crates/relayer-components/src/transaction/traits/components/tx_response_querier.rs @@ -0,0 +1,55 @@ +use async_trait::async_trait; + +use crate::core::traits::component::{DelegateComponent, HasComponents}; +use crate::std_prelude::*; +use crate::transaction::traits::types::HasTxTypes; + +pub struct TxResponseQuerierComponent; + +#[async_trait] +pub trait TxResponseQuerier +where + TxContext: HasTxTypes, +{ + async fn query_tx_response( + context: &TxContext, + tx_hash: &TxContext::TxHash, + ) -> Result, TxContext::Error>; +} + +#[async_trait] +pub trait CanQueryTxResponse: HasTxTypes { + async fn query_tx_response( + &self, + tx_hash: &Self::TxHash, + ) -> Result, Self::Error>; +} + +#[async_trait] +impl TxResponseQuerier for Component +where + TxContext: HasTxTypes, + Component: DelegateComponent, + Component::Delegate: TxResponseQuerier, +{ + async fn query_tx_response( + context: &TxContext, + tx_hash: &TxContext::TxHash, + ) -> Result, TxContext::Error> { + Component::Delegate::query_tx_response(context, tx_hash).await + } +} + +#[async_trait] +impl CanQueryTxResponse for TxContext +where + TxContext: HasTxTypes + HasComponents, + TxContext::Components: TxResponseQuerier, +{ + async fn query_tx_response( + &self, + tx_hash: &Self::TxHash, + ) -> Result, Self::Error> { + TxContext::Components::query_tx_response(self, tx_hash).await + } +} diff --git a/crates/relayer-components/src/transaction/traits/components/tx_submitter.rs b/crates/relayer-components/src/transaction/traits/components/tx_submitter.rs new file mode 100644 index 0000000000..c03a10f5b9 --- /dev/null +++ b/crates/relayer-components/src/transaction/traits/components/tx_submitter.rs @@ -0,0 +1,49 @@ +use async_trait::async_trait; + +use crate::core::traits::component::{DelegateComponent, HasComponents}; +use crate::std_prelude::*; +use crate::transaction::traits::types::HasTxTypes; + +pub struct TxSubmitterComponent; + +#[async_trait] +pub trait TxSubmitter +where + TxContext: HasTxTypes, +{ + async fn submit_tx( + context: &TxContext, + tx: &TxContext::Transaction, + ) -> Result; +} + +#[async_trait] +pub trait CanSubmitTx: HasTxTypes { + async fn submit_tx(&self, tx: &Self::Transaction) -> Result; +} + +#[async_trait] +impl TxSubmitter for Component +where + TxContext: HasTxTypes, + Component: DelegateComponent, + Component::Delegate: TxSubmitter, +{ + async fn submit_tx( + context: &TxContext, + tx: &TxContext::Transaction, + ) -> Result { + Component::Delegate::submit_tx(context, tx).await + } +} + +#[async_trait] +impl CanSubmitTx for TxContext +where + TxContext: HasTxTypes + HasComponents, + TxContext::Components: TxSubmitter, +{ + async fn submit_tx(&self, tx: &Self::Transaction) -> Result { + TxContext::Components::submit_tx(self, tx).await + } +} diff --git a/crates/relayer-components/src/transaction/traits/event.rs b/crates/relayer-components/src/transaction/traits/event.rs new file mode 100644 index 0000000000..57510f28fb --- /dev/null +++ b/crates/relayer-components/src/transaction/traits/event.rs @@ -0,0 +1,8 @@ +use crate::std_prelude::*; +use crate::transaction::traits::types::HasTxTypes; + +pub trait CanParseTxResponseAsEvents: HasTxTypes { + fn parse_tx_response_as_events( + response: Self::TxResponse, + ) -> Result>, Self::Error>; +} diff --git a/crates/relayer-components/src/transaction/traits/fee.rs b/crates/relayer-components/src/transaction/traits/fee.rs new file mode 100644 index 0000000000..b76447f238 --- /dev/null +++ b/crates/relayer-components/src/transaction/traits/fee.rs @@ -0,0 +1,5 @@ +use crate::transaction::traits::types::HasTxTypes; + +pub trait HasFeeForSimulation: HasTxTypes { + fn fee_for_simulation(&self) -> &Self::Fee; +} diff --git a/crates/relayer-components/src/transaction/traits/logs/logger.rs b/crates/relayer-components/src/transaction/traits/logs/logger.rs new file mode 100644 index 0000000000..0492fe75c4 --- /dev/null +++ b/crates/relayer-components/src/transaction/traits/logs/logger.rs @@ -0,0 +1,34 @@ +use crate::chain::traits::types::chain_id::HasChainId; +use crate::logger::traits::level::HasLoggerWithBaseLevels; +use crate::logger::traits::log::CanLog; +use crate::logger::traits::logger::BaseLogger; +use crate::logger::types::wrapper::LogWrapper; + +pub trait CanLogTx: HasLoggerWithBaseLevels { + fn log_tx<'a>( + &'a self, + level: ::LogLevel, + message: &str, + build_log: impl for<'r> FnOnce(LogWrapper<'a, 'r, Self::Logger>), + ); +} + +impl CanLogTx for TxContext +where + TxContext: CanLog + HasChainId, +{ + fn log_tx<'a>( + &'a self, + level: ::LogLevel, + message: &str, + build_log: impl for<'r> FnOnce(LogWrapper<'a, 'r, Self::Logger>), + ) { + self.log(level, message, |log| { + log.nested("tx_context", |log| { + log.display("chain_id", self.chain_id()); + }); + + build_log(log); + }) + } +} diff --git a/crates/relayer-components/src/transaction/traits/logs/mod.rs b/crates/relayer-components/src/transaction/traits/logs/mod.rs new file mode 100644 index 0000000000..79a19673d4 --- /dev/null +++ b/crates/relayer-components/src/transaction/traits/logs/mod.rs @@ -0,0 +1,2 @@ +pub mod logger; +pub mod nonce; diff --git a/crates/relayer-components/src/transaction/traits/logs/nonce.rs b/crates/relayer-components/src/transaction/traits/logs/nonce.rs new file mode 100644 index 0000000000..1397ef3cb4 --- /dev/null +++ b/crates/relayer-components/src/transaction/traits/logs/nonce.rs @@ -0,0 +1,7 @@ +use crate::logger::traits::has_logger::HasLoggerType; +use crate::logger::traits::logger::BaseLogger; +use crate::transaction::traits::types::HasTxTypes; + +pub trait CanLogNonce: HasTxTypes + HasLoggerType { + fn log_nonce<'a>(nonce: &'a Self::Nonce) -> ::LogValue<'a>; +} diff --git a/crates/relayer-components/src/transaction/traits/mod.rs b/crates/relayer-components/src/transaction/traits/mod.rs new file mode 100644 index 0000000000..79ee3201fa --- /dev/null +++ b/crates/relayer-components/src/transaction/traits/mod.rs @@ -0,0 +1,7 @@ +pub mod components; +pub mod event; +pub mod fee; +pub mod logs; +pub mod nonce; +pub mod signer; +pub mod types; diff --git a/crates/relayer-components/src/transaction/traits/nonce/error.rs b/crates/relayer-components/src/transaction/traits/nonce/error.rs new file mode 100644 index 0000000000..6064dd1a60 --- /dev/null +++ b/crates/relayer-components/src/transaction/traits/nonce/error.rs @@ -0,0 +1,16 @@ +use crate::core::traits::error::InjectError; +use crate::std_prelude::*; +use crate::transaction::traits::types::HasTxTypes; + +pub struct NonceMistmatchError { + pub expected_nonce: Nonce, + pub given_nonce: Nonce, +} + +pub trait HasNonceMismatchError: + HasTxTypes + InjectError> +{ + fn try_extract_nonce_mismatch_error( + e: &Self::Error, + ) -> Option>; +} diff --git a/crates/relayer-components/src/transaction/traits/nonce/guard.rs b/crates/relayer-components/src/transaction/traits/nonce/guard.rs new file mode 100644 index 0000000000..e82732b86f --- /dev/null +++ b/crates/relayer-components/src/transaction/traits/nonce/guard.rs @@ -0,0 +1,7 @@ +use crate::transaction::traits::types::HasNonceType; + +pub trait HasNonceGuard: HasNonceType { + type NonceGuard<'a>: Send + Sync; + + fn deref_nonce<'a, 'b>(guard: &'a Self::NonceGuard<'b>) -> &'a Self::Nonce; +} diff --git a/crates/relayer-components/src/transaction/traits/nonce/mod.rs b/crates/relayer-components/src/transaction/traits/nonce/mod.rs new file mode 100644 index 0000000000..b5e269887b --- /dev/null +++ b/crates/relayer-components/src/transaction/traits/nonce/mod.rs @@ -0,0 +1,3 @@ +pub mod error; +pub mod guard; +pub mod mutex; diff --git a/crates/relayer-components/src/transaction/traits/nonce/mutex.rs b/crates/relayer-components/src/transaction/traits/nonce/mutex.rs new file mode 100644 index 0000000000..2d69588c49 --- /dev/null +++ b/crates/relayer-components/src/transaction/traits/nonce/mutex.rs @@ -0,0 +1,28 @@ +use crate::runtime::traits::mutex::HasMutex; +use crate::runtime::traits::runtime::HasRuntime; +use crate::transaction::traits::nonce::guard::HasNonceGuard; +use crate::transaction::traits::types::HasSignerType; + +/** + A naive nonce allocator that simply query the current nonce from the context + and then pass it to the continuation. + + To ensure that the nonce works safely with parallel transaction submissions, + the allocator requires the context to provide a mutex, which is acquired across + the time when the nonce is being allocated and used. Because of this, the naive + allocator only allows one transaction to be submitted at a time. +*/ +pub trait HasMutexForNonceAllocation: HasRuntime + HasNonceGuard + HasSignerType +where + Self::Runtime: HasMutex, +{ + fn mutex_for_nonce_allocation( + &self, + signer: &Self::Signer, + ) -> &::Mutex<()>; + + fn mutex_to_nonce_guard<'a>( + mutex_guard: ::MutexGuard<'a, ()>, + nonce: Self::Nonce, + ) -> Self::NonceGuard<'a>; +} diff --git a/crates/relayer-components/src/transaction/traits/signer.rs b/crates/relayer-components/src/transaction/traits/signer.rs new file mode 100644 index 0000000000..4ba96ca193 --- /dev/null +++ b/crates/relayer-components/src/transaction/traits/signer.rs @@ -0,0 +1,25 @@ +use crate::transaction::traits::types::HasSignerType; + +/** + A simplified accessor for a transaction context to provide + one or more signers for signing transactions. + + The context may return different signers across different calls, + so that the transaction sender can use multiple signers to + submit parallel transactions. + + Note that this method does not support fair allocation of + multiple signers, as the context cannot know how long a + signer is going to be used. If we want to use a more + sophisticated strategy to multiple signers, we can define + more complex trait similar to + [`NonceAllocator`](crate::transaction::traits::nonce::NonceAllocator) + so that the usage of each signer is tracked across the implementation. + + On the other hand, this trait is suited for use in the minimal relayer, + where there is no need to implement the logic to support parallel + transactions or multiple signers. +*/ +pub trait HasSigner: HasSignerType { + fn get_signer(&self) -> &Self::Signer; +} diff --git a/crates/relayer-components/src/transaction/traits/types.rs b/crates/relayer-components/src/transaction/traits/types.rs new file mode 100644 index 0000000000..5241cc647d --- /dev/null +++ b/crates/relayer-components/src/transaction/traits/types.rs @@ -0,0 +1,26 @@ +use crate::chain::traits::types::event::HasEventType; +use crate::chain::traits::types::message::HasMessageType; +use crate::core::traits::error::HasErrorType; +use crate::core::traits::sync::Async; + +pub trait HasNonceType: Async { + type Nonce: Async; +} + +pub trait HasSignerType: Async { + type Signer: Async; +} + +pub trait HasTxTypes: + HasMessageType + HasEventType + HasNonceType + HasSignerType + HasErrorType +{ + type Transaction: Async; + + type Fee: Async; + + type TxHash: Async; + + type TxResponse: Async; + + fn tx_size(tx: &Self::Transaction) -> usize; +} diff --git a/crates/relayer-components/src/vendor/async_trait.rs b/crates/relayer-components/src/vendor/async_trait.rs new file mode 100644 index 0000000000..8f89eebc8e --- /dev/null +++ b/crates/relayer-components/src/vendor/async_trait.rs @@ -0,0 +1 @@ +pub use async_trait::async_trait; diff --git a/crates/relayer-components/src/vendor/mod.rs b/crates/relayer-components/src/vendor/mod.rs new file mode 100644 index 0000000000..cd31a2855c --- /dev/null +++ b/crates/relayer-components/src/vendor/mod.rs @@ -0,0 +1 @@ +pub mod async_trait; diff --git a/crates/relayer-cosmos/Cargo.toml b/crates/relayer-cosmos/Cargo.toml new file mode 100644 index 0000000000..45f6064665 --- /dev/null +++ b/crates/relayer-cosmos/Cargo.toml @@ -0,0 +1,52 @@ +[package] +name = "ibc-relayer-cosmos" +version = "0.25.0" +edition = "2021" +license = "Apache-2.0" +readme = "README.md" +keywords = ["blockchain", "consensus", "cosmos", "ibc", "tendermint"] +repository = "https://github.com/informalsystems/ibc-rs" +authors = ["Informal Systems "] +rust-version = "1.68" +description = """ + Implementation of an IBC Relayer in Rust, as a library +""" + +[package.metadata.docs.rs] +all-features = true + +[features] +default = ["flex-error/std", "flex-error/eyre_tracer"] + +[dependencies] +ibc-proto = { version = "0.34.0" } +ibc-relayer = { version = "0.25.0", path = "../relayer" } +ibc-relayer-types = { version = "0.25.0", path = "../relayer-types" } +ibc-telemetry = { version = "0.25.0", path = "../telemetry" } +ibc-relayer-runtime = { version = "0.1.0", path = "../relayer-runtime" } +ibc-relayer-all-in-one = { version = "0.1.0", path = "../relayer-all-in-one" } +ibc-relayer-components = { version = "0.1.0", path = "../relayer-components" } +ibc-relayer-components-extra = { version = "0.1.0", path = "../relayer-components-extra" } + +serde = "1.0" +serde_derive = "1.0" +async-trait = "0.1.56" +flex-error = { version = "0.4.4", default-features = false } +prost = { version = "0.11" } +eyre = "0.6.8" +tokio = "1.24" +itertools = "0.10.3" +futures = "0.3" +tracing = "0.1.36" +opentelemetry = { version = "0.17.0", features = ["metrics"] } +tonic = { version = "0.9", features = ["tls", "tls-roots"] } +moka = { version = "0.10", features = ["future"] } + +[dependencies.tendermint] +version = "=0.33.0" +features = ["secp256k1"] + +[dependencies.tendermint-rpc] +version = "=0.33.0" +features = ["http-client", "websocket-client"] + diff --git a/crates/relayer-cosmos/src/all_for_one/birelay.rs b/crates/relayer-cosmos/src/all_for_one/birelay.rs new file mode 100644 index 0000000000..7e8ff32a6f --- /dev/null +++ b/crates/relayer-cosmos/src/all_for_one/birelay.rs @@ -0,0 +1,29 @@ +use ibc_relayer_all_in_one::all_for_one::birelay::AfoBiRelay; +use ibc_relayer_components::relay::types::aliases::{DstChain, SrcChain}; + +use crate::all_for_one::relay::AfoCosmosRelay; + +pub trait AfoCosmosBiRelay: + AfoBiRelay +{ + type CosmosRelayAToB: AfoCosmosRelay; + + type CosmosRelayBToA: AfoCosmosRelay< + CosmosSrcChain = DstChain, + CosmosDstChain = SrcChain, + >; +} + +impl AfoCosmosBiRelay for BiRelay +where + BiRelay: AfoBiRelay, + RelayAToB: AfoCosmosRelay, + RelayBToA: AfoCosmosRelay< + CosmosSrcChain = RelayAToB::CosmosDstChain, + CosmosDstChain = RelayAToB::CosmosSrcChain, + >, +{ + type CosmosRelayAToB = RelayAToB; + + type CosmosRelayBToA = RelayBToA; +} diff --git a/crates/relayer-cosmos/src/all_for_one/builder.rs b/crates/relayer-cosmos/src/all_for_one/builder.rs new file mode 100644 index 0000000000..be342867b3 --- /dev/null +++ b/crates/relayer-cosmos/src/all_for_one/builder.rs @@ -0,0 +1,40 @@ +use async_trait::async_trait; +use ibc_relayer::chain::handle::BaseChainHandle; +use ibc_relayer_all_in_one::all_for_one::builder::CanBuildAfoBiRelay; +use ibc_relayer_all_in_one::one_for_all::types::birelay::OfaBiRelayWrapper; +use ibc_relayer_all_in_one::one_for_all::types::builder::OfaBuilderWrapper; +use ibc_relayer_components::core::traits::error::HasErrorType; +use ibc_relayer_types::core::ics24_host::identifier::{ChainId, ClientId}; + +use crate::all_for_one::birelay::AfoCosmosBiRelay; +use crate::contexts::birelay::CosmosBiRelay; +use crate::contexts::builder::CosmosBuilder; + +#[async_trait] +pub trait CanBuildAfoCosmosBaseBiRelay: HasErrorType { + type AfoCosmosBaseBiRelay: AfoCosmosBiRelay; + + async fn build_afo_cosmos_birelay( + &self, + chain_id_a: &ChainId, + chain_id_b: &ChainId, + client_id_a: &ClientId, + client_id_b: &ClientId, + ) -> Result; +} + +#[async_trait] +impl CanBuildAfoCosmosBaseBiRelay for OfaBuilderWrapper { + type AfoCosmosBaseBiRelay = OfaBiRelayWrapper>; + + async fn build_afo_cosmos_birelay( + &self, + chain_id_a: &ChainId, + chain_id_b: &ChainId, + client_id_a: &ClientId, + client_id_b: &ClientId, + ) -> Result { + self.build_afo_birelay(chain_id_a, chain_id_b, client_id_a, client_id_b) + .await + } +} diff --git a/crates/relayer-cosmos/src/all_for_one/chain.rs b/crates/relayer-cosmos/src/all_for_one/chain.rs new file mode 100644 index 0000000000..fd07c71b55 --- /dev/null +++ b/crates/relayer-cosmos/src/all_for_one/chain.rs @@ -0,0 +1,71 @@ +use alloc::sync::Arc; +use ibc_relayer::chain::endpoint::ChainStatus; +use ibc_relayer_all_in_one::all_for_one::chain::{AfoChain, AfoCounterpartyChain}; +use ibc_relayer_runtime::types::runtime::TokioRuntimeContext; +use ibc_relayer_types::clients::ics07_tendermint::consensus_state::ConsensusState; +use ibc_relayer_types::core::ics04_channel::events::WriteAcknowledgement; +use ibc_relayer_types::core::ics04_channel::packet::Packet; +use ibc_relayer_types::core::ics04_channel::packet::Sequence; +use ibc_relayer_types::core::ics24_host::identifier::{ChannelId, ClientId, ConnectionId, PortId}; +use ibc_relayer_types::timestamp::Timestamp; +use ibc_relayer_types::Height; +use tendermint::abci::Event as AbciEvent; + +use crate::traits::message::CosmosMessage; +use crate::types::channel::CosmosInitChannelOptions; +use crate::types::connection::CosmosInitConnectionOptions; +use crate::types::error::Error; + +pub trait AfoCosmosChain: + AfoChain< + Counterparty, + AfoRuntime = TokioRuntimeContext, + Error = Error, + Height = Height, + Timestamp = Timestamp, + Message = Arc, + Event = Arc, + ClientId = ClientId, + ConnectionId = ConnectionId, + ChannelId = ChannelId, + PortId = PortId, + Sequence = Sequence, + WriteAcknowledgementEvent = WriteAcknowledgement, + ConsensusState = ConsensusState, + ChainStatus = ChainStatus, + IncomingPacket = Packet, + OutgoingPacket = Packet, + InitConnectionOptions = CosmosInitConnectionOptions, + InitChannelOptions = CosmosInitChannelOptions, +> +where + Counterparty: AfoCounterpartyChain, +{ +} + +impl AfoCosmosChain for Chain +where + Chain: AfoChain< + Counterparty, + AfoRuntime = TokioRuntimeContext, + Error = Error, + Height = Height, + Timestamp = Timestamp, + Message = Arc, + Event = Arc, + ClientId = ClientId, + ConnectionId = ConnectionId, + ChannelId = ChannelId, + PortId = PortId, + Sequence = Sequence, + WriteAcknowledgementEvent = WriteAcknowledgement, + ConsensusState = ConsensusState, + ChainStatus = ChainStatus, + IncomingPacket = Packet, + OutgoingPacket = Packet, + InitConnectionOptions = CosmosInitConnectionOptions, + InitChannelOptions = CosmosInitChannelOptions, + >, + Counterparty: AfoCounterpartyChain, +{ +} diff --git a/crates/relayer-cosmos/src/all_for_one/mod.rs b/crates/relayer-cosmos/src/all_for_one/mod.rs new file mode 100644 index 0000000000..acb07e66e7 --- /dev/null +++ b/crates/relayer-cosmos/src/all_for_one/mod.rs @@ -0,0 +1,9 @@ +//! The `all_for_one` trait exposes the concrete APIs that users use to +//! construct concrete relayer implementations. In other words, the public +//! API methods that are part of this trait are used to facilitate the +//! functionality of relayers. + +pub mod birelay; +pub mod builder; +pub mod chain; +pub mod relay; diff --git a/crates/relayer-cosmos/src/all_for_one/relay.rs b/crates/relayer-cosmos/src/all_for_one/relay.rs new file mode 100644 index 0000000000..1dad8b7bce --- /dev/null +++ b/crates/relayer-cosmos/src/all_for_one/relay.rs @@ -0,0 +1,33 @@ +use ibc_relayer_all_in_one::all_for_one::relay::AfoRelay; +use ibc_relayer_runtime::types::runtime::TokioRuntimeContext; +use ibc_relayer_types::core::ics04_channel::packet::Packet; + +use crate::all_for_one::chain::AfoCosmosChain; + +pub trait AfoCosmosRelay: + AfoRelay< + AfoSrcChain = Self::CosmosSrcChain, + AfoDstChain = Self::CosmosDstChain, + Packet = Packet, + AfoRuntime = TokioRuntimeContext, +> +{ + type CosmosSrcChain: AfoCosmosChain; + + type CosmosDstChain: AfoCosmosChain; +} + +impl AfoCosmosRelay for Relay +where + Relay: AfoRelay< + AfoSrcChain = SrcChain, + AfoDstChain = DstChain, + Packet = Packet, + AfoRuntime = TokioRuntimeContext, + >, + SrcChain: AfoCosmosChain, + DstChain: AfoCosmosChain, +{ + type CosmosSrcChain = SrcChain; + type CosmosDstChain = DstChain; +} diff --git a/crates/relayer-cosmos/src/contexts/birelay.rs b/crates/relayer-cosmos/src/contexts/birelay.rs new file mode 100644 index 0000000000..b176f5d42e --- /dev/null +++ b/crates/relayer-cosmos/src/contexts/birelay.rs @@ -0,0 +1,33 @@ +use ibc_relayer::chain::handle::ChainHandle; +use ibc_relayer_all_in_one::one_for_all::types::relay::OfaRelayWrapper; +use ibc_relayer_runtime::types::runtime::TokioRuntimeContext; + +use crate::contexts::relay::CosmosRelay; + +pub struct CosmosBiRelay +where + ChainA: ChainHandle, + ChainB: ChainHandle, +{ + pub runtime: TokioRuntimeContext, + pub relay_a_to_b: OfaRelayWrapper>, + pub relay_b_to_a: OfaRelayWrapper>, +} + +impl CosmosBiRelay +where + ChainA: ChainHandle, + ChainB: ChainHandle, +{ + pub fn new( + runtime: TokioRuntimeContext, + relay_a_to_b: OfaRelayWrapper>, + relay_b_to_a: OfaRelayWrapper>, + ) -> Self { + Self { + runtime, + relay_a_to_b, + relay_b_to_a, + } + } +} diff --git a/crates/relayer-cosmos/src/contexts/builder.rs b/crates/relayer-cosmos/src/contexts/builder.rs new file mode 100644 index 0000000000..9b4122f63e --- /dev/null +++ b/crates/relayer-cosmos/src/contexts/builder.rs @@ -0,0 +1,173 @@ +use alloc::sync::Arc; +use std::collections::HashMap; +use tendermint_rpc::client::CompatMode; +use tendermint_rpc::Client; +use tendermint_rpc::HttpClient; +use tokio::task; + +use eyre::eyre; +use ibc_relayer::chain::cosmos::types::config::TxConfig; +use ibc_relayer::chain::handle::BaseChainHandle; +use ibc_relayer::chain::handle::ChainHandle; +use ibc_relayer::config::filter::PacketFilter; +use ibc_relayer::config::Config; +use ibc_relayer::keyring::AnySigningKeyPair; +use ibc_relayer::keyring::Secp256k1KeyPair; +use ibc_relayer::spawn::spawn_chain_runtime; +use ibc_relayer_all_in_one::one_for_all::types::builder::OfaBuilderWrapper; +use ibc_relayer_all_in_one::one_for_all::types::chain::OfaChainWrapper; +use ibc_relayer_components_extra::batch::types::config::BatchConfig; +use ibc_relayer_runtime::types::runtime::TokioRuntimeContext; +use ibc_relayer_types::core::ics24_host::identifier::ChainId; +use ibc_relayer_types::core::ics24_host::identifier::ClientId; +use tokio::runtime::Runtime as TokioRuntime; + +use crate::contexts::chain::CosmosChain; +use crate::contexts::relay::CosmosRelay; +use crate::types::batch::CosmosBatchSender; +use crate::types::error::{BaseError, Error}; +use crate::types::telemetry::CosmosTelemetry; + +pub struct CosmosBuilder { + pub config: Config, + pub packet_filter: PacketFilter, + pub telemetry: CosmosTelemetry, + pub runtime: TokioRuntimeContext, + pub batch_config: BatchConfig, + pub key_map: HashMap, +} + +impl CosmosBuilder { + pub fn new( + config: Config, + runtime: Arc, + telemetry: CosmosTelemetry, + packet_filter: PacketFilter, + batch_config: BatchConfig, + key_map: HashMap, + ) -> Self { + let runtime = TokioRuntimeContext::new(runtime); + + Self { + config, + packet_filter, + telemetry, + runtime, + batch_config, + key_map, + } + } + + pub fn new_wrapped( + config: Config, + runtime: Arc, + telemetry: CosmosTelemetry, + packet_filter: PacketFilter, + batch_config: BatchConfig, + key_map: HashMap, + ) -> OfaBuilderWrapper { + OfaBuilderWrapper::new_with_homogenous_cache(Self::new( + config, + runtime, + telemetry, + packet_filter, + batch_config, + key_map, + )) + } + + pub async fn build_chain( + &self, + chain_id: &ChainId, + ) -> Result, Error> { + let runtime = self.runtime.runtime.clone(); + + let (handle, key, chain_config) = task::block_in_place(|| -> Result<_, Error> { + let handle = spawn_chain_runtime::(&self.config, chain_id, runtime) + .map_err(BaseError::spawn)?; + + let key = get_keypair(chain_id, &handle, &self.key_map)?; + + let chain_config = handle.config().map_err(BaseError::relayer)?; + + Ok((handle, key, chain_config)) + })?; + + let event_source_mode = chain_config.event_source.clone(); + + let tx_config = TxConfig::try_from(&chain_config).map_err(BaseError::relayer)?; + + let mut rpc_client = + HttpClient::new(tx_config.rpc_address.clone()).map_err(BaseError::tendermint_rpc)?; + + let status = rpc_client.status().await.unwrap(); + let compat_mode = CompatMode::from_version(status.node_info.version).unwrap(); + + rpc_client.set_compat_mode(compat_mode); + + let context = CosmosChain::new( + handle, + tx_config, + rpc_client, + compat_mode, + key, + event_source_mode, + self.runtime.clone(), + self.telemetry.clone(), + ); + + Ok(context) + } + + pub fn build_relay( + &self, + src_client_id: &ClientId, + dst_client_id: &ClientId, + src_chain: OfaChainWrapper>, + dst_chain: OfaChainWrapper>, + src_batch_sender: CosmosBatchSender, + dst_batch_sender: CosmosBatchSender, + ) -> Result, Error> { + let relay = CosmosRelay::new( + self.runtime.clone(), + src_chain, + dst_chain, + src_client_id.clone(), + dst_client_id.clone(), + self.packet_filter.clone(), + src_batch_sender, + dst_batch_sender, + ); + + Ok(relay) + } +} + +pub fn get_keypair( + chain_id: &ChainId, + handle: &BaseChainHandle, + key_map: &HashMap, +) -> Result { + if let Some(key) = key_map.get(chain_id) { + let chain_config = handle.config().map_err(BaseError::relayer)?; + + // try add the key to the chain handle, in case if it is only in the key map, + // as for the case of integration tests. + let _ = handle.add_key( + chain_config.key_name, + AnySigningKeyPair::Secp256k1(key.clone()), + ); + + return Ok(key.clone()); + } + + let keypair = handle.get_key().map_err(BaseError::relayer)?; + + let AnySigningKeyPair::Secp256k1(key) = keypair else { + return Err( + BaseError::generic(eyre!("no Secp256k1 key pair for chain {}", chain_id)).into(), + ); + }; + + Ok(key) +} diff --git a/crates/relayer-cosmos/src/contexts/chain.rs b/crates/relayer-cosmos/src/contexts/chain.rs new file mode 100644 index 0000000000..b7528afcd2 --- /dev/null +++ b/crates/relayer-cosmos/src/contexts/chain.rs @@ -0,0 +1,78 @@ +use alloc::sync::Arc; +use ibc_relayer::chain::cosmos::types::config::TxConfig; +use ibc_relayer::chain::handle::ChainHandle; +use ibc_relayer::config::EventSourceMode; +use ibc_relayer::event::source::queries::all as all_queries; +use ibc_relayer::keyring::Secp256k1KeyPair; +use ibc_relayer_components::runtime::impls::subscription::empty::EmptySubscription; +use ibc_relayer_components::runtime::traits::subscription::Subscription; +use ibc_relayer_runtime::types::runtime::TokioRuntimeContext; +use ibc_relayer_types::core::ics02_client::height::Height; +use ibc_relayer_types::core::ics24_host::identifier::ChainId; +use tendermint::abci::Event as AbciEvent; +use tendermint_rpc::client::CompatMode; +use tendermint_rpc::HttpClient; + +use crate::contexts::transaction::CosmosTxContext; +use crate::impls::subscription::CanCreateAbciEventSubscription; +use crate::types::telemetry::CosmosTelemetry; + +#[derive(Clone)] +pub struct CosmosChain { + pub handle: Handle, + pub chain_id: ChainId, + pub compat_mode: CompatMode, + pub runtime: TokioRuntimeContext, + pub telemetry: CosmosTelemetry, + pub subscription: Arc)>>, + pub tx_context: Arc, +} + +impl CosmosChain { + pub fn new( + handle: Handle, + tx_config: TxConfig, + rpc_client: HttpClient, + compat_mode: CompatMode, + key_entry: Secp256k1KeyPair, + event_source_mode: EventSourceMode, + runtime: TokioRuntimeContext, + telemetry: CosmosTelemetry, + ) -> Self { + let chain_version = tx_config.chain_id.version(); + + let subscription = match event_source_mode { + EventSourceMode::Push { + url, + batch_delay: _, + } => { + runtime.new_abci_event_subscription(chain_version, url, compat_mode, all_queries()) + } + EventSourceMode::Pull { interval: _ } => { + // TODO: implement pull-based event source + Arc::new(EmptySubscription::new()) + } + }; + + let chain_id = tx_config.chain_id.clone(); + + let tx_context = Arc::new(CosmosTxContext::new( + tx_config, + rpc_client, + key_entry, + runtime.clone(), + )); + + let chain = Self { + handle, + chain_id, + compat_mode, + runtime, + telemetry, + subscription, + tx_context, + }; + + chain + } +} diff --git a/crates/relayer-cosmos/src/contexts/mod.rs b/crates/relayer-cosmos/src/contexts/mod.rs new file mode 100644 index 0000000000..3427ba7f12 --- /dev/null +++ b/crates/relayer-cosmos/src/contexts/mod.rs @@ -0,0 +1,5 @@ +pub mod birelay; +pub mod builder; +pub mod chain; +pub mod relay; +pub mod transaction; diff --git a/crates/relayer-cosmos/src/contexts/relay.rs b/crates/relayer-cosmos/src/contexts/relay.rs new file mode 100644 index 0000000000..06667c191d --- /dev/null +++ b/crates/relayer-cosmos/src/contexts/relay.rs @@ -0,0 +1,60 @@ +use std::collections::HashSet; + +use alloc::sync::Arc; +use futures::lock::Mutex; +use ibc_relayer::chain::handle::ChainHandle; +use ibc_relayer::config::filter::PacketFilter; +use ibc_relayer_all_in_one::one_for_all::types::chain::OfaChainWrapper; +use ibc_relayer_runtime::types::runtime::TokioRuntimeContext; +use ibc_relayer_types::core::ics04_channel::packet::Sequence; +use ibc_relayer_types::core::ics24_host::identifier::{ChannelId, ClientId, PortId}; + +use crate::contexts::chain::CosmosChain; +use crate::types::batch::CosmosBatchSender; + +pub struct CosmosRelay +where + SrcChain: ChainHandle, + DstChain: ChainHandle, +{ + pub runtime: TokioRuntimeContext, + pub src_chain: OfaChainWrapper>, + pub dst_chain: OfaChainWrapper>, + pub src_client_id: ClientId, + pub dst_client_id: ClientId, + pub packet_filter: PacketFilter, + pub packet_lock_mutex: Arc>>, + pub src_chain_message_batch_sender: CosmosBatchSender, + pub dst_chain_message_batch_sender: CosmosBatchSender, +} + +impl CosmosRelay +where + SrcChain: ChainHandle, + DstChain: ChainHandle, +{ + pub fn new( + runtime: TokioRuntimeContext, + src_chain: OfaChainWrapper>, + dst_chain: OfaChainWrapper>, + src_client_id: ClientId, + dst_client_id: ClientId, + packet_filter: PacketFilter, + src_chain_message_batch_sender: CosmosBatchSender, + dst_chain_message_batch_sender: CosmosBatchSender, + ) -> Self { + let relay = Self { + runtime, + src_chain, + dst_chain, + src_client_id, + dst_client_id, + packet_filter, + src_chain_message_batch_sender, + dst_chain_message_batch_sender, + packet_lock_mutex: Arc::new(Mutex::new(HashSet::new())), + }; + + relay + } +} diff --git a/crates/relayer-cosmos/src/contexts/transaction.rs b/crates/relayer-cosmos/src/contexts/transaction.rs new file mode 100644 index 0000000000..caa8b4f0c5 --- /dev/null +++ b/crates/relayer-cosmos/src/contexts/transaction.rs @@ -0,0 +1,30 @@ +use futures::lock::Mutex; +use ibc_relayer::chain::cosmos::types::config::TxConfig; +use ibc_relayer::keyring::Secp256k1KeyPair; +use ibc_relayer_runtime::types::runtime::TokioRuntimeContext; +use tendermint_rpc::HttpClient; + +pub struct CosmosTxContext { + pub tx_config: TxConfig, + pub rpc_client: HttpClient, + pub key_entry: Secp256k1KeyPair, + pub nonce_mutex: Mutex<()>, + pub runtime: TokioRuntimeContext, +} + +impl CosmosTxContext { + pub fn new( + tx_config: TxConfig, + rpc_client: HttpClient, + key_entry: Secp256k1KeyPair, + runtime: TokioRuntimeContext, + ) -> Self { + Self { + tx_config, + rpc_client, + key_entry, + nonce_mutex: Mutex::new(()), + runtime, + } + } +} diff --git a/crates/relayer-cosmos/src/impls/birelay.rs b/crates/relayer-cosmos/src/impls/birelay.rs new file mode 100644 index 0000000000..2013f6dd52 --- /dev/null +++ b/crates/relayer-cosmos/src/impls/birelay.rs @@ -0,0 +1,50 @@ +use ibc_relayer::chain::handle::ChainHandle; +use ibc_relayer_all_in_one::one_for_all::traits::birelay::OfaBiRelay; +use ibc_relayer_all_in_one::one_for_all::types::relay::OfaRelayWrapper; +use ibc_relayer_runtime::types::error::Error as TokioError; +use ibc_relayer_runtime::types::log::logger::TracingLogger; +use ibc_relayer_runtime::types::runtime::TokioRuntimeContext; + +use crate::contexts::birelay::CosmosBiRelay; +use crate::contexts::relay::CosmosRelay; +use crate::types::error::{BaseError, Error}; + +impl OfaBiRelay for CosmosBiRelay +where + ChainA: ChainHandle, + ChainB: ChainHandle, +{ + type Error = Error; + + type Runtime = TokioRuntimeContext; + + type Logger = TracingLogger; + + type RelayAToB = CosmosRelay; + + type RelayBToA = CosmosRelay; + + fn runtime(&self) -> &Self::Runtime { + &self.runtime + } + + fn runtime_error(e: TokioError) -> Error { + BaseError::tokio(e).into() + } + + fn logger(&self) -> &TracingLogger { + &TracingLogger + } + + fn relay_a_to_b(&self) -> &OfaRelayWrapper { + &self.relay_a_to_b + } + + fn relay_b_to_a(&self) -> &OfaRelayWrapper { + &self.relay_b_to_a + } + + fn relay_error(e: Error) -> Error { + e + } +} diff --git a/crates/relayer-cosmos/src/impls/builder.rs b/crates/relayer-cosmos/src/impls/builder.rs new file mode 100644 index 0000000000..6e8b952a94 --- /dev/null +++ b/crates/relayer-cosmos/src/impls/builder.rs @@ -0,0 +1,118 @@ +use async_trait::async_trait; +use ibc_relayer::chain::handle::BaseChainHandle; +use ibc_relayer_all_in_one::one_for_all::traits::builder::OfaBuilder; +use ibc_relayer_all_in_one::one_for_all::types::chain::OfaChainWrapper; +use ibc_relayer_all_in_one::one_for_all::types::relay::OfaRelayWrapper; +use ibc_relayer_components_extra::batch::types::config::BatchConfig; +use ibc_relayer_runtime::types::error::Error as TokioRuntimeError; +use ibc_relayer_runtime::types::log::logger::TracingLogger; +use ibc_relayer_runtime::types::runtime::TokioRuntimeContext; +use ibc_relayer_types::core::ics24_host::identifier::{ChainId, ClientId}; + +use crate::contexts::birelay::CosmosBiRelay; +use crate::contexts::builder::CosmosBuilder; +use crate::contexts::chain::CosmosChain; +use crate::contexts::relay::CosmosRelay; +use crate::types::batch::CosmosBatchSender; +use crate::types::error::{BaseError, Error}; + +#[async_trait] +impl OfaBuilder for CosmosBuilder { + type Error = Error; + + type Runtime = TokioRuntimeContext; + + type Logger = TracingLogger; + + type BiRelay = CosmosBiRelay; + + fn runtime(&self) -> &TokioRuntimeContext { + &self.runtime + } + + fn runtime_error(e: TokioRuntimeError) -> Error { + BaseError::tokio(e).into() + } + + fn birelay_error(e: Error) -> Error { + e + } + + fn logger(&self) -> &TracingLogger { + &TracingLogger + } + + async fn build_chain_a( + &self, + chain_id: &ChainId, + ) -> Result, Error> { + let chain = self.build_chain(chain_id).await?; + + Ok(chain) + } + + async fn build_chain_b( + &self, + chain_id: &ChainId, + ) -> Result, Error> { + let chain = self.build_chain(chain_id).await?; + + Ok(chain) + } + + async fn build_relay_a_to_b( + &self, + src_client_id: &ClientId, + dst_client_id: &ClientId, + src_chain: OfaChainWrapper>, + dst_chain: OfaChainWrapper>, + src_batch_sender: CosmosBatchSender, + dst_batch_sender: CosmosBatchSender, + ) -> Result, Error> { + let relay = self.build_relay( + src_client_id, + dst_client_id, + src_chain, + dst_chain, + src_batch_sender, + dst_batch_sender, + )?; + + Ok(relay) + } + + async fn build_relay_b_to_a( + &self, + src_client_id: &ClientId, + dst_client_id: &ClientId, + src_chain: OfaChainWrapper>, + dst_chain: OfaChainWrapper>, + src_batch_sender: CosmosBatchSender, + dst_batch_sender: CosmosBatchSender, + ) -> Result, Error> { + let relay = self.build_relay( + src_client_id, + dst_client_id, + src_chain, + dst_chain, + src_batch_sender, + dst_batch_sender, + )?; + + Ok(relay) + } + + async fn build_birelay( + &self, + relay_a_to_b: OfaRelayWrapper>, + relay_b_to_a: OfaRelayWrapper>, + ) -> Result, Error> { + let birelay = CosmosBiRelay::new(self.runtime.clone(), relay_a_to_b, relay_b_to_a); + + Ok(birelay) + } + + fn batch_config(&self) -> &BatchConfig { + &self.batch_config + } +} diff --git a/crates/relayer-cosmos/src/impls/chain.rs b/crates/relayer-cosmos/src/impls/chain.rs new file mode 100644 index 0000000000..b2c450e73f --- /dev/null +++ b/crates/relayer-cosmos/src/impls/chain.rs @@ -0,0 +1,807 @@ +use alloc::sync::Arc; +use async_trait::async_trait; +use ibc_relayer::chain::client::ClientSettings; +use ibc_relayer::chain::endpoint::ChainStatus; +use ibc_relayer::chain::handle::ChainHandle; +use ibc_relayer::event::{ + channel_open_init_try_from_abci_event, channel_open_try_try_from_abci_event, + connection_open_ack_try_from_abci_event, connection_open_try_try_from_abci_event, +}; +use ibc_relayer_all_in_one::one_for_all::traits::chain::{OfaChain, OfaChainTypes, OfaIbcChain}; +use ibc_relayer_components::chain::traits::components::message_sender::CanSendMessages; +use ibc_relayer_components::runtime::traits::subscription::Subscription; +use ibc_relayer_runtime::types::error::Error as TokioError; +use ibc_relayer_runtime::types::log::logger::TracingLogger; +use ibc_relayer_runtime::types::log::value::LogValue; +use ibc_relayer_runtime::types::runtime::TokioRuntimeContext; +use ibc_relayer_types::core::ics02_client::events::CLIENT_ID_ATTRIBUTE_KEY; +use ibc_relayer_types::core::ics04_channel::events::{SendPacket, WriteAcknowledgement}; +use ibc_relayer_types::core::ics04_channel::packet::Packet; +use ibc_relayer_types::core::ics04_channel::packet::Sequence; +use ibc_relayer_types::core::ics04_channel::timeout::TimeoutHeight; +use ibc_relayer_types::core::ics24_host::identifier::{ + ChainId, ChannelId, ClientId, ConnectionId, PortId, +}; +use ibc_relayer_types::events::IbcEventType; +use ibc_relayer_types::signer::Signer; +use ibc_relayer_types::timestamp::Timestamp; +use ibc_relayer_types::Height; +use prost::Message as _; +use tendermint::abci::Event as AbciEvent; + +use crate::contexts::chain::CosmosChain; +use crate::methods::chain::query_chain_status; +use crate::methods::channel::{ + build_channel_open_ack_message, build_channel_open_ack_payload, + build_channel_open_confirm_message, build_channel_open_confirm_payload, + build_channel_open_init_message, build_channel_open_try_message, + build_channel_open_try_payload, query_chain_id_from_channel_id, +}; +use crate::methods::client_state::query_client_state; +use crate::methods::connection::{ + build_connection_open_ack_message, build_connection_open_ack_payload, + build_connection_open_confirm_message, build_connection_open_confirm_payload, + build_connection_open_init_message, build_connection_open_init_payload, + build_connection_open_try_message, build_connection_open_try_payload, +}; +use crate::methods::consensus_state::{find_consensus_state_height_before, query_consensus_state}; +use crate::methods::create_client::{build_create_client_message, build_create_client_payload}; +use crate::methods::event::{ + try_extract_send_packet_event, try_extract_write_acknowledgement_event, +}; +use crate::methods::packet::{ + build_ack_packet_message, build_ack_packet_payload, build_receive_packet_message, + build_receive_packet_payload, build_timeout_unordered_packet_message, + build_timeout_unordered_packet_payload, query_is_packet_received, + query_write_acknowledgement_event, +}; +use crate::methods::unreceived_packet::{ + query_packet_commitments, query_send_packets_from_sequences, query_unreceived_packet_sequences, +}; +use crate::methods::update_client::{build_update_client_message, build_update_client_payload}; +use crate::traits::message::CosmosMessage; +use crate::types::channel::CosmosInitChannelOptions; +use crate::types::connection::CosmosInitConnectionOptions; +use crate::types::error::{BaseError, Error}; +use crate::types::events::channel::{CosmosChannelOpenInitEvent, CosmosChannelOpenTryEvent}; +use crate::types::events::client::CosmosCreateClientEvent; +use crate::types::events::connection::{ + CosmosConnectionOpenInitEvent, CosmosConnectionOpenTryEvent, +}; +use crate::types::payloads::channel::{ + CosmosChannelOpenAckPayload, CosmosChannelOpenConfirmPayload, CosmosChannelOpenTryPayload, +}; +use crate::types::payloads::client::{CosmosCreateClientPayload, CosmosUpdateClientPayload}; +use crate::types::payloads::connection::{ + CosmosConnectionOpenAckPayload, CosmosConnectionOpenConfirmPayload, + CosmosConnectionOpenInitPayload, CosmosConnectionOpenTryPayload, +}; +use crate::types::payloads::packet::{ + CosmosAckPacketPayload, CosmosReceivePacketPayload, CosmosTimeoutUnorderedPacketPayload, +}; +use crate::types::telemetry::CosmosTelemetry; +use crate::types::tendermint::{TendermintClientState, TendermintConsensusState}; + +#[async_trait] +impl OfaChainTypes for CosmosChain +where + Chain: ChainHandle, +{ + type Error = Error; + + type Runtime = TokioRuntimeContext; + + type Logger = TracingLogger; + + type Telemetry = CosmosTelemetry; + + type Message = Arc; + + type Event = Arc; + + type ClientState = TendermintClientState; + + type ConsensusState = TendermintConsensusState; + + type Height = Height; + + type Timestamp = Timestamp; + + type ChainId = ChainId; + + type ClientId = ClientId; + + type ConnectionId = ConnectionId; + + type ChannelId = ChannelId; + + type PortId = PortId; + + type Sequence = Sequence; + + type ChainStatus = ChainStatus; + + type IncomingPacket = Packet; + + type OutgoingPacket = Packet; + + type CreateClientPayloadOptions = ClientSettings; + + type InitConnectionOptions = CosmosInitConnectionOptions; + + type InitChannelOptions = CosmosInitChannelOptions; + + type CreateClientPayload = CosmosCreateClientPayload; + + type CreateClientEvent = CosmosCreateClientEvent; + + type UpdateClientPayload = CosmosUpdateClientPayload; + + type ConnectionOpenInitPayload = CosmosConnectionOpenInitPayload; + + type ConnectionOpenTryPayload = CosmosConnectionOpenTryPayload; + + type ConnectionOpenAckPayload = CosmosConnectionOpenAckPayload; + + type ConnectionOpenConfirmPayload = CosmosConnectionOpenConfirmPayload; + + type ChannelOpenTryPayload = CosmosChannelOpenTryPayload; + + type ChannelOpenAckPayload = CosmosChannelOpenAckPayload; + + type ChannelOpenConfirmPayload = CosmosChannelOpenConfirmPayload; + + type ReceivePacketPayload = CosmosReceivePacketPayload; + + type AckPacketPayload = CosmosAckPacketPayload; + + type TimeoutUnorderedPacketPayload = CosmosTimeoutUnorderedPacketPayload; + + type SendPacketEvent = SendPacket; + + type WriteAcknowledgementEvent = WriteAcknowledgement; + + type ConnectionOpenInitEvent = CosmosConnectionOpenInitEvent; + + type ConnectionOpenTryEvent = CosmosConnectionOpenTryEvent; + + type ChannelOpenInitEvent = CosmosChannelOpenInitEvent; + + type ChannelOpenTryEvent = CosmosChannelOpenTryEvent; +} + +#[async_trait] +impl OfaChain for CosmosChain +where + Chain: ChainHandle, +{ + fn runtime(&self) -> &TokioRuntimeContext { + &self.runtime + } + + fn runtime_error(e: TokioError) -> Error { + BaseError::tokio(e).into() + } + + fn logger(&self) -> &TracingLogger { + &TracingLogger + } + + fn log_event(event: &Arc) -> LogValue<'_> { + LogValue::Debug(event) + } + + fn log_incoming_packet(packet: &Packet) -> LogValue<'_> { + LogValue::Display(packet) + } + + fn log_outgoing_packet(packet: &Packet) -> LogValue<'_> { + LogValue::Display(packet) + } + + fn telemetry(&self) -> &CosmosTelemetry { + &self.telemetry + } + + fn increment_height(height: &Height) -> Result { + Ok(height.increment()) + } + + fn estimate_message_size(message: &Arc) -> Result { + let raw = message + .encode_protobuf(&Signer::dummy()) + .map_err(BaseError::encode)?; + + Ok(raw.encoded_len()) + } + + fn chain_status_height(status: &ChainStatus) -> &Height { + &status.height + } + + fn chain_status_timestamp(status: &ChainStatus) -> &Timestamp { + &status.timestamp + } + + fn try_extract_write_acknowledgement_event( + event: &Arc, + ) -> Option { + try_extract_write_acknowledgement_event(event) + } + + fn try_extract_send_packet_event(event: &Arc) -> Option { + try_extract_send_packet_event(event) + } + + fn extract_packet_from_send_packet_event(event: &SendPacket) -> Packet { + event.packet.clone() + } + + fn extract_packet_from_write_acknowledgement_event(ack: &WriteAcknowledgement) -> &Packet { + &ack.packet + } + + fn try_extract_create_client_event(event: Arc) -> Option { + let event_type = event.kind.parse().ok()?; + + if let IbcEventType::CreateClient = event_type { + for tag in &event.attributes { + let key = tag.key.as_str(); + let value = tag.value.as_str(); + if key == CLIENT_ID_ATTRIBUTE_KEY { + let client_id = value.parse().ok()?; + + return Some(CosmosCreateClientEvent { client_id }); + } + } + + None + } else { + None + } + } + + fn create_client_event_client_id(event: &CosmosCreateClientEvent) -> &ClientId { + &event.client_id + } + + fn client_state_latest_height(client_state: &TendermintClientState) -> &Height { + &client_state.latest_height + } + + fn try_extract_connection_open_init_event( + event: Arc, + ) -> Option { + let event_type = event.kind.parse().ok()?; + + if let IbcEventType::OpenInitConnection = event_type { + let open_ack_event = connection_open_ack_try_from_abci_event(&event).ok()?; + + let connection_id = open_ack_event.connection_id()?.clone(); + + Some(CosmosConnectionOpenInitEvent { connection_id }) + } else { + None + } + } + + fn connection_open_init_event_connection_id( + event: &CosmosConnectionOpenInitEvent, + ) -> &ConnectionId { + &event.connection_id + } + + fn try_extract_connection_open_try_event( + event: Arc, + ) -> Option { + let event_type = event.kind.parse().ok()?; + + if let IbcEventType::OpenTryConnection = event_type { + let open_try_event = connection_open_try_try_from_abci_event(&event).ok()?; + + let connection_id = open_try_event.connection_id()?.clone(); + + Some(CosmosConnectionOpenTryEvent { connection_id }) + } else { + None + } + } + + fn connection_open_try_event_connection_id( + event: &CosmosConnectionOpenTryEvent, + ) -> &ConnectionId { + &event.connection_id + } + + fn try_extract_channel_open_init_event( + event: Arc, + ) -> Option { + let event_type = event.kind.parse().ok()?; + + if let IbcEventType::OpenInitChannel = event_type { + let open_init_event = channel_open_init_try_from_abci_event(&event).ok()?; + + let channel_id = open_init_event.channel_id()?.clone(); + + Some(CosmosChannelOpenInitEvent { channel_id }) + } else { + None + } + } + + fn channel_open_try_event_channel_id(event: &CosmosChannelOpenTryEvent) -> &ChannelId { + &event.channel_id + } + + fn try_extract_channel_open_try_event( + event: Arc, + ) -> Option { + let event_type = event.kind.parse().ok()?; + + if let IbcEventType::OpenTryChannel = event_type { + let open_try_event = channel_open_try_try_from_abci_event(&event).ok()?; + + let channel_id = open_try_event.channel_id()?.clone(); + + Some(CosmosChannelOpenTryEvent { channel_id }) + } else { + None + } + } + + fn channel_open_init_event_channel_id(event: &CosmosChannelOpenInitEvent) -> &ChannelId { + &event.channel_id + } + + fn chain_id(&self) -> &ChainId { + &self.chain_id + } + + async fn send_messages( + &self, + messages: Vec>, + ) -> Result>>, Error> { + let events = self.tx_context.send_messages(messages).await?; + + Ok(events) + } + + async fn query_chain_status(&self) -> Result { + query_chain_status(self).await + } + + fn event_subscription(&self) -> &Arc)>> { + &self.subscription + } + + async fn query_write_acknowledgement_event( + &self, + packet: &Packet, + ) -> Result, Error> { + query_write_acknowledgement_event(self, packet).await + } + + async fn build_create_client_payload( + &self, + client_settings: &ClientSettings, + ) -> Result { + build_create_client_payload(self, client_settings).await + } + + async fn build_update_client_payload( + &self, + trusted_height: &Height, + target_height: &Height, + client_state: TendermintClientState, + ) -> Result { + build_update_client_payload(self, trusted_height, target_height, client_state).await + } + + async fn build_connection_open_init_payload( + &self, + _client_state: &Self::ClientState, + ) -> Result { + build_connection_open_init_payload(self).await + } + + async fn build_connection_open_try_payload( + &self, + _client_state: &TendermintClientState, + height: &Height, + client_id: &ClientId, + connection_id: &ConnectionId, + ) -> Result { + build_connection_open_try_payload(self, height, client_id, connection_id).await + } + + async fn build_connection_open_ack_payload( + &self, + _client_state: &TendermintClientState, + height: &Height, + client_id: &ClientId, + connection_id: &ConnectionId, + ) -> Result { + build_connection_open_ack_payload(self, height, client_id, connection_id).await + } + + async fn build_connection_open_confirm_payload( + &self, + _client_state: &TendermintClientState, + height: &Height, + client_id: &ClientId, + connection_id: &ConnectionId, + ) -> Result { + build_connection_open_confirm_payload(self, height, client_id, connection_id).await + } + + async fn build_channel_open_try_payload( + &self, + _client_state: &TendermintClientState, + height: &Height, + port_id: &PortId, + channel_id: &ChannelId, + ) -> Result { + build_channel_open_try_payload(self, height, port_id, channel_id).await + } + + async fn build_channel_open_ack_payload( + &self, + _client_state: &TendermintClientState, + height: &Height, + port_id: &PortId, + channel_id: &ChannelId, + ) -> Result { + build_channel_open_ack_payload(self, height, port_id, channel_id).await + } + + async fn build_channel_open_confirm_payload( + &self, + _client_state: &TendermintClientState, + height: &Height, + port_id: &PortId, + channel_id: &ChannelId, + ) -> Result { + build_channel_open_confirm_payload(self, height, port_id, channel_id).await + } + + /// Construct a receive packet to be sent to a destination Cosmos + /// chain from a source Cosmos chain. + async fn build_receive_packet_payload( + &self, + _client_state: &TendermintClientState, + height: &Height, + packet: &Packet, + ) -> Result { + build_receive_packet_payload(self, height, packet).await + } + + /// Construct an acknowledgement packet to be sent from a Cosmos + /// chain that successfully received a packet from another Cosmos + /// chain. + async fn build_ack_packet_payload( + &self, + _client_state: &TendermintClientState, + height: &Height, + packet: &Packet, + ack: &WriteAcknowledgement, + ) -> Result { + build_ack_packet_payload(self, height, packet, ack).await + } + + /// Construct a timeout packet message to be sent between Cosmos chains + /// over an unordered channel in the event that a packet that originated + /// from a source chain was not received. + async fn build_timeout_unordered_packet_payload( + &self, + _client_state: &TendermintClientState, + height: &Height, + packet: &Packet, + ) -> Result { + build_timeout_unordered_packet_payload(self, height, packet).await + } +} + +#[async_trait] +impl OfaIbcChain> for CosmosChain +where + Chain: ChainHandle, + Counterparty: ChainHandle, +{ + fn incoming_packet_src_channel_id(packet: &Packet) -> &ChannelId { + &packet.source_channel + } + + fn incoming_packet_dst_channel_id(packet: &Packet) -> &ChannelId { + &packet.destination_channel + } + + fn incoming_packet_src_port(packet: &Packet) -> &PortId { + &packet.source_port + } + + fn incoming_packet_dst_port(packet: &Packet) -> &PortId { + &packet.destination_port + } + + fn incoming_packet_sequence(packet: &Packet) -> &Sequence { + &packet.sequence + } + + fn incoming_packet_timeout_height(packet: &Packet) -> Option<&Height> { + match &packet.timeout_height { + TimeoutHeight::Never => None, + TimeoutHeight::At(h) => Some(h), + } + } + + fn incoming_packet_timeout_timestamp(packet: &Packet) -> &Timestamp { + &packet.timeout_timestamp + } + + fn outgoing_packet_src_channel_id(packet: &Packet) -> &ChannelId { + &packet.source_channel + } + + fn outgoing_packet_dst_channel_id(packet: &Packet) -> &ChannelId { + &packet.destination_channel + } + + fn outgoing_packet_src_port(packet: &Packet) -> &PortId { + &packet.source_port + } + + fn outgoing_packet_dst_port(packet: &Packet) -> &PortId { + &packet.destination_port + } + + fn outgoing_packet_sequence(packet: &Packet) -> &Sequence { + &packet.sequence + } + + fn outgoing_packet_timeout_height(packet: &Packet) -> Option<&Height> { + match &packet.timeout_height { + TimeoutHeight::Never => None, + TimeoutHeight::At(h) => Some(h), + } + } + + fn outgoing_packet_timeout_timestamp(packet: &Packet) -> &Timestamp { + &packet.timeout_timestamp + } + + fn counterparty_message_height_for_update_client( + message: &Arc, + ) -> Option { + message.counterparty_message_height_for_update_client() + } + + async fn query_chain_id_from_channel_id( + &self, + channel_id: &ChannelId, + port_id: &PortId, + ) -> Result { + query_chain_id_from_channel_id(self, channel_id, port_id).await + } + + async fn query_client_state( + &self, + client_id: &ClientId, + ) -> Result { + query_client_state(self, client_id).await + } + + async fn query_consensus_state( + &self, + client_id: &ClientId, + height: &Height, + ) -> Result { + query_consensus_state(self, client_id, height).await + } + + async fn query_is_packet_received( + &self, + port_id: &PortId, + channel_id: &ChannelId, + sequence: &Sequence, + ) -> Result { + query_is_packet_received(self, port_id, channel_id, sequence).await + } + + /// Query the sequences of the packets that the chain has committed to be + /// sent to the counterparty chain, of which the full packet relaying is not + /// yet completed. Once the chain receives the ack from the counterparty + /// chain, a given sequence should be removed from the packet commitment list. + async fn query_packet_commitments( + &self, + channel_id: &ChannelId, + port_id: &PortId, + ) -> Result<(Vec, Height), Error> { + query_packet_commitments(self, channel_id, port_id).await + } + + /// Given a list of counterparty commitment sequences, + /// return a filtered list of sequences which the chain + /// has not received the packet from the counterparty chain. + async fn query_unreceived_packet_sequences( + &self, + channel_id: &ChannelId, + port_id: &PortId, + sequences: &[Sequence], + ) -> Result, Self::Error> { + query_unreceived_packet_sequences(self, channel_id, port_id, sequences).await + } + + /// Given a list of sequences, a channel and port will query a list of outgoing + /// packets which have not been relayed. + async fn query_send_packets_from_sequences( + &self, + channel_id: &ChannelId, + port_id: &PortId, + counterparty_channel_id: &ChannelId, + counterparty_port_id: &PortId, + sequences: &[Sequence], + // The height is given to query the packets from a specific height. + // This height should be the same as the query height from the + // `CanQueryPacketCommitments` made on the same chain. + height: &Height, + ) -> Result, Self::Error> { + query_send_packets_from_sequences( + self, + channel_id, + port_id, + counterparty_channel_id, + counterparty_port_id, + sequences, + height, + ) + .await + } + + async fn build_receive_packet_message( + &self, + packet: &Packet, + payload: CosmosReceivePacketPayload, + ) -> Result, Error> { + build_receive_packet_message(packet, payload) + } + + async fn build_ack_packet_message( + &self, + packet: &Packet, + payload: CosmosAckPacketPayload, + ) -> Result, Error> { + build_ack_packet_message(packet, payload) + } + + async fn build_timeout_unordered_packet_message( + &self, + packet: &Packet, + payload: CosmosTimeoutUnorderedPacketPayload, + ) -> Result, Error> { + build_timeout_unordered_packet_message(packet, payload) + } + + async fn build_create_client_message( + &self, + payload: CosmosCreateClientPayload, + ) -> Result, Error> { + build_create_client_message(payload) + } + + async fn build_update_client_message( + &self, + client_id: &ClientId, + payload: CosmosUpdateClientPayload, + ) -> Result>, Error> { + build_update_client_message(client_id, payload) + } + + async fn find_consensus_state_height_before( + &self, + client_id: &ClientId, + target_height: &Height, + ) -> Result { + find_consensus_state_height_before(self, client_id, target_height).await + } + + async fn build_connection_open_init_message( + &self, + client_id: &ClientId, + counterparty_client_id: &ClientId, + init_connection_options: &CosmosInitConnectionOptions, + counterparty_payload: CosmosConnectionOpenInitPayload, + ) -> Result, Error> { + build_connection_open_init_message( + self, + client_id, + counterparty_client_id, + init_connection_options, + counterparty_payload, + ) + .await + } + + async fn build_connection_open_try_message( + &self, + client_id: &ClientId, + counterparty_client_id: &ClientId, + counterparty_connection_id: &ConnectionId, + counterparty_payload: CosmosConnectionOpenTryPayload, + ) -> Result, Error> { + build_connection_open_try_message( + client_id, + counterparty_client_id, + counterparty_connection_id, + counterparty_payload, + ) + } + + async fn build_connection_open_ack_message( + &self, + connection_id: &ConnectionId, + counterparty_connection_id: &ConnectionId, + counterparty_payload: CosmosConnectionOpenAckPayload, + ) -> Result, Error> { + build_connection_open_ack_message( + connection_id, + counterparty_connection_id, + counterparty_payload, + ) + } + + async fn build_connection_open_confirm_message( + &self, + connection_id: &ConnectionId, + counterparty_payload: CosmosConnectionOpenConfirmPayload, + ) -> Result, Error> { + build_connection_open_confirm_message(connection_id, counterparty_payload) + } + + async fn build_channel_open_init_message( + &self, + port_id: &PortId, + counterparty_port_id: &PortId, + init_channel_options: &CosmosInitChannelOptions, + ) -> Result, Error> { + build_channel_open_init_message(port_id, counterparty_port_id, init_channel_options) + } + + async fn build_channel_open_try_message( + &self, + port_id: &PortId, + counterparty_port_id: &PortId, + counterparty_channel_id: &ChannelId, + counterparty_payload: CosmosChannelOpenTryPayload, + ) -> Result, Error> { + build_channel_open_try_message( + port_id, + counterparty_port_id, + counterparty_channel_id, + counterparty_payload, + ) + } + + async fn build_channel_open_ack_message( + &self, + port_id: &PortId, + channel_id: &ChannelId, + counterparty_channel_id: &ChannelId, + counterparty_payload: CosmosChannelOpenAckPayload, + ) -> Result, Error> { + build_channel_open_ack_message( + port_id, + channel_id, + counterparty_channel_id, + counterparty_payload, + ) + } + + async fn build_channel_open_confirm_message( + &self, + port_id: &PortId, + channel_id: &ChannelId, + counterparty_payload: CosmosChannelOpenConfirmPayload, + ) -> Result, Error> { + build_channel_open_confirm_message(port_id, channel_id, counterparty_payload) + } +} diff --git a/crates/relayer-cosmos/src/impls/mod.rs b/crates/relayer-cosmos/src/impls/mod.rs new file mode 100644 index 0000000000..037cd4756d --- /dev/null +++ b/crates/relayer-cosmos/src/impls/mod.rs @@ -0,0 +1,7 @@ +pub mod birelay; +pub mod builder; +pub mod chain; +pub mod relay; +pub mod subscription; +pub mod telemetry; +pub mod transaction; diff --git a/crates/relayer-cosmos/src/impls/relay.rs b/crates/relayer-cosmos/src/impls/relay.rs new file mode 100644 index 0000000000..a723a41755 --- /dev/null +++ b/crates/relayer-cosmos/src/impls/relay.rs @@ -0,0 +1,185 @@ +use async_trait::async_trait; +use eyre::eyre; +use futures::channel::oneshot::{channel, Sender}; +use ibc_relayer::chain::handle::ChainHandle; +use ibc_relayer_all_in_one::one_for_all::traits::chain::OfaChain; +use ibc_relayer_all_in_one::one_for_all::traits::relay::OfaRelay; +use ibc_relayer_all_in_one::one_for_all::types::chain::OfaChainWrapper; +use ibc_relayer_runtime::types::error::Error as TokioError; +use ibc_relayer_runtime::types::log::logger::TracingLogger; +use ibc_relayer_runtime::types::runtime::TokioRuntimeContext; +use ibc_relayer_types::core::ics04_channel::packet::Packet; +use ibc_relayer_types::core::ics24_host::identifier::{ChannelId, ClientId, ConnectionId}; + +use crate::contexts::chain::CosmosChain; +use crate::contexts::relay::CosmosRelay; +use crate::types::batch::CosmosBatchSender; +use crate::types::error::{BaseError, Error}; + +pub struct PacketLock { + pub release_sender: Option>, +} + +impl Drop for PacketLock { + fn drop(&mut self) { + if let Some(sender) = self.release_sender.take() { + let _ = sender.send(()); + } + } +} + +#[async_trait] +impl OfaRelay for CosmosRelay +where + SrcChain: ChainHandle, + DstChain: ChainHandle, +{ + type Error = Error; + + type Runtime = TokioRuntimeContext; + + type Logger = TracingLogger; + + type Packet = Packet; + + type SrcChain = CosmosChain; + + type DstChain = CosmosChain; + + type PacketLock<'a> = PacketLock; + + fn runtime_error(e: TokioError) -> Error { + BaseError::tokio(e).into() + } + + fn src_chain_error(e: Error) -> Error { + e + } + + fn dst_chain_error(e: Error) -> Error { + e + } + + fn runtime(&self) -> &TokioRuntimeContext { + &self.runtime + } + + fn logger(&self) -> &TracingLogger { + &TracingLogger + } + + fn src_client_id(&self) -> &ClientId { + &self.src_client_id + } + + fn dst_client_id(&self) -> &ClientId { + &self.dst_client_id + } + + fn src_chain(&self) -> &OfaChainWrapper { + &self.src_chain + } + + fn dst_chain(&self) -> &OfaChainWrapper { + &self.dst_chain + } + + async fn try_acquire_packet_lock<'a>(&'a self, packet: &'a Packet) -> Option { + let packet_key = ( + packet.source_channel.clone(), + packet.source_port.clone(), + packet.destination_channel.clone(), + packet.destination_port.clone(), + packet.sequence, + ); + + let mutex = &self.packet_lock_mutex; + + let mut lock_table = mutex.lock().await; + + if lock_table.contains(&packet_key) { + None + } else { + lock_table.insert(packet_key.clone()); + + let runtime = &self.runtime().runtime; + + let (sender, receiver) = channel(); + + let mutex = mutex.clone(); + + runtime.spawn(async move { + let _ = receiver.await; + let mut lock_table = mutex.lock().await; + lock_table.remove(&packet_key); + }); + + Some(PacketLock { + release_sender: Some(sender), + }) + } + } + + fn is_retryable_error(_: &Error) -> bool { + false + } + + fn max_retry_exceeded_error(e: Error) -> Error { + e + } + + fn missing_connection_init_event_error(&self) -> Error { + BaseError::generic(eyre!("missing_connection_init_event_error")).into() + } + + fn missing_src_create_client_event_error( + src_chain: &Self::SrcChain, + dst_chain: &Self::DstChain, + ) -> Self::Error { + BaseError::generic(eyre!("missing CreateClient event when creating client from chain {} with counterparty chain {}", + src_chain.chain_id(), + dst_chain.chain_id(), + )).into() + } + + fn missing_dst_create_client_event_error( + dst_chain: &Self::DstChain, + src_chain: &Self::SrcChain, + ) -> Self::Error { + BaseError::generic(eyre!("missing CreateClient event when creating client from chain {} with counterparty chain {}", + dst_chain.chain_id(), + src_chain.chain_id(), + )).into() + } + + fn missing_connection_try_event_error(&self, src_connection_id: &ConnectionId) -> Error { + BaseError::generic(eyre!( + "missing_connection_try_event_error: {}", + src_connection_id + )) + .into() + } + + fn missing_channel_init_event_error(&self) -> Error { + BaseError::generic(eyre!("missing_channel_init_event_error")).into() + } + + fn missing_channel_try_event_error(&self, src_channel_id: &ChannelId) -> Error { + BaseError::generic(eyre!("missing_channel_try_event_error: {}", src_channel_id)).into() + } + + async fn should_relay_packet(&self, packet: &Packet) -> Result { + Ok(self + .packet_filter + .channel_policy + .is_allowed(&packet.source_port, &packet.source_channel)) + } + + fn src_chain_message_batch_sender(&self) -> &CosmosBatchSender { + &self.src_chain_message_batch_sender + } + + fn dst_chain_message_batch_sender(&self) -> &CosmosBatchSender { + &self.dst_chain_message_batch_sender + } +} diff --git a/crates/relayer-cosmos/src/impls/subscription.rs b/crates/relayer-cosmos/src/impls/subscription.rs new file mode 100644 index 0000000000..0a9d14dc94 --- /dev/null +++ b/crates/relayer-cosmos/src/impls/subscription.rs @@ -0,0 +1,254 @@ +use alloc::sync::Arc; +use core::pin::Pin; +use core::time::Duration; +use futures::lock::Mutex; +use tendermint_rpc::client::CompatMode; +use tracing::error; + +use async_trait::async_trait; +use futures::stream::{self, Stream, StreamExt, TryStreamExt}; +use ibc_relayer_components::core::traits::sync::Async; +use ibc_relayer_components::runtime::impls::subscription::closure::CanCreateClosureSubscription; +use ibc_relayer_components::runtime::traits::subscription::Subscription; +use ibc_relayer_components_extra::runtime::impls::subscription::multiplex::CanMultiplexSubscription; +use ibc_relayer_components_extra::runtime::traits::spawn::{HasSpawner, Spawner}; +use ibc_relayer_types::core::ics02_client::height::Height; +use moka::future::Cache; +use tendermint::abci::Event as AbciEvent; +use tendermint_rpc::event::{Event as RpcEvent, EventData as RpcEventData}; +use tendermint_rpc::query::Query; +use tendermint_rpc::{SubscriptionClient, WebSocketClient, WebSocketClientUrl}; + +use crate::types::error::{BaseError, Error}; + +/** + Creates a new ABCI event subscription that automatically reconnects. +*/ +pub trait CanCreateAbciEventSubscription: Async { + fn new_abci_event_subscription( + &self, + chain_version: u64, + websocket_url: WebSocketClientUrl, + compat_mode: CompatMode, + queries: Vec, + ) -> Arc)>>; +} + +impl CanCreateAbciEventSubscription for Runtime +where + Runtime: + CanCreateAbciEventStream + CanCreateClosureSubscription + CanMultiplexSubscription + Clone, +{ + fn new_abci_event_subscription( + &self, + chain_version: u64, + websocket_url: WebSocketClientUrl, + compat_mode: CompatMode, + queries: Vec, + ) -> Arc)>> { + let base_subscription = { + let runtime = self.clone(); + + Runtime::new_closure_subscription(move || { + let runtime = runtime.clone(); + let websocket_url = websocket_url.clone(); + let queries = queries.clone(); + + Box::pin(async move { + let stream_result = runtime + .new_abci_event_stream( + chain_version, + &websocket_url, + &compat_mode, + &queries, + ) + .await; + + match stream_result { + Ok(event_stream) => Some(event_stream), + Err(e) => { + error!( + error = "failed to create new ABCI event stream. terminating subscription", + details = %e, + websocket_url = %websocket_url, + ); + + None + } + } + }) + }) + }; + + let subscription = self.multiplex_subscription(base_subscription, |e| e); + + subscription + } +} + +#[async_trait] +pub trait CanCreateAbciEventStream: Async { + async fn new_abci_event_stream( + &self, + chain_version: u64, + websocket_url: &WebSocketClientUrl, + compat_mode: &CompatMode, + queries: &[Query], + ) -> Result)> + Send + 'static>>, Error>; +} + +#[async_trait] +impl CanCreateAbciEventStream for Runtime +where + Runtime: HasSpawner, +{ + async fn new_abci_event_stream( + &self, + chain_version: u64, + websocket_url: &WebSocketClientUrl, + compat_mode: &CompatMode, + queries: &[Query], + ) -> Result)> + Send + 'static>>, Error> { + let builder = WebSocketClient::builder(websocket_url.clone()).compat_mode(*compat_mode); + + let (client, driver) = builder.build().await.map_err(BaseError::tendermint_rpc)?; + + let spawner = self.spawner(); + spawner.spawn(async move { + let _ = driver.run().await; + }); + + let event_stream = + new_abci_event_stream_with_queries(chain_version, &client, queries).await?; + + Ok(event_stream) + } +} + +/** + A shared cache used for deduplicating events from multiple RPC event streams. + + With the current behavior of Cosmos chains, subscribing to RPC events with + multiple query parameters may result in duplicate ABCI events found in the + separate RPC event streams. + + To deduplicate the events, we need a shared cache that can be used to record + which event has been emitted before, and filter out the events that have been + emitted. + + We use a `moka` cache with TTL of 5 minutes, indexed by the event height. + This ensures the the event cache for a given height is cleared after 5 + minutes. + + Each cache entry contains a `Vec` of ABCI events, and duplication detection + is done inefficiently by looking through the whole list and comparing for + equality. Unfortunately, there is currently no better way to determine if + two ABCI events are the same, as `AbciEvent` does not implement `Ord` or + `Hash`. +*/ +type EventCache = Cache>>>>; + +async fn new_abci_event_stream_with_queries( + chain_version: u64, + websocket_client: &WebSocketClient, + queries: &[Query], +) -> Result)> + Send + 'static>>, Error> { + let cache = Cache::builder() + .time_to_live(Duration::from_secs(5 * 60)) + .build(); + + let event_streams = stream::iter(queries) + .then(|query| { + new_abci_event_stream_with_query(cache.clone(), chain_version, websocket_client, query) + }) + .try_collect::>() + .await?; + + let event_stream = stream::select_all(event_streams); + + Ok(Box::pin(event_stream)) +} + +async fn new_abci_event_stream_with_query( + cache: EventCache, + chain_version: u64, + websocket_client: &WebSocketClient, + query: &Query, +) -> Result)> + Send + 'static>>, Error> { + let rpc_stream = new_rpc_event_stream(websocket_client, query).await?; + + let abci_stream = rpc_stream + .filter_map(move |rpc_event| { + let cache = cache.clone(); + + async move { + match rpc_event.data { + RpcEventData::Tx { tx_result } => { + let raw_height = tx_result.height as u64; + + let height = Height::new(chain_version, raw_height) + .map_err(|e| { + error!( + error = "error creating new height, skipping event", + details = %e, + raw_height = raw_height, + chain_version = chain_version, + ); + + e + }) + .ok()?; + + let cache_entry = cache.entry(raw_height).or_default().await; + let mut events_cache = cache_entry.value().lock().await; + + // Store events from the current RPC event in a new vector + // to avoid duplication checks in the next statement. + // We assume that all events in an RPC event is unique. + let mut new_events = Vec::new(); + + let events_with_height = tx_result + .result + .events + .into_iter() + .filter_map(|event| { + let event = Arc::new(event); + + // Checks if the event has already been emitted + // through other event streams. If so, skip + // emitting the given event. + if events_cache.contains(&event) { + None + } else { + new_events.push(event.clone()); + Some((height, event)) + } + }) + .collect::>(); + + events_cache.append(&mut new_events); + + Some(stream::iter(events_with_height)) + } + _ => None, + } + } + }) + .flatten(); + + Ok(Box::pin(abci_stream)) +} + +async fn new_rpc_event_stream( + websocket_client: &WebSocketClient, + query: &Query, +) -> Result + Send + 'static>>, Error> { + let subscription = websocket_client + .subscribe(query.clone()) + .await + .map_err(BaseError::tendermint_rpc)?; + + let stream = subscription.filter_map(|event| async { event.ok() }); + + Ok(Box::pin(stream)) +} diff --git a/crates/relayer-cosmos/src/impls/telemetry.rs b/crates/relayer-cosmos/src/impls/telemetry.rs new file mode 100644 index 0000000000..43e24e1b30 --- /dev/null +++ b/crates/relayer-cosmos/src/impls/telemetry.rs @@ -0,0 +1,138 @@ +use ibc_relayer_components_extra::telemetry::traits::metrics::{ + HasLabel, HasMetric, TelemetryCounter, TelemetryUpDownCounter, TelemetryValueRecorder, +}; +use opentelemetry::metrics::Unit; +use opentelemetry::KeyValue; + +use crate::types::telemetry::CosmosTelemetry; + +impl HasLabel for CosmosTelemetry { + type Label = KeyValue; + + fn new_label(key: &str, value: &str) -> Self::Label { + KeyValue::new(key.to_string(), value.to_string()) + } +} + +impl HasMetric for CosmosTelemetry { + type Value = u64; + + type Unit = Unit; + + fn update_metric( + &self, + name: &str, + labels: &[Self::Label], + value: Self::Value, + description: Option<&str>, + unit: Option, + ) { + let mut counters = self.counters.lock().unwrap(); + + if let Some(metric) = counters.get(name) { + metric.add(value, labels); + } else { + let builder = self.meter.u64_counter(name); + + let builder = if let Some(description) = description { + builder.with_description(description) + } else { + builder + }; + + let builder = if let Some(unit) = unit { + builder.with_unit(unit) + } else { + builder + }; + + let metric = builder.init(); + + metric.add(value, labels); + + counters.insert(name.to_string(), metric); + } + } +} + +impl HasMetric for CosmosTelemetry { + type Value = u64; + + type Unit = Unit; + + fn update_metric( + &self, + name: &str, + labels: &[Self::Label], + value: Self::Value, + description: Option<&str>, + unit: Option, + ) { + let mut value_recorders = self.value_recorders.lock().unwrap(); + + if let Some(metric) = value_recorders.get(name) { + metric.record(value, labels); + } else { + let builder = self.meter.u64_value_recorder(name); + + let builder = if let Some(description) = description { + builder.with_description(description) + } else { + builder + }; + + let builder = if let Some(unit) = unit { + builder.with_unit(unit) + } else { + builder + }; + + let metric = builder.init(); + + metric.record(value, labels); + + value_recorders.insert(name.to_string(), metric); + } + } +} + +impl HasMetric for CosmosTelemetry { + type Value = i64; + + type Unit = Unit; + + fn update_metric( + &self, + name: &str, + labels: &[Self::Label], + value: Self::Value, + description: Option<&str>, + unit: Option, + ) { + let mut updown_counters = self.updown_counters.lock().unwrap(); + + if let Some(metric) = updown_counters.get(name) { + metric.add(value, labels); + } else { + let builder = self.meter.i64_up_down_counter(name); + + let builder = if let Some(description) = description { + builder.with_description(description) + } else { + builder + }; + + let builder = if let Some(unit) = unit { + builder.with_unit(unit) + } else { + builder + }; + + let metric = builder.init(); + + metric.add(value, labels); + + updown_counters.insert(name.to_string(), metric); + } + } +} diff --git a/crates/relayer-cosmos/src/impls/transaction/component.rs b/crates/relayer-cosmos/src/impls/transaction/component.rs new file mode 100644 index 0000000000..f77e7d6317 --- /dev/null +++ b/crates/relayer-cosmos/src/impls/transaction/component.rs @@ -0,0 +1,10 @@ +use ibc_relayer_components::core::traits::component::HasComponents; +use ibc_relayer_components_extra::components::extra::transaction::ExtraTxComponents; + +use crate::contexts::transaction::CosmosTxContext; + +pub struct CosmosTxComponents; + +impl HasComponents for CosmosTxContext { + type Components = ExtraTxComponents; +} diff --git a/crates/relayer-cosmos/src/impls/transaction/components/encode.rs b/crates/relayer-cosmos/src/impls/transaction/components/encode.rs new file mode 100644 index 0000000000..5230ca16fc --- /dev/null +++ b/crates/relayer-cosmos/src/impls/transaction/components/encode.rs @@ -0,0 +1,39 @@ +use alloc::sync::Arc; +use async_trait::async_trait; +use ibc_proto::cosmos::tx::v1beta1::Fee; +use ibc_relayer::chain::cosmos::encode::{key_pair_to_signer, sign_tx}; +use ibc_relayer::chain::cosmos::types::account::Account; +use ibc_relayer::chain::cosmos::types::tx::SignedTx; +use ibc_relayer::config::types::Memo; +use ibc_relayer::keyring::Secp256k1KeyPair; +use ibc_relayer_components::transaction::traits::components::tx_encoder::TxEncoder; + +use crate::contexts::transaction::CosmosTxContext; +use crate::impls::transaction::component::CosmosTxComponents; +use crate::traits::message::CosmosMessage; +use crate::types::error::{BaseError, Error}; + +#[async_trait] +impl TxEncoder for CosmosTxComponents { + async fn encode_tx( + context: &CosmosTxContext, + key_pair: &Secp256k1KeyPair, + account: &Account, + fee: &Fee, + messages: &[Arc], + ) -> Result { + let tx_config = &context.tx_config; + let memo = Memo::default(); + let signer = key_pair_to_signer(key_pair).map_err(BaseError::relayer)?; + + let raw_messages = messages + .iter() + .map(|message| message.encode_protobuf(&signer).map_err(BaseError::encode)) + .collect::, _>>()?; + + let signed_tx = sign_tx(tx_config, key_pair, account, &memo, &raw_messages, fee) + .map_err(BaseError::relayer)?; + + Ok(signed_tx) + } +} diff --git a/crates/relayer-cosmos/src/impls/transaction/components/estimate.rs b/crates/relayer-cosmos/src/impls/transaction/components/estimate.rs new file mode 100644 index 0000000000..f76dd4cd09 --- /dev/null +++ b/crates/relayer-cosmos/src/impls/transaction/components/estimate.rs @@ -0,0 +1,35 @@ +use async_trait::async_trait; +use ibc_proto::cosmos::tx::v1beta1::{Fee, Tx}; +use ibc_relayer::chain::cosmos::gas::gas_amount_to_fee; +use ibc_relayer::chain::cosmos::simulate::send_tx_simulate; +use ibc_relayer::chain::cosmos::types::tx::SignedTx; +use ibc_relayer_components::transaction::traits::components::tx_fee_estimater::TxFeeEstimator; + +use crate::contexts::transaction::CosmosTxContext; +use crate::impls::transaction::component::CosmosTxComponents; +use crate::types::error::{BaseError, Error}; + +#[async_trait] +impl TxFeeEstimator for CosmosTxComponents { + async fn estimate_tx_fee(context: &CosmosTxContext, tx: &SignedTx) -> Result { + let tx = Tx { + body: Some(tx.body.clone()), + auth_info: Some(tx.auth_info.clone()), + signatures: tx.signatures.clone(), + }; + + let tx_config = &context.tx_config; + + let response = send_tx_simulate(&tx_config.grpc_address, tx) + .await + .map_err(BaseError::relayer)?; + + let gas_info = response + .gas_info + .ok_or_else(BaseError::missing_simulate_gas_info)?; + + let fee = gas_amount_to_fee(&tx_config.gas_config, gas_info.gas_used); + + Ok(fee) + } +} diff --git a/crates/relayer-cosmos/src/impls/transaction/components/mod.rs b/crates/relayer-cosmos/src/impls/transaction/components/mod.rs new file mode 100644 index 0000000000..caa094d3ef --- /dev/null +++ b/crates/relayer-cosmos/src/impls/transaction/components/mod.rs @@ -0,0 +1,5 @@ +pub mod encode; +pub mod estimate; +pub mod query_nonce; +pub mod query_tx_response; +pub mod submit; diff --git a/crates/relayer-cosmos/src/impls/transaction/components/query_nonce.rs b/crates/relayer-cosmos/src/impls/transaction/components/query_nonce.rs new file mode 100644 index 0000000000..4f366fea2d --- /dev/null +++ b/crates/relayer-cosmos/src/impls/transaction/components/query_nonce.rs @@ -0,0 +1,26 @@ +use async_trait::async_trait; +use ibc_relayer::chain::cosmos::query::account::query_account; +use ibc_relayer::chain::cosmos::types::account::Account; +use ibc_relayer::keyring::{Secp256k1KeyPair, SigningKeyPair}; +use ibc_relayer_components::transaction::traits::components::nonce_querier::NonceQuerier; + +use crate::contexts::transaction::CosmosTxContext; +use crate::impls::transaction::component::CosmosTxComponents; +use crate::types::error::{BaseError, Error}; + +#[async_trait] +impl NonceQuerier for CosmosTxComponents { + async fn query_nonce( + context: &CosmosTxContext, + key_pair: &Secp256k1KeyPair, + ) -> Result { + let tx_config = &context.tx_config; + let address = key_pair.account(); + + let account = query_account(&tx_config.grpc_address, &address) + .await + .map_err(BaseError::relayer)?; + + Ok(account.into()) + } +} diff --git a/crates/relayer-cosmos/src/impls/transaction/components/query_tx_response.rs b/crates/relayer-cosmos/src/impls/transaction/components/query_tx_response.rs new file mode 100644 index 0000000000..fd2afb946f --- /dev/null +++ b/crates/relayer-cosmos/src/impls/transaction/components/query_tx_response.rs @@ -0,0 +1,26 @@ +use async_trait::async_trait; +use ibc_relayer::chain::cosmos::query::tx::query_tx_response; +use ibc_relayer_components::transaction::traits::components::tx_response_querier::TxResponseQuerier; +use tendermint::Hash as TxHash; +use tendermint_rpc::endpoint::tx::Response as TxResponse; + +use crate::contexts::transaction::CosmosTxContext; +use crate::impls::transaction::component::CosmosTxComponents; +use crate::types::error::{BaseError, Error}; + +#[async_trait] +impl TxResponseQuerier for CosmosTxComponents { + async fn query_tx_response( + context: &CosmosTxContext, + tx_hash: &TxHash, + ) -> Result, Error> { + let tx_config = &context.tx_config; + let rpc_client = &context.rpc_client; + + let response = query_tx_response(rpc_client, &tx_config.rpc_address, tx_hash) + .await + .map_err(BaseError::relayer)?; + + Ok(response) + } +} diff --git a/crates/relayer-cosmos/src/impls/transaction/components/submit.rs b/crates/relayer-cosmos/src/impls/transaction/components/submit.rs new file mode 100644 index 0000000000..d7e841534a --- /dev/null +++ b/crates/relayer-cosmos/src/impls/transaction/components/submit.rs @@ -0,0 +1,43 @@ +use async_trait::async_trait; +use ibc_proto::cosmos::tx::v1beta1::TxRaw; +use ibc_relayer::chain::cosmos::tx::broadcast_tx_sync; +use ibc_relayer::chain::cosmos::types::tx::SignedTx; +use ibc_relayer_components::transaction::traits::components::tx_submitter::TxSubmitter; +use tendermint::Hash as TxHash; + +use crate::contexts::transaction::CosmosTxContext; +use crate::impls::transaction::component::CosmosTxComponents; +use crate::types::error::{BaseError, Error}; + +#[async_trait] +impl TxSubmitter for CosmosTxComponents { + async fn submit_tx(context: &CosmosTxContext, tx: &SignedTx) -> Result { + let data = encode_tx_raw(tx)?; + + let tx_config = &context.tx_config; + let rpc_client = &context.rpc_client; + + let response = broadcast_tx_sync(rpc_client, &tx_config.rpc_address, data) + .await + .map_err(BaseError::relayer)?; + + if response.code.is_err() { + return Err(BaseError::check_tx(response).into()); + } + + Ok(response.hash) + } +} + +fn encode_tx_raw(signed_tx: &SignedTx) -> Result, Error> { + let tx_raw = TxRaw { + body_bytes: signed_tx.body_bytes.clone(), + auth_info_bytes: signed_tx.auth_info_bytes.clone(), + signatures: signed_tx.signatures.clone(), + }; + + let mut tx_bytes = Vec::new(); + prost::Message::encode(&tx_raw, &mut tx_bytes).map_err(BaseError::encode)?; + + Ok(tx_bytes) +} diff --git a/crates/relayer-cosmos/src/impls/transaction/error.rs b/crates/relayer-cosmos/src/impls/transaction/error.rs new file mode 100644 index 0000000000..eb7c68aa06 --- /dev/null +++ b/crates/relayer-cosmos/src/impls/transaction/error.rs @@ -0,0 +1,11 @@ +use ibc_relayer_components::transaction::components::poll::CanRaiseNoTxResponseError; +use tendermint::Hash as TxHash; + +use crate::contexts::transaction::CosmosTxContext; +use crate::types::error::{BaseError, Error}; + +impl CanRaiseNoTxResponseError for CosmosTxContext { + fn tx_no_response_error(tx_hash: &TxHash) -> Error { + BaseError::tx_no_response(*tx_hash).into() + } +} diff --git a/crates/relayer-cosmos/src/impls/transaction/fields.rs b/crates/relayer-cosmos/src/impls/transaction/fields.rs new file mode 100644 index 0000000000..75e872fb23 --- /dev/null +++ b/crates/relayer-cosmos/src/impls/transaction/fields.rs @@ -0,0 +1,54 @@ +use core::time::Duration; +use futures::lock::Mutex; +use ibc_proto::cosmos::tx::v1beta1::Fee; +use ibc_relayer::keyring::Secp256k1KeyPair; +use ibc_relayer_components::chain::traits::types::chain_id::HasChainId; +use ibc_relayer_components::runtime::traits::mutex::HasMutex; +use ibc_relayer_components::transaction::components::poll::HasPollTimeout; +use ibc_relayer_components::transaction::traits::fee::HasFeeForSimulation; +use ibc_relayer_components::transaction::traits::nonce::mutex::HasMutexForNonceAllocation; +use ibc_relayer_components::transaction::traits::signer::HasSigner; +use ibc_relayer_types::core::ics24_host::identifier::ChainId; + +use crate::contexts::transaction::CosmosTxContext; + +impl HasChainId for CosmosTxContext { + fn chain_id(&self) -> &ChainId { + &self.tx_config.chain_id + } +} + +impl HasSigner for CosmosTxContext { + fn get_signer(&self) -> &Secp256k1KeyPair { + &self.key_entry + } +} + +impl HasFeeForSimulation for CosmosTxContext { + fn fee_for_simulation(&self) -> &Fee { + &self.tx_config.gas_config.max_fee + } +} + +impl HasPollTimeout for CosmosTxContext { + fn poll_timeout(&self) -> Duration { + Duration::from_secs(300) + } + + fn poll_backoff(&self) -> Duration { + Duration::from_millis(200) + } +} + +impl HasMutexForNonceAllocation for CosmosTxContext { + fn mutex_for_nonce_allocation(&self, _signer: &Secp256k1KeyPair) -> &Mutex<()> { + &self.nonce_mutex + } + + fn mutex_to_nonce_guard<'a>( + mutex_guard: ::MutexGuard<'a, ()>, + nonce: Self::Nonce, + ) -> Self::NonceGuard<'a> { + (mutex_guard, nonce) + } +} diff --git a/crates/relayer-cosmos/src/impls/transaction/instances.rs b/crates/relayer-cosmos/src/impls/transaction/instances.rs new file mode 100644 index 0000000000..e5cd7e813d --- /dev/null +++ b/crates/relayer-cosmos/src/impls/transaction/instances.rs @@ -0,0 +1,23 @@ +use alloc::sync::Arc; +use async_trait::async_trait; +use ibc_relayer_components::chain::traits::components::message_sender::{ + CanSendMessages, MessageSender, +}; +use tendermint::abci::Event as AbciEvent; + +use crate::contexts::transaction::CosmosTxContext; +use crate::traits::message::CosmosMessage; +use crate::types::error::Error; + +pub struct CosmosTxInstances; + +// Proof that CosmosTxContext implements [`CanSendMessages`]. +#[async_trait] +impl MessageSender for CosmosTxInstances { + async fn send_messages( + tx_context: &CosmosTxContext, + messages: Vec>, + ) -> Result>>, Error> { + tx_context.send_messages(messages).await + } +} diff --git a/crates/relayer-cosmos/src/impls/transaction/log.rs b/crates/relayer-cosmos/src/impls/transaction/log.rs new file mode 100644 index 0000000000..0811d4ef13 --- /dev/null +++ b/crates/relayer-cosmos/src/impls/transaction/log.rs @@ -0,0 +1,19 @@ +use ibc_relayer::chain::cosmos::types::account::Account; +use ibc_relayer_components::logger::traits::has_logger::HasLogger; +use ibc_relayer_components::transaction::traits::logs::nonce::CanLogNonce; +use ibc_relayer_runtime::types::log::logger::TracingLogger; +use ibc_relayer_runtime::types::log::value::LogValue; + +use crate::contexts::transaction::CosmosTxContext; + +impl HasLogger for CosmosTxContext { + fn logger(&self) -> &TracingLogger { + &TracingLogger + } +} + +impl CanLogNonce for CosmosTxContext { + fn log_nonce(nonce: &Account) -> LogValue<'_> { + LogValue::Debug(nonce) + } +} diff --git a/crates/relayer-cosmos/src/impls/transaction/mod.rs b/crates/relayer-cosmos/src/impls/transaction/mod.rs new file mode 100644 index 0000000000..6d137c3be1 --- /dev/null +++ b/crates/relayer-cosmos/src/impls/transaction/mod.rs @@ -0,0 +1,8 @@ +pub mod component; +pub mod components; +pub mod error; +pub mod fields; +pub mod instances; +pub mod log; +pub mod parse; +pub mod types; diff --git a/crates/relayer-cosmos/src/impls/transaction/parse.rs b/crates/relayer-cosmos/src/impls/transaction/parse.rs new file mode 100644 index 0000000000..32d0cf460c --- /dev/null +++ b/crates/relayer-cosmos/src/impls/transaction/parse.rs @@ -0,0 +1,73 @@ +use alloc::sync::Arc; +use ibc_relayer_components::transaction::traits::event::CanParseTxResponseAsEvents; +use tendermint::abci::Event as AbciEvent; +use tendermint_rpc::endpoint::tx::Response as TxResponse; + +use crate::contexts::transaction::CosmosTxContext; +use crate::types::error::Error; + +impl CanParseTxResponseAsEvents for CosmosTxContext { + fn parse_tx_response_as_events( + response: TxResponse, + ) -> Result>>, Error> { + let events = split_events_by_messages(response.tx_result.events); + + Ok(events) + } +} + +fn split_events_by_messages(in_events: Vec) -> Vec>> { + let mut out_events = Vec::new(); + let mut current_events = Vec::new(); + let mut first_message_event_found = false; + + for event in in_events.into_iter() { + // TODO: What is the purpose of this filter ? + // It seems that the event kind "message" from the Tx Response of some chains + // only have 1 event attribute: + // event kind: message + // event attributes: [ + // EventAttribute { + // key: "sender", + // value: "cosmos1w2jl4lt77j0u3wuvknmrflp9pmwx5zmrr2j8x7", + // index: true, + // }, + // ] + // But others have multiple event attributes: + // event kind in send_message: message + // event.attributes: [ + // EventAttribute { + // key: "action", + // value: "/ibc.core.channel.v1.MsgAcknowledgement", + // index: true, + // }, + // EventAttribute { + // key: "sender", + // value: "cosmos1zl89j8asalm9s7gd5spskxtmh4l49lzs86auqx", + // index: true, + // }, + // ] + // + //if event.kind == "message" + // && event.attributes.len() == 1 + // && &event.attributes[0].key == "action" + + if event.kind == "message" && event.attributes.iter().any(|attr| attr.key == "action") { + if first_message_event_found { + out_events.push(current_events); + } else { + first_message_event_found = true; + } + + current_events = vec![Arc::new(event)]; + } else if first_message_event_found { + current_events.push(Arc::new(event)); + } + } + + if !current_events.is_empty() { + out_events.push(current_events); + } + + out_events +} diff --git a/crates/relayer-cosmos/src/impls/transaction/types.rs b/crates/relayer-cosmos/src/impls/transaction/types.rs new file mode 100644 index 0000000000..90c077c362 --- /dev/null +++ b/crates/relayer-cosmos/src/impls/transaction/types.rs @@ -0,0 +1,94 @@ +use alloc::sync::Arc; +use futures::lock::MutexGuard; +use ibc_proto::cosmos::tx::v1beta1::{Fee, TxRaw}; +use ibc_relayer::chain::cosmos::types::account::Account; +use ibc_relayer::chain::cosmos::types::tx::SignedTx; +use ibc_relayer::keyring::Secp256k1KeyPair; +use ibc_relayer_components::chain::traits::types::chain_id::HasChainIdType; +use ibc_relayer_components::chain::traits::types::event::HasEventType; +use ibc_relayer_components::chain::traits::types::message::HasMessageType; +use ibc_relayer_components::core::traits::error::HasErrorType; +use ibc_relayer_components::logger::traits::has_logger::HasLoggerType; +use ibc_relayer_components::runtime::traits::runtime::HasRuntime; +use ibc_relayer_components::transaction::traits::nonce::guard::HasNonceGuard; +use ibc_relayer_components::transaction::traits::types::{HasNonceType, HasSignerType, HasTxTypes}; +use ibc_relayer_runtime::types::error::Error as TokioError; +use ibc_relayer_runtime::types::log::logger::TracingLogger; +use ibc_relayer_runtime::types::runtime::TokioRuntimeContext; +use ibc_relayer_types::core::ics24_host::identifier::ChainId; +use prost::Message; +use tendermint::abci::Event as AbciEvent; +use tendermint::Hash as TxHash; +use tendermint_rpc::endpoint::tx::Response as TxResponse; + +use crate::contexts::transaction::CosmosTxContext; +use crate::traits::message::CosmosMessage; +use crate::types::error::{BaseError, Error}; + +impl HasErrorType for CosmosTxContext { + type Error = Error; +} + +impl HasRuntime for CosmosTxContext { + type Runtime = TokioRuntimeContext; + + fn runtime(&self) -> &TokioRuntimeContext { + &self.runtime + } + + fn runtime_error(e: TokioError) -> Error { + BaseError::tokio(e).into() + } +} + +impl HasLoggerType for CosmosTxContext { + type Logger = TracingLogger; +} + +impl HasChainIdType for CosmosTxContext { + type ChainId = ChainId; +} + +impl HasMessageType for CosmosTxContext { + type Message = Arc; +} + +impl HasEventType for CosmosTxContext { + type Event = Arc; +} + +impl HasSignerType for CosmosTxContext { + type Signer = Secp256k1KeyPair; +} + +impl HasNonceType for CosmosTxContext { + type Nonce = Account; +} + +impl HasNonceGuard for CosmosTxContext { + type NonceGuard<'a> = (MutexGuard<'a, ()>, Account); + + fn deref_nonce<'a, 'b>((_, nonce): &'a Self::NonceGuard<'b>) -> &'a Account { + nonce + } +} + +impl HasTxTypes for CosmosTxContext { + type Transaction = SignedTx; + + type Fee = Fee; + + type TxHash = TxHash; + + type TxResponse = TxResponse; + + fn tx_size(signed_tx: &SignedTx) -> usize { + let tx_raw = TxRaw { + body_bytes: signed_tx.body_bytes.clone(), + auth_info_bytes: signed_tx.auth_info_bytes.clone(), + signatures: signed_tx.signatures.clone(), + }; + + tx_raw.encoded_len() + } +} diff --git a/crates/relayer-cosmos/src/lib.rs b/crates/relayer-cosmos/src/lib.rs new file mode 100644 index 0000000000..284841e522 --- /dev/null +++ b/crates/relayer-cosmos/src/lib.rs @@ -0,0 +1,15 @@ +//! A relayer instance for relaying between Cosmos chains. + +#![allow(clippy::type_complexity)] +#![allow(clippy::too_many_arguments)] +#![allow(clippy::let_and_return)] +#![allow(clippy::needless_lifetimes)] + +extern crate alloc; + +pub mod all_for_one; +pub mod contexts; +pub mod impls; +pub mod methods; +pub mod traits; +pub mod types; diff --git a/crates/relayer-cosmos/src/methods/chain.rs b/crates/relayer-cosmos/src/methods/chain.rs new file mode 100644 index 0000000000..14dae75233 --- /dev/null +++ b/crates/relayer-cosmos/src/methods/chain.rs @@ -0,0 +1,20 @@ +use ibc_relayer::chain::endpoint::ChainStatus; +use ibc_relayer::chain::handle::ChainHandle; + +use crate::contexts::chain::CosmosChain; +use crate::methods::runtime::HasBlockingChainHandle; +use crate::types::error::{BaseError, Error}; + +pub async fn query_chain_status( + chain: &CosmosChain, +) -> Result { + chain + .with_blocking_chain_handle(move |chain_handle| { + let status = chain_handle + .query_application_status() + .map_err(BaseError::relayer)?; + + Ok(status) + }) + .await +} diff --git a/crates/relayer-cosmos/src/methods/channel.rs b/crates/relayer-cosmos/src/methods/channel.rs new file mode 100644 index 0000000000..189a2ee15b --- /dev/null +++ b/crates/relayer-cosmos/src/methods/channel.rs @@ -0,0 +1,236 @@ +use alloc::sync::Arc; +use ibc_relayer::chain::counterparty::counterparty_chain_from_channel; +use ibc_relayer::chain::handle::ChainHandle; +use ibc_relayer::chain::requests::{IncludeProof, QueryChannelRequest, QueryHeight}; +use ibc_relayer_types::core::ics04_channel::channel::{ + ChannelEnd, Counterparty as ChannelCounterparty, State, +}; +use ibc_relayer_types::core::ics24_host::identifier::{ChainId, ChannelId, PortId}; +use ibc_relayer_types::Height; + +use crate::contexts::chain::CosmosChain; +use crate::methods::runtime::HasBlockingChainHandle; +use crate::traits::message::{CosmosMessage, ToCosmosMessage}; +use crate::types::channel::CosmosInitChannelOptions; +use crate::types::error::{BaseError, Error}; +use crate::types::messages::channel::open_ack::CosmosChannelOpenAckMessage; +use crate::types::messages::channel::open_confirm::CosmosChannelOpenConfirmMessage; +use crate::types::messages::channel::open_init::CosmosChannelOpenInitMessage; +use crate::types::messages::channel::open_try::CosmosChannelOpenTryMessage; +use crate::types::payloads::channel::{ + CosmosChannelOpenAckPayload, CosmosChannelOpenConfirmPayload, CosmosChannelOpenTryPayload, +}; + +pub async fn query_chain_id_from_channel_id( + chain: &CosmosChain, + channel_id: &ChannelId, + port_id: &PortId, +) -> Result { + let port_id = port_id.clone(); + let channel_id = channel_id.clone(); + + chain + .with_blocking_chain_handle(move |chain_handle| { + let channel_id = counterparty_chain_from_channel(&chain_handle, &channel_id, &port_id) + .map_err(BaseError::supervisor)?; + + Ok(channel_id) + }) + .await +} + +pub async fn build_channel_open_try_payload( + chain: &CosmosChain, + height: &Height, + port_id: &PortId, + channel_id: &ChannelId, +) -> Result { + let height = *height; + let port_id = port_id.clone(); + let channel_id = channel_id.clone(); + + chain + .with_blocking_chain_handle(move |chain_handle| { + let (channel_end, _) = chain_handle + .query_channel( + QueryChannelRequest { + port_id: port_id.clone(), + channel_id: channel_id.clone(), + height: QueryHeight::Latest, + }, + IncludeProof::No, + ) + .map_err(BaseError::relayer)?; + + let proofs = chain_handle + .build_channel_proofs(&port_id, &channel_id, height) + .map_err(BaseError::relayer)?; + + let payload = CosmosChannelOpenTryPayload { + ordering: channel_end.ordering, + connection_hops: channel_end.connection_hops, + version: channel_end.version, + update_height: proofs.height(), + proof_init: proofs.object_proof().clone(), + }; + + Ok(payload) + }) + .await +} + +pub async fn build_channel_open_ack_payload( + chain: &CosmosChain, + height: &Height, + port_id: &PortId, + channel_id: &ChannelId, +) -> Result { + let height = *height; + let port_id = port_id.clone(); + let channel_id = channel_id.clone(); + + chain + .with_blocking_chain_handle(move |chain_handle| { + let (channel_end, _) = chain_handle + .query_channel( + QueryChannelRequest { + port_id: port_id.clone(), + channel_id: channel_id.clone(), + height: QueryHeight::Latest, + }, + IncludeProof::No, + ) + .map_err(BaseError::relayer)?; + + let proofs = chain_handle + .build_channel_proofs(&port_id, &channel_id, height) + .map_err(BaseError::relayer)?; + + let payload = CosmosChannelOpenAckPayload { + version: channel_end.version, + update_height: proofs.height(), + proof_try: proofs.object_proof().clone(), + }; + + Ok(payload) + }) + .await +} + +pub async fn build_channel_open_confirm_payload( + chain: &CosmosChain, + height: &Height, + port_id: &PortId, + channel_id: &ChannelId, +) -> Result { + let height = *height; + let port_id = port_id.clone(); + let channel_id = channel_id.clone(); + + chain + .with_blocking_chain_handle(move |chain_handle| { + let proofs = chain_handle + .build_channel_proofs(&port_id, &channel_id, height) + .map_err(BaseError::relayer)?; + + let payload = CosmosChannelOpenConfirmPayload { + update_height: proofs.height(), + proof_ack: proofs.object_proof().clone(), + }; + + Ok(payload) + }) + .await +} + +pub fn build_channel_open_init_message( + port_id: &PortId, + counterparty_port_id: &PortId, + init_channel_options: &CosmosInitChannelOptions, +) -> Result, Error> { + let port_id = port_id.clone(); + let ordering = init_channel_options.ordering; + let connection_hops = init_channel_options.connection_hops.clone(); + let channel_version = init_channel_options.channel_version.clone(); + + let counterparty = ChannelCounterparty::new(counterparty_port_id.clone(), None); + + let channel = ChannelEnd::new( + State::Init, + ordering, + counterparty, + connection_hops, + channel_version, + ); + + let message = CosmosChannelOpenInitMessage { port_id, channel }; + + Ok(message.to_cosmos_message()) +} + +pub fn build_channel_open_try_message( + port_id: &PortId, + counterparty_port_id: &PortId, + counterparty_channel_id: &ChannelId, + counterparty_payload: CosmosChannelOpenTryPayload, +) -> Result, Error> { + let port_id = port_id.clone(); + let counterparty = ChannelCounterparty::new( + counterparty_port_id.clone(), + Some(counterparty_channel_id.clone()), + ); + let ordering = counterparty_payload.ordering; + let connection_hops = counterparty_payload.connection_hops.clone(); + let version = counterparty_payload.version.clone(); + + let channel = ChannelEnd::new( + State::TryOpen, + ordering, + counterparty, + connection_hops, + version.clone(), + ); + + let message = CosmosChannelOpenTryMessage { + port_id, + channel, + counterparty_version: version, + update_height: counterparty_payload.update_height, + proof_init: counterparty_payload.proof_init, + }; + + Ok(message.to_cosmos_message()) +} + +pub fn build_channel_open_ack_message( + port_id: &PortId, + channel_id: &ChannelId, + counterparty_channel_id: &ChannelId, + counterparty_payload: CosmosChannelOpenAckPayload, +) -> Result, Error> { + let message = CosmosChannelOpenAckMessage { + port_id: port_id.clone(), + channel_id: channel_id.clone(), + counterparty_channel_id: counterparty_channel_id.clone(), + counterparty_version: counterparty_payload.version, + update_height: counterparty_payload.update_height, + proof_try: counterparty_payload.proof_try, + }; + + Ok(message.to_cosmos_message()) +} + +pub fn build_channel_open_confirm_message( + port_id: &PortId, + channel_id: &ChannelId, + counterparty_payload: CosmosChannelOpenConfirmPayload, +) -> Result, Error> { + let message = CosmosChannelOpenConfirmMessage { + port_id: port_id.clone(), + channel_id: channel_id.clone(), + update_height: counterparty_payload.update_height, + proof_ack: counterparty_payload.proof_ack, + }; + + Ok(message.to_cosmos_message()) +} diff --git a/crates/relayer-cosmos/src/methods/client_state.rs b/crates/relayer-cosmos/src/methods/client_state.rs new file mode 100644 index 0000000000..0e25d6384a --- /dev/null +++ b/crates/relayer-cosmos/src/methods/client_state.rs @@ -0,0 +1,36 @@ +use eyre::eyre; +use ibc_relayer::chain::handle::ChainHandle; +use ibc_relayer::chain::requests::{IncludeProof, QueryClientStateRequest, QueryHeight}; +use ibc_relayer::client_state::AnyClientState; +use ibc_relayer_types::clients::ics07_tendermint::client_state::ClientState; +use ibc_relayer_types::core::ics24_host::identifier::ClientId; + +use crate::contexts::chain::CosmosChain; +use crate::methods::runtime::HasBlockingChainHandle; +use crate::types::error::{BaseError, Error}; + +pub async fn query_client_state( + chain: &CosmosChain, + client_id: &ClientId, +) -> Result { + let client_id = client_id.clone(); + + chain + .with_blocking_chain_handle(move |chain_handle| { + let (client_state, _) = chain_handle + .query_client_state( + QueryClientStateRequest { + client_id, + height: QueryHeight::Latest, + }, + IncludeProof::No, + ) + .map_err(BaseError::relayer)?; + + match client_state { + AnyClientState::Tendermint(client_state) => Ok(client_state), + _ => Err(BaseError::generic(eyre!("expected tendermint client state")).into()), + } + }) + .await +} diff --git a/crates/relayer-cosmos/src/methods/connection.rs b/crates/relayer-cosmos/src/methods/connection.rs new file mode 100644 index 0000000000..a5de80da74 --- /dev/null +++ b/crates/relayer-cosmos/src/methods/connection.rs @@ -0,0 +1,303 @@ +use alloc::sync::Arc; +use eyre::eyre; +use ibc_relayer::chain::handle::ChainHandle; +use ibc_relayer::chain::requests::{IncludeProof, QueryConnectionRequest, QueryHeight}; +use ibc_relayer::client_state::AnyClientState; +use ibc_relayer::connection::ConnectionMsgType; +use ibc_relayer_types::core::ics24_host::identifier::{ClientId, ConnectionId}; +use ibc_relayer_types::Height; + +use crate::contexts::chain::CosmosChain; +use crate::methods::runtime::HasBlockingChainHandle; +use crate::traits::message::{CosmosMessage, ToCosmosMessage}; +use crate::types::connection::CosmosInitConnectionOptions; +use crate::types::error::{BaseError, Error}; +use crate::types::messages::connection::open_ack::CosmosConnectionOpenAckMessage; +use crate::types::messages::connection::open_confirm::CosmosConnectionOpenConfirmMessage; +use crate::types::messages::connection::open_init::CosmosConnectionOpenInitMessage; +use crate::types::messages::connection::open_try::CosmosConnectionOpenTryMessage; +use crate::types::payloads::connection::{ + CosmosConnectionOpenAckPayload, CosmosConnectionOpenConfirmPayload, + CosmosConnectionOpenInitPayload, CosmosConnectionOpenTryPayload, +}; + +pub async fn build_connection_open_init_payload( + chain: &CosmosChain, +) -> Result { + chain + .with_blocking_chain_handle(move |chain_handle| { + let commitment_prefix = chain_handle + .query_commitment_prefix() + .map_err(BaseError::relayer)?; + + Ok(CosmosConnectionOpenInitPayload { commitment_prefix }) + }) + .await +} + +pub async fn build_connection_open_try_payload( + chain: &CosmosChain, + height: &Height, + client_id: &ClientId, + connection_id: &ConnectionId, +) -> Result { + let height = *height; + let client_id = client_id.clone(); + let connection_id = connection_id.clone(); + + chain + .with_blocking_chain_handle(move |chain_handle| { + let commitment_prefix = chain_handle + .query_commitment_prefix() + .map_err(BaseError::relayer)?; + + let (connection, _) = chain_handle + .query_connection( + QueryConnectionRequest { + connection_id: connection_id.clone(), + height: QueryHeight::Latest, + }, + IncludeProof::No, + ) + .map_err(BaseError::relayer)?; + + let versions = connection.versions().to_vec(); + let delay_period = connection.delay_period(); + + let (m_client_state, proofs) = chain_handle + .build_connection_proofs_and_client_state( + ConnectionMsgType::OpenTry, + &connection_id, + &client_id, + height, + ) + .map_err(BaseError::relayer)?; + + let any_client_state = m_client_state + .ok_or_else(|| BaseError::generic(eyre!("expect some client state")))?; + + let client_state = match any_client_state { + AnyClientState::Tendermint(client_state) => client_state, + _ => return Err(BaseError::generic(eyre!("expect tendermint client state")).into()), + }; + + let proof_client = proofs + .client_proof() + .ok_or_else(|| BaseError::generic(eyre!("expect non empty client proof")))? + .clone(); + + let proof_consensus = proofs + .consensus_proof() + .ok_or_else(|| BaseError::generic(eyre!("expect non empty consensus proof")))? + .clone(); + + let payload = CosmosConnectionOpenTryPayload { + commitment_prefix, + client_state, + versions, + delay_period, + update_height: proofs.height(), + proof_init: proofs.object_proof().clone(), + proof_client, + proof_consensus, + }; + + Ok(payload) + }) + .await +} + +pub async fn build_connection_open_ack_payload( + chain: &CosmosChain, + height: &Height, + client_id: &ClientId, + connection_id: &ConnectionId, +) -> Result { + let height = *height; + let client_id = client_id.clone(); + let connection_id = connection_id.clone(); + + chain + .with_blocking_chain_handle(move |chain_handle| { + let (connection, _) = chain_handle + .query_connection( + QueryConnectionRequest { + connection_id: connection_id.clone(), + height: QueryHeight::Latest, + }, + IncludeProof::No, + ) + .map_err(BaseError::relayer)?; + + let version = connection + .versions() + .iter() + .next() + .cloned() + .unwrap_or_default(); + + let (m_client_state, proofs) = chain_handle + .build_connection_proofs_and_client_state( + ConnectionMsgType::OpenAck, + &connection_id, + &client_id, + height, + ) + .map_err(BaseError::relayer)?; + + let any_client_state = m_client_state + .ok_or_else(|| BaseError::generic(eyre!("expect some client state")))?; + + let client_state = match any_client_state { + AnyClientState::Tendermint(client_state) => client_state, + _ => return Err(BaseError::generic(eyre!("expect tendermint client state")).into()), + }; + + let proof_client = proofs + .client_proof() + .ok_or_else(|| BaseError::generic(eyre!("expect non empty client proof")))? + .clone(); + + let proof_consensus = proofs + .consensus_proof() + .ok_or_else(|| BaseError::generic(eyre!("expect non empty consensus proof")))? + .clone(); + + let payload = CosmosConnectionOpenAckPayload { + client_state, + version, + update_height: proofs.height(), + proof_try: proofs.object_proof().clone(), + proof_client, + proof_consensus, + }; + + Ok(payload) + }) + .await +} + +pub async fn build_connection_open_confirm_payload( + chain: &CosmosChain, + height: &Height, + client_id: &ClientId, + connection_id: &ConnectionId, +) -> Result { + let height = *height; + let client_id = client_id.clone(); + let connection_id = connection_id.clone(); + + chain + .with_blocking_chain_handle(move |chain_handle| { + let (_, proofs) = chain_handle + .build_connection_proofs_and_client_state( + ConnectionMsgType::OpenConfirm, + &connection_id, + &client_id, + height, + ) + .map_err(BaseError::relayer)?; + + let update_height = proofs.height(); + let proof_ack = proofs.object_proof().clone(); + + let payload = CosmosConnectionOpenConfirmPayload { + update_height, + proof_ack, + }; + + Ok(payload) + }) + .await +} + +pub async fn build_connection_open_init_message( + chain: &CosmosChain, + client_id: &ClientId, + counterparty_client_id: &ClientId, + init_connection_options: &CosmosInitConnectionOptions, + counterparty_payload: CosmosConnectionOpenInitPayload, +) -> Result, Error> { + let client_id = client_id.clone(); + let counterparty_client_id = counterparty_client_id.clone(); + let counterparty_commitment_prefix = counterparty_payload.commitment_prefix; + let delay_period = init_connection_options.delay_period; + + chain + .with_blocking_chain_handle(move |chain_handle| { + let versions = chain_handle + .query_compatible_versions() + .map_err(BaseError::relayer)?; + + let version = versions.into_iter().next().unwrap_or_default(); + + let message = CosmosConnectionOpenInitMessage { + client_id, + counterparty_client_id, + counterparty_commitment_prefix, + version, + delay_period, + }; + + Ok(message.to_cosmos_message()) + }) + .await +} + +pub fn build_connection_open_try_message( + client_id: &ClientId, + counterparty_client_id: &ClientId, + counterparty_connection_id: &ConnectionId, + counterparty_payload: CosmosConnectionOpenTryPayload, +) -> Result, Error> { + let message = CosmosConnectionOpenTryMessage { + client_id: client_id.clone(), + counterparty_client_id: counterparty_client_id.clone(), + counterparty_connection_id: counterparty_connection_id.clone(), + counterparty_commitment_prefix: counterparty_payload.commitment_prefix.clone(), + counterparty_versions: counterparty_payload.versions, + delay_period: counterparty_payload.delay_period, + client_state: counterparty_payload.client_state.into(), + update_height: counterparty_payload.update_height, + proof_init: counterparty_payload.proof_init, + proof_client: counterparty_payload.proof_client, + proof_consensus: counterparty_payload.proof_consensus, + }; + + Ok(message.to_cosmos_message()) +} + +pub fn build_connection_open_ack_message( + connection_id: &ConnectionId, + counterparty_connection_id: &ConnectionId, + counterparty_payload: CosmosConnectionOpenAckPayload, +) -> Result, Error> { + let connection_id = connection_id.clone(); + let counterparty_connection_id = counterparty_connection_id.clone(); + + let message = CosmosConnectionOpenAckMessage { + connection_id, + counterparty_connection_id, + version: counterparty_payload.version, + client_state: counterparty_payload.client_state.into(), + update_height: counterparty_payload.update_height, + proof_try: counterparty_payload.proof_try, + proof_client: counterparty_payload.proof_client, + proof_consensus: counterparty_payload.proof_consensus, + }; + + Ok(message.to_cosmos_message()) +} + +pub fn build_connection_open_confirm_message( + connection_id: &ConnectionId, + counterparty_payload: CosmosConnectionOpenConfirmPayload, +) -> Result, Error> { + let message = CosmosConnectionOpenConfirmMessage { + connection_id: connection_id.clone(), + update_height: counterparty_payload.update_height, + proof_ack: counterparty_payload.proof_ack, + }; + + Ok(message.to_cosmos_message()) +} diff --git a/crates/relayer-cosmos/src/methods/consensus_state.rs b/crates/relayer-cosmos/src/methods/consensus_state.rs new file mode 100644 index 0000000000..656564be42 --- /dev/null +++ b/crates/relayer-cosmos/src/methods/consensus_state.rs @@ -0,0 +1,81 @@ +use eyre::eyre; +use ibc_relayer::chain::handle::ChainHandle; +use ibc_relayer::chain::requests::{ + IncludeProof, PageRequest, QueryConsensusStateHeightsRequest, QueryConsensusStateRequest, + QueryHeight, +}; +use ibc_relayer::consensus_state::AnyConsensusState; +use ibc_relayer_types::clients::ics07_tendermint::consensus_state::ConsensusState; +use ibc_relayer_types::core::ics24_host::identifier::ClientId; +use ibc_relayer_types::Height; + +use crate::contexts::chain::CosmosChain; +use crate::methods::runtime::HasBlockingChainHandle; +use crate::types::error::{BaseError, Error}; + +pub async fn query_consensus_state( + chain: &CosmosChain, + client_id: &ClientId, + height: &Height, +) -> Result { + let client_id = client_id.clone(); + let height = *height; + + chain + .with_blocking_chain_handle(move |chain_handle| { + let (any_consensus_state, _) = chain_handle + .query_consensus_state( + QueryConsensusStateRequest { + client_id: client_id.clone(), + consensus_height: height, + query_height: QueryHeight::Latest, + }, + IncludeProof::No, + ) + .map_err(BaseError::relayer)?; + + match any_consensus_state { + AnyConsensusState::Tendermint(consensus_state) => Ok(consensus_state), + _ => Err(BaseError::mismatch_consensus_state().into()), + } + }) + .await +} + +pub async fn find_consensus_state_height_before( + chain: &CosmosChain, + client_id: &ClientId, + target_height: &Height, +) -> Result { + let client_id = client_id.clone(); + let target_height = *target_height; + + chain + .with_blocking_chain_handle(move |chain_handle| { + let heights = { + let mut heights = chain_handle + .query_consensus_state_heights(QueryConsensusStateHeightsRequest { + client_id, + pagination: Some(PageRequest::all()), + }) + .map_err(BaseError::relayer)?; + + heights.sort_by_key(|&h| core::cmp::Reverse(h)); + + heights + }; + + let height = heights + .into_iter() + .find(|height| height < &target_height) + .ok_or_else(|| { + BaseError::generic(eyre!( + "no consensus state found that is smaller than target height {}", + target_height + )) + })?; + + Ok(height) + }) + .await +} diff --git a/crates/relayer-cosmos/src/methods/create_client.rs b/crates/relayer-cosmos/src/methods/create_client.rs new file mode 100644 index 0000000000..7ab5385e69 --- /dev/null +++ b/crates/relayer-cosmos/src/methods/create_client.rs @@ -0,0 +1,68 @@ +use alloc::sync::Arc; +use eyre::eyre; +use ibc_relayer::chain::client::ClientSettings; +use ibc_relayer::chain::handle::ChainHandle; +use ibc_relayer::client_state::AnyClientState; +use ibc_relayer::consensus_state::AnyConsensusState; + +use crate::contexts::chain::CosmosChain; +use crate::methods::runtime::HasBlockingChainHandle; +use crate::traits::message::{CosmosMessage, ToCosmosMessage}; +use crate::types::error::{BaseError, Error}; +use crate::types::messages::client::create::CosmosCreateClientMessage; +use crate::types::payloads::client::CosmosCreateClientPayload; + +pub async fn build_create_client_payload( + chain: &CosmosChain, + client_settings: &ClientSettings, +) -> Result { + let client_settings = client_settings.clone(); + + chain + .with_blocking_chain_handle(move |chain_handle| { + let height = chain_handle + .query_latest_height() + .map_err(BaseError::relayer)?; + + let any_client_state = chain_handle + .build_client_state(height, client_settings) + .map_err(BaseError::relayer)?; + + let client_state = match &any_client_state { + AnyClientState::Tendermint(client_state) => client_state.clone(), + _ => { + return Err(BaseError::generic(eyre!("expect Tendermint client state")).into()); + } + }; + + let any_consensus_state = chain_handle + .build_consensus_state(any_client_state.latest_height(), height, any_client_state) + .map_err(BaseError::relayer)?; + + let consensus_state = match any_consensus_state { + AnyConsensusState::Tendermint(consensus_state) => consensus_state, + _ => { + return Err( + BaseError::generic(eyre!("expect Tendermint consensus state")).into(), + ); + } + }; + + Ok(CosmosCreateClientPayload { + client_state, + consensus_state, + }) + }) + .await +} + +pub fn build_create_client_message( + payload: CosmosCreateClientPayload, +) -> Result, Error> { + let message = CosmosCreateClientMessage { + client_state: payload.client_state.into(), + consensus_state: payload.consensus_state.into(), + }; + + Ok(message.to_cosmos_message()) +} diff --git a/crates/relayer-cosmos/src/methods/encode.rs b/crates/relayer-cosmos/src/methods/encode.rs new file mode 100644 index 0000000000..393caccc0e --- /dev/null +++ b/crates/relayer-cosmos/src/methods/encode.rs @@ -0,0 +1,39 @@ +use ibc_proto::google::protobuf::Any; +use prost::{EncodeError, Message as ProstMessage}; + +pub fn encode_protobuf(message: &Message) -> Result, EncodeError> +where + Message: ProstMessage, +{ + let mut buf = Vec::new(); + Message::encode(message, &mut buf)?; + Ok(buf) +} + +pub fn encode_to_any(type_url: &str, message: &Message) -> Result +where + Message: ProstMessage, +{ + let encoded_message = encode_protobuf(message)?; + + let any_message = Any { + type_url: type_url.to_string(), + value: encoded_message, + }; + + Ok(any_message) +} + +pub fn encode_any_to_bytes( + type_url: &str, + message: &Message, +) -> Result, EncodeError> +where + Message: ProstMessage, +{ + let any = encode_to_any(type_url, message)?; + + let bytes = encode_protobuf(&any)?; + + Ok(bytes) +} diff --git a/crates/relayer-cosmos/src/methods/event.rs b/crates/relayer-cosmos/src/methods/event.rs new file mode 100644 index 0000000000..ca818b5a7f --- /dev/null +++ b/crates/relayer-cosmos/src/methods/event.rs @@ -0,0 +1,38 @@ +use alloc::sync::Arc; + +use ibc_relayer::event::extract_packet_and_write_ack_from_tx; +use tendermint::abci::Event as AbciEvent; + +use ibc_relayer_types::core::ics04_channel::events::{SendPacket, WriteAcknowledgement}; +use ibc_relayer_types::events::IbcEventType; + +pub fn try_extract_send_packet_event(event: &Arc) -> Option { + let event_type = event.kind.parse().ok()?; + + if let IbcEventType::SendPacket = event_type { + let (packet, _) = extract_packet_and_write_ack_from_tx(event).ok()?; + + let send_packet_event = SendPacket { packet }; + + Some(send_packet_event) + } else { + None + } +} + +pub fn try_extract_write_acknowledgement_event( + event: &Arc, +) -> Option { + if let IbcEventType::WriteAck = event.kind.parse().ok()? { + let (packet, write_ack) = extract_packet_and_write_ack_from_tx(event).ok()?; + + let ack = WriteAcknowledgement { + packet, + ack: write_ack, + }; + + Some(ack) + } else { + None + } +} diff --git a/crates/relayer-cosmos/src/methods/mod.rs b/crates/relayer-cosmos/src/methods/mod.rs new file mode 100644 index 0000000000..b786c3a058 --- /dev/null +++ b/crates/relayer-cosmos/src/methods/mod.rs @@ -0,0 +1,12 @@ +pub mod chain; +pub mod channel; +pub mod client_state; +pub mod connection; +pub mod consensus_state; +pub mod create_client; +pub mod encode; +pub mod event; +pub mod packet; +pub mod runtime; +pub mod unreceived_packet; +pub mod update_client; diff --git a/crates/relayer-cosmos/src/methods/packet.rs b/crates/relayer-cosmos/src/methods/packet.rs new file mode 100644 index 0000000000..1bb2ede032 --- /dev/null +++ b/crates/relayer-cosmos/src/methods/packet.rs @@ -0,0 +1,222 @@ +use alloc::sync::Arc; +use ibc_relayer::chain::handle::ChainHandle; +use ibc_relayer::chain::requests::{Qualified, QueryUnreceivedPacketsRequest}; +use ibc_relayer::link::packet_events::query_write_ack_events; +use ibc_relayer::path::PathIdentifiers; +use ibc_relayer_types::core::ics04_channel::events::WriteAcknowledgement; +use ibc_relayer_types::core::ics04_channel::packet::{Packet, PacketMsgType, Sequence}; +use ibc_relayer_types::core::ics24_host::identifier::{ChannelId, PortId}; +use ibc_relayer_types::events::IbcEvent; +use ibc_relayer_types::Height; + +use crate::contexts::chain::CosmosChain; +use crate::methods::runtime::HasBlockingChainHandle; +use crate::traits::message::{CosmosMessage, ToCosmosMessage}; +use crate::types::error::{BaseError, Error}; +use crate::types::messages::packet::ack::CosmosAckPacketMessage; +use crate::types::messages::packet::receive::CosmosReceivePacketMessage; +use crate::types::messages::packet::timeout::CosmosTimeoutPacketMessage; +use crate::types::payloads::packet::{ + CosmosAckPacketPayload, CosmosReceivePacketPayload, CosmosTimeoutUnorderedPacketPayload, +}; + +pub async fn build_receive_packet_payload( + chain: &CosmosChain, + height: &Height, + packet: &Packet, +) -> Result { + let height = *height; + let packet = packet.clone(); + + chain + .with_blocking_chain_handle(move |chain_handle| { + let proofs = chain_handle + .build_packet_proofs( + PacketMsgType::Recv, + &packet.source_port, + &packet.source_channel, + packet.sequence, + height, + ) + .map_err(BaseError::relayer)?; + + Ok(CosmosReceivePacketPayload { + update_height: proofs.height(), + proof_commitment: proofs.object_proof().clone(), + }) + }) + .await +} + +pub fn build_receive_packet_message( + packet: &Packet, + payload: CosmosReceivePacketPayload, +) -> Result, Error> { + let message = CosmosReceivePacketMessage { + packet: packet.clone(), + update_height: payload.update_height, + proof_commitment: payload.proof_commitment, + }; + + Ok(message.to_cosmos_message()) +} + +pub async fn build_ack_packet_payload( + chain: &CosmosChain, + height: &Height, + packet: &Packet, + ack: &WriteAcknowledgement, +) -> Result { + let height = *height; + let packet = packet.clone(); + let ack = ack.clone(); + + chain + .with_blocking_chain_handle(move |chain_handle| { + let proofs = chain_handle + .build_packet_proofs( + PacketMsgType::Ack, + &packet.destination_port, + &packet.destination_channel, + packet.sequence, + height, + ) + .map_err(BaseError::relayer)?; + + let ack = ack.ack; + + Ok(CosmosAckPacketPayload { + ack, + update_height: proofs.height(), + proof_acked: proofs.object_proof().clone(), + }) + }) + .await +} + +pub fn build_ack_packet_message( + packet: &Packet, + payload: CosmosAckPacketPayload, +) -> Result, Error> { + let message = CosmosAckPacketMessage { + packet: packet.clone(), + acknowledgement: payload.ack, + update_height: payload.update_height, + proof_acked: payload.proof_acked, + }; + + Ok(message.to_cosmos_message()) +} + +pub async fn build_timeout_unordered_packet_payload( + chain: &CosmosChain, + height: &Height, + packet: &Packet, +) -> Result { + let height = *height; + let packet = packet.clone(); + + chain + .with_blocking_chain_handle(move |chain_handle| { + let proofs = chain_handle + .build_packet_proofs( + PacketMsgType::TimeoutUnordered, + &packet.destination_port, + &packet.destination_channel, + packet.sequence, + height, + ) + .map_err(BaseError::relayer)?; + + Ok(CosmosTimeoutUnorderedPacketPayload { + update_height: proofs.height(), + proof_unreceived: proofs.object_proof().clone(), + }) + }) + .await +} + +pub fn build_timeout_unordered_packet_message( + packet: &Packet, + payload: CosmosTimeoutUnorderedPacketPayload, +) -> Result, Error> { + let message = CosmosTimeoutPacketMessage { + next_sequence_recv: packet.sequence, + packet: packet.clone(), + update_height: payload.update_height, + proof_unreceived: payload.proof_unreceived, + }; + + Ok(message.to_cosmos_message()) +} + +pub async fn query_is_packet_received( + chain: &CosmosChain, + port_id: &PortId, + channel_id: &ChannelId, + sequence: &Sequence, +) -> Result { + let port_id = port_id.clone(); + let channel_id = channel_id.clone(); + let sequence = *sequence; + + chain + .with_blocking_chain_handle(move |chain_handle| { + let unreceived_packet = chain_handle + .query_unreceived_packets(QueryUnreceivedPacketsRequest { + port_id: port_id.clone(), + channel_id: channel_id.clone(), + packet_commitment_sequences: vec![sequence], + }) + .map_err(BaseError::relayer)?; + + let is_packet_received = unreceived_packet.is_empty(); + + Ok(is_packet_received) + }) + .await +} + +pub async fn query_write_acknowledgement_event( + chain: &CosmosChain, + packet: &Packet, +) -> Result, Error> { + let packet = packet.clone(); + + chain + .with_blocking_chain_handle(move |chain_handle| { + let status = chain_handle + .query_application_status() + .map_err(BaseError::relayer)?; + + let query_height = Qualified::Equal(status.height); + + let path_ident = PathIdentifiers { + port_id: packet.destination_port.clone(), + channel_id: packet.destination_channel.clone(), + counterparty_port_id: packet.source_port.clone(), + counterparty_channel_id: packet.source_channel.clone(), + }; + + let ibc_events = query_write_ack_events( + &chain_handle, + &path_ident, + &[packet.sequence], + query_height, + ) + .map_err(BaseError::relayer)?; + + let write_ack = ibc_events.into_iter().find_map(|event_with_height| { + let event = event_with_height.event; + + if let IbcEvent::WriteAcknowledgement(write_ack) = event { + Some(write_ack) + } else { + None + } + }); + + Ok(write_ack) + }) + .await +} diff --git a/crates/relayer-cosmos/src/methods/runtime.rs b/crates/relayer-cosmos/src/methods/runtime.rs new file mode 100644 index 0000000000..ab33bf6d0f --- /dev/null +++ b/crates/relayer-cosmos/src/methods/runtime.rs @@ -0,0 +1,42 @@ +use async_trait::async_trait; +use ibc_relayer::chain::handle::ChainHandle; +use ibc_relayer_components::core::traits::sync::Async; + +use crate::contexts::chain::CosmosChain; +use crate::types::error::{BaseError, Error}; + +#[async_trait] +pub trait HasBlockingChainHandle: Async { + type ChainHandle: ChainHandle; + + async fn with_blocking_chain_handle( + &self, + cont: impl FnOnce(Self::ChainHandle) -> Result + Send + 'static, + ) -> Result + where + R: Send + 'static; +} + +#[async_trait] +impl HasBlockingChainHandle for CosmosChain +where + Chain: ChainHandle, +{ + type ChainHandle = Chain; + + async fn with_blocking_chain_handle( + &self, + cont: impl FnOnce(Chain) -> Result + Send + 'static, + ) -> Result + where + R: Send + 'static, + { + let chain_handle = self.handle.clone(); + + self.runtime + .runtime + .spawn_blocking(move || cont(chain_handle)) + .await + .map_err(BaseError::join)? + } +} diff --git a/crates/relayer-cosmos/src/methods/unreceived_packet.rs b/crates/relayer-cosmos/src/methods/unreceived_packet.rs new file mode 100644 index 0000000000..2ae1d066ef --- /dev/null +++ b/crates/relayer-cosmos/src/methods/unreceived_packet.rs @@ -0,0 +1,175 @@ +use alloc::sync::Arc; +use futures::stream::{self, StreamExt, TryStreamExt}; +use tonic::Request; + +use ibc_proto::ibc::core::channel::v1::query_client::QueryClient as ChannelQueryClient; +use ibc_relayer::chain::cosmos::query::packet_query; +use ibc_relayer::chain::handle::ChainHandle; +use ibc_relayer::chain::requests::{ + Qualified, QueryHeight, QueryPacketCommitmentsRequest, QueryPacketEventDataRequest, + QueryUnreceivedPacketsRequest, +}; +use ibc_relayer_all_in_one::one_for_all::traits::chain::OfaChain; +use ibc_relayer_types::core::ics04_channel::packet::Packet; +use ibc_relayer_types::core::ics04_channel::packet::Sequence; +use ibc_relayer_types::core::ics24_host::identifier::{ChannelId, PortId}; +use ibc_relayer_types::events::WithBlockDataType; +use ibc_relayer_types::Height; +use tendermint_rpc::{Client, Order}; + +use crate::contexts::chain::CosmosChain; +use crate::methods::event::try_extract_send_packet_event; +use crate::types::error::{BaseError, Error}; + +pub async fn query_packet_commitments( + chain: &CosmosChain, + channel_id: &ChannelId, + port_id: &PortId, +) -> Result<(Vec, Height), Error> { + let mut client = ChannelQueryClient::connect(chain.tx_context.tx_config.grpc_address.clone()) + .await + .map_err(BaseError::grpc_transport)?; + + let raw_request = QueryPacketCommitmentsRequest { + port_id: port_id.clone(), + channel_id: channel_id.clone(), + pagination: None, + }; + + let request = Request::new(raw_request.into()); + + let response = client + .packet_commitments(request) + .await + .map_err(|e| BaseError::grpc_status(e, "query_packet_commitments".to_owned()))? + .into_inner(); + + let commitment_sequences: Vec = response + .commitments + .into_iter() + .map(|packet_state| packet_state.sequence.into()) + .collect(); + + let raw_height = response + .height + .ok_or_else(|| BaseError::missing_height("query_packet_commitments".to_owned()))?; + let height = raw_height.try_into().map_err(BaseError::ics02)?; + + Ok((commitment_sequences, height)) +} + +/// Given a list of counterparty commitment sequences, +/// return a filtered list of sequences which the chain +/// has not received the packet from the counterparty chain. +pub async fn query_unreceived_packet_sequences( + chain: &CosmosChain, + channel_id: &ChannelId, + port_id: &PortId, + sequences: &[Sequence], +) -> Result, Error> { + let mut client = ChannelQueryClient::connect(chain.tx_context.tx_config.grpc_address.clone()) + .await + .map_err(BaseError::grpc_transport)?; + + let raw_request = QueryUnreceivedPacketsRequest { + port_id: port_id.clone(), + channel_id: channel_id.clone(), + packet_commitment_sequences: sequences.to_vec(), + }; + + let request = Request::new(raw_request.into()); + + let response = client + .unreceived_packets(request) + .await + .map_err(|e| BaseError::grpc_status(e, "unreceived_packets".to_owned()))? + .into_inner(); + + let response_sequences = response.sequences.into_iter().map(|s| s.into()).collect(); + Ok(response_sequences) +} + +/// Given a single sequence, a channel and port will query the outgoing +/// packets if it hasn't been relayed. +async fn query_send_packet_from_sequence( + chain: &CosmosChain, + channel_id: &ChannelId, + port_id: &PortId, + counterparty_channel_id: &ChannelId, + counterparty_port_id: &PortId, + sequence: &Sequence, + height: &Height, +) -> Result { + // The unreceived packet are queried from the source chain, so the destination + // channel id and port id are the counterparty channel id and counterparty port id. + let request = QueryPacketEventDataRequest { + event_id: WithBlockDataType::SendPacket, + source_channel_id: channel_id.clone(), + source_port_id: port_id.clone(), + destination_channel_id: counterparty_channel_id.clone(), + destination_port_id: counterparty_port_id.clone(), + sequences: vec![*sequence], + height: Qualified::SmallerEqual(QueryHeight::Specific(*height)), + }; + let mut events = vec![]; + let query = packet_query(&request, *sequence); + let response = chain + .tx_context + .rpc_client + .tx_search(query, false, 1, 10, Order::Descending) + .await + .map_err(BaseError::tendermint_rpc)?; + + for tx in response.txs.iter() { + let mut event = tx + .tx_result + .events + .iter() + .map(|event| Arc::new(event.clone())) + .collect(); + events.append(&mut event); + } + + let send_packets: Vec = events + .iter() + .filter_map(try_extract_send_packet_event) + .map(|event| { + as OfaChain>::extract_packet_from_send_packet_event(&event) + }) + .collect(); + + let send_packet = send_packets + .first() + .ok_or_else(BaseError::missing_send_packet)?; + + Ok(send_packet.clone()) +} + +/// Given a list of sequences, a channel and port will query a list of outgoing +/// packets which have not been relayed. +pub async fn query_send_packets_from_sequences( + chain: &CosmosChain, + channel_id: &ChannelId, + port_id: &PortId, + counterparty_channel_id: &ChannelId, + counterparty_port_id: &PortId, + sequences: &[Sequence], + height: &Height, +) -> Result, Error> { + let send_packets = stream::iter(sequences) + .then(|sequence| { + query_send_packet_from_sequence( + chain, + channel_id, + port_id, + counterparty_channel_id, + counterparty_port_id, + sequence, + height, + ) + }) + .try_collect::>() + .await?; + + Ok(send_packets) +} diff --git a/crates/relayer-cosmos/src/methods/update_client.rs b/crates/relayer-cosmos/src/methods/update_client.rs new file mode 100644 index 0000000000..82872340f1 --- /dev/null +++ b/crates/relayer-cosmos/src/methods/update_client.rs @@ -0,0 +1,69 @@ +use alloc::sync::Arc; +use core::iter; +use eyre::eyre; +use ibc_relayer::chain::handle::ChainHandle; +use ibc_relayer::client_state::AnyClientState; +use ibc_relayer::light_client::AnyHeader; +use ibc_relayer_types::clients::ics07_tendermint::client_state::ClientState; +use ibc_relayer_types::clients::ics07_tendermint::header::Header as TendermintHeader; +use ibc_relayer_types::core::ics24_host::identifier::ClientId; +use ibc_relayer_types::Height; + +use crate::contexts::chain::CosmosChain; +use crate::methods::runtime::HasBlockingChainHandle; +use crate::traits::message::{CosmosMessage, ToCosmosMessage}; +use crate::types::error::{BaseError, Error}; +use crate::types::messages::client::update::CosmosUpdateClientMessage; +use crate::types::payloads::client::CosmosUpdateClientPayload; + +pub async fn build_update_client_payload( + chain: &CosmosChain, + trusted_height: &Height, + target_height: &Height, + client_state: ClientState, +) -> Result { + let trusted_height = *trusted_height; + let target_height = *target_height; + + chain + .with_blocking_chain_handle(move |chain_handle| { + let (header, support) = chain_handle + .build_header( + trusted_height, + target_height, + AnyClientState::Tendermint(client_state), + ) + .map_err(BaseError::relayer)?; + + let headers = iter::once(header) + .chain(support.into_iter()) + .map(|header| match header { + AnyHeader::Tendermint(header) => Ok(header), + _ => Err(BaseError::generic(eyre!("expect tendermint header")).into()), + }) + .collect::, Error>>()?; + + Ok(CosmosUpdateClientPayload { headers }) + }) + .await +} + +pub fn build_update_client_message( + client_id: &ClientId, + payload: CosmosUpdateClientPayload, +) -> Result>, Error> { + let messages = payload + .headers + .into_iter() + .map(|header| { + let message = CosmosUpdateClientMessage { + client_id: client_id.clone(), + header: header.into(), + }; + + message.to_cosmos_message() + }) + .collect(); + + Ok(messages) +} diff --git a/crates/relayer-cosmos/src/traits/message.rs b/crates/relayer-cosmos/src/traits/message.rs new file mode 100644 index 0000000000..e1905fea72 --- /dev/null +++ b/crates/relayer-cosmos/src/traits/message.rs @@ -0,0 +1,34 @@ +use alloc::sync::Arc; +use ibc_proto::google::protobuf::Any; +use ibc_relayer_types::signer::Signer; +use ibc_relayer_types::Height; +use prost::EncodeError; + +pub trait CosmosMessage: Send + Sync + 'static { + fn counterparty_message_height_for_update_client(&self) -> Option { + None + } + + fn trusted_height(&self) -> Option { + None + } + + fn encode_protobuf(&self, signer: &Signer) -> Result; +} + +pub trait ToCosmosMessage { + fn to_cosmos_message(self) -> Arc; +} + +impl ToCosmosMessage for Message +where + Message: CosmosMessage, +{ + fn to_cosmos_message(self) -> Arc { + Arc::new(self) + } +} + +pub fn wrap_cosmos_message(message: Message) -> Arc { + Arc::new(message) +} diff --git a/crates/relayer-cosmos/src/traits/mod.rs b/crates/relayer-cosmos/src/traits/mod.rs new file mode 100644 index 0000000000..e216a50180 --- /dev/null +++ b/crates/relayer-cosmos/src/traits/mod.rs @@ -0,0 +1 @@ +pub mod message; diff --git a/crates/relayer-cosmos/src/types/batch.rs b/crates/relayer-cosmos/src/types/batch.rs new file mode 100644 index 0000000000..ea59d4ca98 --- /dev/null +++ b/crates/relayer-cosmos/src/types/batch.rs @@ -0,0 +1,16 @@ +use alloc::sync::Arc; +use tendermint::abci::Event as AbciEvent; +use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender}; +use tokio::sync::oneshot::Sender as SenderOnce; + +use crate::traits::message::CosmosMessage; +use crate::types::error::Error; + +pub type CosmosBatchPayload = ( + Vec>, + SenderOnce>>, Error>>, +); + +pub type CosmosBatchSender = UnboundedSender; + +pub type CosmosBatchReceiver = UnboundedReceiver; diff --git a/crates/relayer-cosmos/src/types/channel.rs b/crates/relayer-cosmos/src/types/channel.rs new file mode 100644 index 0000000000..8ccd399b06 --- /dev/null +++ b/crates/relayer-cosmos/src/types/channel.rs @@ -0,0 +1,10 @@ +use ibc_relayer_types::core::ics04_channel::channel::Ordering; +use ibc_relayer_types::core::ics04_channel::version::Version; +use ibc_relayer_types::core::ics24_host::identifier::ConnectionId; + +#[derive(Default)] +pub struct CosmosInitChannelOptions { + pub ordering: Ordering, + pub connection_hops: Vec, + pub channel_version: Version, +} diff --git a/crates/relayer-cosmos/src/types/connection.rs b/crates/relayer-cosmos/src/types/connection.rs new file mode 100644 index 0000000000..844d6f3959 --- /dev/null +++ b/crates/relayer-cosmos/src/types/connection.rs @@ -0,0 +1,9 @@ +use core::time::Duration; + +use ibc_relayer_types::core::ics03_connection::version::Version; + +#[derive(Default)] +pub struct CosmosInitConnectionOptions { + pub delay_period: Duration, + pub connection_version: Version, +} diff --git a/crates/relayer-cosmos/src/types/error.rs b/crates/relayer-cosmos/src/types/error.rs new file mode 100644 index 0000000000..20d1c08b38 --- /dev/null +++ b/crates/relayer-cosmos/src/types/error.rs @@ -0,0 +1,112 @@ +use alloc::sync::Arc; + +use eyre::Report; +use flex_error::{define_error, TraceError}; +use ibc_relayer::error::Error as RelayerError; +use ibc_relayer::foreign_client::ForeignClientError; +use ibc_relayer::spawn::SpawnError; +use ibc_relayer::supervisor::error::Error as SupervisorError; +use ibc_relayer_runtime::types::error::Error as TokioError; +use ibc_relayer_types::core::ics02_client::error::Error as ClientError; +use ibc_relayer_types::core::ics04_channel::error::Error as ChannelError; +use ibc_relayer_types::core::ics23_commitment; +use ibc_relayer_types::proofs::ProofError; +use prost::EncodeError; +use tendermint::Hash as TxHash; +use tendermint_rpc::endpoint::broadcast::tx_sync::Response; +use tendermint_rpc::Error as TendermintRpcError; +use tokio::task::JoinError; +use tonic::transport::Error as TransportError; +use tonic::Status as GrpcStatus; + +pub type Error = Arc; + +define_error! { + #[derive(Debug)] + BaseError { + Generic + [ TraceError ] + | _ | { "generic error" }, + + Tokio + [ TokioError ] + | _ | { "tokio runtime error" }, + + Channel + [ ChannelError ] + | _ | { "channel error" }, + + Relayer + [ RelayerError ] + | _ | { "ibc-relayer error" }, + + ForeignClient + [ ForeignClientError ] + | _ | { "foreign client error" }, + + Spawn + [ SpawnError ] + | _ | { "failed to spawn chain runtime" }, + + Supervisor + [ SupervisorError ] + | _ | { "supervisor error" }, + + Encode + [ TraceError ] + | _ | { "protobuf encode error" }, + + Ics23 + [ ics23_commitment::error::Error ] + | _ | { "ICS23 error" }, + + Proofs + [ ProofError ] + | _ | { "proofs error" }, + + TendermintRpc + [ TendermintRpcError ] + | _ | { "tendermint rpc error" }, + + MismatchConsensusState + | _ | { "consensus state of a cosmos chain on the counterparty chain must be a tendermint consensus state" }, + + MismatchEventType + { expected: String, actual: String } + | e | { format_args!("mismatch event type, expected: {}, actual: {}", e.expected, e.actual) }, + + TxNoResponse + { tx_hash: TxHash } + | e | { format_args!("failed to receive tx response for tx hash: {}", e.tx_hash) }, + + MissingSimulateGasInfo + | _ | { "missing gas info returned from send_tx_simulate" }, + + MissingSendPacket + | _ | { "missing send packet" }, + + CheckTx + { response: Response } + | e | { format_args!("check tx error: {:?}", e.response) }, + + Join + [ TraceError ] + | _ | { "error joining tokio tasks" }, + + GrpcTransport + [ TraceError ] + |_| { "error in underlying transport when making gRPC call" }, + + GrpcStatus + { status: GrpcStatus, query: String } + |e| { format!("gRPC call `{}` failed with status: {1}", e.query, e.status) }, + + MissingHeight + { query: String } + | e | { format_args!("height from query `{}` is missing", e.query) }, + + Ics02 + [ ClientError ] + |e| { format!("ICS 02 error: {}", e.source) }, + } +} diff --git a/crates/relayer-cosmos/src/types/events/channel.rs b/crates/relayer-cosmos/src/types/events/channel.rs new file mode 100644 index 0000000000..217967614b --- /dev/null +++ b/crates/relayer-cosmos/src/types/events/channel.rs @@ -0,0 +1,8 @@ +use ibc_relayer_types::core::ics24_host::identifier::ChannelId; + +pub struct CosmosChannelOpenInitEvent { + pub channel_id: ChannelId, +} +pub struct CosmosChannelOpenTryEvent { + pub channel_id: ChannelId, +} diff --git a/crates/relayer-cosmos/src/types/events/client.rs b/crates/relayer-cosmos/src/types/events/client.rs new file mode 100644 index 0000000000..986e5daa4a --- /dev/null +++ b/crates/relayer-cosmos/src/types/events/client.rs @@ -0,0 +1,5 @@ +use ibc_relayer_types::core::ics24_host::identifier::ClientId; + +pub struct CosmosCreateClientEvent { + pub client_id: ClientId, +} diff --git a/crates/relayer-cosmos/src/types/events/connection.rs b/crates/relayer-cosmos/src/types/events/connection.rs new file mode 100644 index 0000000000..4708a10722 --- /dev/null +++ b/crates/relayer-cosmos/src/types/events/connection.rs @@ -0,0 +1,9 @@ +use ibc_relayer_types::core::ics24_host::identifier::ConnectionId; + +pub struct CosmosConnectionOpenInitEvent { + pub connection_id: ConnectionId, +} + +pub struct CosmosConnectionOpenTryEvent { + pub connection_id: ConnectionId, +} diff --git a/crates/relayer-cosmos/src/types/events/mod.rs b/crates/relayer-cosmos/src/types/events/mod.rs new file mode 100644 index 0000000000..210a35c8ac --- /dev/null +++ b/crates/relayer-cosmos/src/types/events/mod.rs @@ -0,0 +1,3 @@ +pub mod channel; +pub mod client; +pub mod connection; diff --git a/crates/relayer-cosmos/src/types/messages/channel/mod.rs b/crates/relayer-cosmos/src/types/messages/channel/mod.rs new file mode 100644 index 0000000000..8246b9ab2a --- /dev/null +++ b/crates/relayer-cosmos/src/types/messages/channel/mod.rs @@ -0,0 +1,4 @@ +pub mod open_ack; +pub mod open_confirm; +pub mod open_init; +pub mod open_try; diff --git a/crates/relayer-cosmos/src/types/messages/channel/open_ack.rs b/crates/relayer-cosmos/src/types/messages/channel/open_ack.rs new file mode 100644 index 0000000000..25326e3ca6 --- /dev/null +++ b/crates/relayer-cosmos/src/types/messages/channel/open_ack.rs @@ -0,0 +1,42 @@ +use ibc_proto::google::protobuf::Any; +use ibc_proto::ibc::core::channel::v1::MsgChannelOpenAck as ProtoMsgChannelOpenAck; +use ibc_relayer_types::core::ics04_channel::version::Version; +use ibc_relayer_types::core::ics23_commitment::commitment::CommitmentProofBytes; +use ibc_relayer_types::core::ics24_host::identifier::{ChannelId, PortId}; +use ibc_relayer_types::signer::Signer; +use ibc_relayer_types::Height; +use prost::EncodeError; + +use crate::methods::encode::encode_to_any; +use crate::traits::message::CosmosMessage; + +const TYPE_URL: &str = "/ibc.core.channel.v1.MsgChannelOpenAck"; + +pub struct CosmosChannelOpenAckMessage { + pub port_id: PortId, + pub channel_id: ChannelId, + pub counterparty_channel_id: ChannelId, + pub counterparty_version: Version, + pub update_height: Height, + pub proof_try: CommitmentProofBytes, +} + +impl CosmosMessage for CosmosChannelOpenAckMessage { + fn counterparty_message_height_for_update_client(&self) -> Option { + Some(self.update_height) + } + + fn encode_protobuf(&self, signer: &Signer) -> Result { + let proto_message = ProtoMsgChannelOpenAck { + port_id: self.port_id.to_string(), + channel_id: self.channel_id.to_string(), + counterparty_channel_id: self.counterparty_channel_id.to_string(), + counterparty_version: self.counterparty_version.to_string(), + proof_height: Some(self.update_height.into()), + proof_try: self.proof_try.clone().into(), + signer: signer.to_string(), + }; + + encode_to_any(TYPE_URL, &proto_message) + } +} diff --git a/crates/relayer-cosmos/src/types/messages/channel/open_confirm.rs b/crates/relayer-cosmos/src/types/messages/channel/open_confirm.rs new file mode 100644 index 0000000000..2afd124b11 --- /dev/null +++ b/crates/relayer-cosmos/src/types/messages/channel/open_confirm.rs @@ -0,0 +1,37 @@ +use ibc_proto::google::protobuf::Any; +use ibc_proto::ibc::core::channel::v1::MsgChannelOpenConfirm as ProtoMsgChannelOpenConfirm; +use ibc_relayer_types::core::ics23_commitment::commitment::CommitmentProofBytes; +use ibc_relayer_types::core::ics24_host::identifier::{ChannelId, PortId}; +use ibc_relayer_types::signer::Signer; +use ibc_relayer_types::Height; +use prost::EncodeError; + +use crate::methods::encode::encode_to_any; +use crate::traits::message::CosmosMessage; + +const TYPE_URL: &str = "/ibc.core.channel.v1.MsgChannelOpenConfirm"; + +pub struct CosmosChannelOpenConfirmMessage { + pub port_id: PortId, + pub channel_id: ChannelId, + pub update_height: Height, + pub proof_ack: CommitmentProofBytes, +} + +impl CosmosMessage for CosmosChannelOpenConfirmMessage { + fn counterparty_message_height_for_update_client(&self) -> Option { + Some(self.update_height) + } + + fn encode_protobuf(&self, signer: &Signer) -> Result { + let proto_message = ProtoMsgChannelOpenConfirm { + port_id: self.port_id.to_string(), + channel_id: self.channel_id.to_string(), + proof_height: Some(self.update_height.into()), + proof_ack: self.proof_ack.clone().into(), + signer: signer.to_string(), + }; + + encode_to_any(TYPE_URL, &proto_message) + } +} diff --git a/crates/relayer-cosmos/src/types/messages/channel/open_init.rs b/crates/relayer-cosmos/src/types/messages/channel/open_init.rs new file mode 100644 index 0000000000..30aba37ae0 --- /dev/null +++ b/crates/relayer-cosmos/src/types/messages/channel/open_init.rs @@ -0,0 +1,28 @@ +use ibc_proto::google::protobuf::Any; +use ibc_proto::ibc::core::channel::v1::MsgChannelOpenInit as ProtoMsgChannelOpenInit; +use ibc_relayer_types::core::ics04_channel::channel::ChannelEnd; +use ibc_relayer_types::core::ics24_host::identifier::PortId; +use ibc_relayer_types::signer::Signer; +use prost::EncodeError; + +use crate::methods::encode::encode_to_any; +use crate::traits::message::CosmosMessage; + +const TYPE_URL: &str = "/ibc.core.channel.v1.MsgChannelOpenInit"; + +pub struct CosmosChannelOpenInitMessage { + pub port_id: PortId, + pub channel: ChannelEnd, +} + +impl CosmosMessage for CosmosChannelOpenInitMessage { + fn encode_protobuf(&self, signer: &Signer) -> Result { + let proto_message = ProtoMsgChannelOpenInit { + port_id: self.port_id.to_string(), + channel: Some(self.channel.clone().into()), + signer: signer.to_string(), + }; + + encode_to_any(TYPE_URL, &proto_message) + } +} diff --git a/crates/relayer-cosmos/src/types/messages/channel/open_try.rs b/crates/relayer-cosmos/src/types/messages/channel/open_try.rs new file mode 100644 index 0000000000..b09783d175 --- /dev/null +++ b/crates/relayer-cosmos/src/types/messages/channel/open_try.rs @@ -0,0 +1,43 @@ +use ibc_proto::google::protobuf::Any; +use ibc_proto::ibc::core::channel::v1::MsgChannelOpenTry as ProtoMsgChannelOpenTry; +use ibc_relayer_types::core::ics04_channel::channel::ChannelEnd; +use ibc_relayer_types::core::ics04_channel::version::Version; +use ibc_relayer_types::core::ics23_commitment::commitment::CommitmentProofBytes; +use ibc_relayer_types::core::ics24_host::identifier::PortId; +use ibc_relayer_types::signer::Signer; +use ibc_relayer_types::Height; +use prost::EncodeError; + +use crate::methods::encode::encode_to_any; +use crate::traits::message::CosmosMessage; + +const TYPE_URL: &str = "/ibc.core.channel.v1.MsgChannelOpenTry"; + +pub struct CosmosChannelOpenTryMessage { + pub port_id: PortId, + pub channel: ChannelEnd, + pub counterparty_version: Version, + pub update_height: Height, + pub proof_init: CommitmentProofBytes, +} + +impl CosmosMessage for CosmosChannelOpenTryMessage { + fn counterparty_message_height_for_update_client(&self) -> Option { + Some(self.update_height) + } + + fn encode_protobuf(&self, signer: &Signer) -> Result { + #[allow(deprecated)] + let proto_message = ProtoMsgChannelOpenTry { + port_id: self.port_id.to_string(), + channel: Some(self.channel.clone().into()), + counterparty_version: self.counterparty_version.to_string(), + proof_height: Some(self.update_height.into()), + proof_init: self.proof_init.clone().into(), + signer: signer.to_string(), + previous_channel_id: "".to_string(), + }; + + encode_to_any(TYPE_URL, &proto_message) + } +} diff --git a/crates/relayer-cosmos/src/types/messages/client/create.rs b/crates/relayer-cosmos/src/types/messages/client/create.rs new file mode 100644 index 0000000000..30bb542f3a --- /dev/null +++ b/crates/relayer-cosmos/src/types/messages/client/create.rs @@ -0,0 +1,26 @@ +use ibc_proto::google::protobuf::Any; +use ibc_proto::ibc::core::client::v1::MsgCreateClient as ProtoMsgCreateClient; +use ibc_relayer_types::signer::Signer; +use prost::EncodeError; + +use crate::methods::encode::encode_to_any; +use crate::traits::message::CosmosMessage; + +const TYPE_URL: &str = "/ibc.core.client.v1.MsgCreateClient"; + +pub struct CosmosCreateClientMessage { + pub client_state: Any, + pub consensus_state: Any, +} + +impl CosmosMessage for CosmosCreateClientMessage { + fn encode_protobuf(&self, signer: &Signer) -> Result { + let proto_message = ProtoMsgCreateClient { + client_state: Some(self.client_state.clone()), + consensus_state: Some(self.consensus_state.clone()), + signer: signer.to_string(), + }; + + encode_to_any(TYPE_URL, &proto_message) + } +} diff --git a/crates/relayer-cosmos/src/types/messages/client/mod.rs b/crates/relayer-cosmos/src/types/messages/client/mod.rs new file mode 100644 index 0000000000..82a5ce72ba --- /dev/null +++ b/crates/relayer-cosmos/src/types/messages/client/mod.rs @@ -0,0 +1,2 @@ +pub mod create; +pub mod update; diff --git a/crates/relayer-cosmos/src/types/messages/client/update.rs b/crates/relayer-cosmos/src/types/messages/client/update.rs new file mode 100644 index 0000000000..dabd324f9e --- /dev/null +++ b/crates/relayer-cosmos/src/types/messages/client/update.rs @@ -0,0 +1,27 @@ +use ibc_proto::google::protobuf::Any; +use ibc_proto::ibc::core::client::v1::MsgUpdateClient as ProtoMsgUpdateClient; +use ibc_relayer_types::core::ics24_host::identifier::ClientId; +use ibc_relayer_types::signer::Signer; +use prost::EncodeError; + +use crate::methods::encode::encode_to_any; +use crate::traits::message::CosmosMessage; + +pub const TYPE_URL: &str = "/ibc.core.client.v1.MsgUpdateClient"; + +pub struct CosmosUpdateClientMessage { + pub client_id: ClientId, + pub header: Any, +} + +impl CosmosMessage for CosmosUpdateClientMessage { + fn encode_protobuf(&self, signer: &Signer) -> Result { + let proto_message = ProtoMsgUpdateClient { + client_id: self.client_id.to_string(), + client_message: Some(self.header.clone()), + signer: signer.to_string(), + }; + + encode_to_any(TYPE_URL, &proto_message) + } +} diff --git a/crates/relayer-cosmos/src/types/messages/connection/mod.rs b/crates/relayer-cosmos/src/types/messages/connection/mod.rs new file mode 100644 index 0000000000..8246b9ab2a --- /dev/null +++ b/crates/relayer-cosmos/src/types/messages/connection/mod.rs @@ -0,0 +1,4 @@ +pub mod open_ack; +pub mod open_confirm; +pub mod open_init; +pub mod open_try; diff --git a/crates/relayer-cosmos/src/types/messages/connection/open_ack.rs b/crates/relayer-cosmos/src/types/messages/connection/open_ack.rs new file mode 100644 index 0000000000..5921e78ef2 --- /dev/null +++ b/crates/relayer-cosmos/src/types/messages/connection/open_ack.rs @@ -0,0 +1,49 @@ +use ibc_proto::google::protobuf::Any; +use ibc_proto::ibc::core::connection::v1::MsgConnectionOpenAck as ProtoMsgConnectionOpenAck; +use ibc_relayer_types::core::ics03_connection::version::Version; +use ibc_relayer_types::core::ics23_commitment::commitment::CommitmentProofBytes; +use ibc_relayer_types::core::ics24_host::identifier::ConnectionId; +use ibc_relayer_types::proofs::ConsensusProof; +use ibc_relayer_types::signer::Signer; +use ibc_relayer_types::Height; +use prost::EncodeError; + +use crate::methods::encode::encode_to_any; +use crate::traits::message::CosmosMessage; + +const TYPE_URL: &str = "/ibc.core.connection.v1.MsgConnectionOpenAck"; + +pub struct CosmosConnectionOpenAckMessage { + pub connection_id: ConnectionId, + pub counterparty_connection_id: ConnectionId, + pub version: Version, + pub client_state: Any, + pub update_height: Height, + pub proof_try: CommitmentProofBytes, + pub proof_client: CommitmentProofBytes, + pub proof_consensus: ConsensusProof, +} + +impl CosmosMessage for CosmosConnectionOpenAckMessage { + fn counterparty_message_height_for_update_client(&self) -> Option { + Some(self.update_height) + } + + fn encode_protobuf(&self, signer: &Signer) -> Result { + let proto_message = ProtoMsgConnectionOpenAck { + connection_id: self.connection_id.as_str().to_string(), + counterparty_connection_id: self.counterparty_connection_id.as_str().to_string(), + client_state: Some(self.client_state.clone()), + proof_height: Some(self.update_height.into()), + proof_try: self.proof_try.clone().into(), + proof_client: self.proof_client.clone().into(), + proof_consensus: self.proof_consensus.proof().clone().into(), + consensus_height: Some(self.proof_consensus.height().into()), + version: Some(self.version.clone().into()), + signer: signer.to_string(), + host_consensus_state_proof: Vec::new(), + }; + + encode_to_any(TYPE_URL, &proto_message) + } +} diff --git a/crates/relayer-cosmos/src/types/messages/connection/open_confirm.rs b/crates/relayer-cosmos/src/types/messages/connection/open_confirm.rs new file mode 100644 index 0000000000..763af82b2e --- /dev/null +++ b/crates/relayer-cosmos/src/types/messages/connection/open_confirm.rs @@ -0,0 +1,35 @@ +use ibc_proto::google::protobuf::Any; +use ibc_proto::ibc::core::connection::v1::MsgConnectionOpenConfirm as ProtoMsgConnectionOpenConfirm; +use ibc_relayer_types::core::ics23_commitment::commitment::CommitmentProofBytes; +use ibc_relayer_types::core::ics24_host::identifier::ConnectionId; +use ibc_relayer_types::signer::Signer; +use ibc_relayer_types::Height; +use prost::EncodeError; + +use crate::methods::encode::encode_to_any; +use crate::traits::message::CosmosMessage; + +const TYPE_URL: &str = "/ibc.core.connection.v1.MsgConnectionOpenConfirm"; + +pub struct CosmosConnectionOpenConfirmMessage { + pub connection_id: ConnectionId, + pub update_height: Height, + pub proof_ack: CommitmentProofBytes, +} + +impl CosmosMessage for CosmosConnectionOpenConfirmMessage { + fn counterparty_message_height_for_update_client(&self) -> Option { + Some(self.update_height) + } + + fn encode_protobuf(&self, signer: &Signer) -> Result { + let proto_message = ProtoMsgConnectionOpenConfirm { + connection_id: self.connection_id.as_str().to_string(), + proof_height: Some(self.update_height.into()), + proof_ack: self.proof_ack.clone().into(), + signer: signer.to_string(), + }; + + encode_to_any(TYPE_URL, &proto_message) + } +} diff --git a/crates/relayer-cosmos/src/types/messages/connection/open_init.rs b/crates/relayer-cosmos/src/types/messages/connection/open_init.rs new file mode 100644 index 0000000000..07700004c0 --- /dev/null +++ b/crates/relayer-cosmos/src/types/messages/connection/open_init.rs @@ -0,0 +1,47 @@ +use core::time::Duration; + +use ibc_proto::google::protobuf::Any; +use ibc_proto::ibc::core::commitment::v1::MerklePrefix; +use ibc_proto::ibc::core::connection::v1::{ + Counterparty, MsgConnectionOpenInit as ProtoMsgConnectionOpenInit, +}; +use ibc_relayer_types::core::ics03_connection::version::Version; +use ibc_relayer_types::core::ics23_commitment::commitment::CommitmentPrefix; +use ibc_relayer_types::core::ics24_host::identifier::ClientId; +use ibc_relayer_types::signer::Signer; +use prost::EncodeError; + +use crate::methods::encode::encode_to_any; +use crate::traits::message::CosmosMessage; + +const TYPE_URL: &str = "/ibc.core.connection.v1.MsgConnectionOpenInit"; + +pub struct CosmosConnectionOpenInitMessage { + pub client_id: ClientId, + pub counterparty_client_id: ClientId, + pub counterparty_commitment_prefix: CommitmentPrefix, + pub version: Version, + pub delay_period: Duration, +} + +impl CosmosMessage for CosmosConnectionOpenInitMessage { + fn encode_protobuf(&self, signer: &Signer) -> Result { + let counterparty = Counterparty { + client_id: self.counterparty_client_id.as_str().to_string(), + prefix: Some(MerklePrefix { + key_prefix: self.counterparty_commitment_prefix.clone().into_vec(), + }), + connection_id: "".to_string(), + }; + + let proto_message = ProtoMsgConnectionOpenInit { + client_id: self.client_id.as_str().to_string(), + counterparty: Some(counterparty), + version: Some(self.version.clone().into()), + delay_period: self.delay_period.as_nanos() as u64, + signer: signer.to_string(), + }; + + encode_to_any(TYPE_URL, &proto_message) + } +} diff --git a/crates/relayer-cosmos/src/types/messages/connection/open_try.rs b/crates/relayer-cosmos/src/types/messages/connection/open_try.rs new file mode 100644 index 0000000000..8d742ab588 --- /dev/null +++ b/crates/relayer-cosmos/src/types/messages/connection/open_try.rs @@ -0,0 +1,72 @@ +use core::time::Duration; + +use ibc_proto::google::protobuf::Any; +use ibc_proto::ibc::core::commitment::v1::MerklePrefix; +use ibc_proto::ibc::core::connection::v1::Counterparty; +use ibc_proto::ibc::core::connection::v1::MsgConnectionOpenTry as ProtoMsgConnectionOpenTry; +use ibc_relayer_types::core::ics03_connection::version::Version; +use ibc_relayer_types::core::ics23_commitment::commitment::CommitmentPrefix; +use ibc_relayer_types::core::ics23_commitment::commitment::CommitmentProofBytes; +use ibc_relayer_types::core::ics24_host::identifier::{ClientId, ConnectionId}; +use ibc_relayer_types::proofs::ConsensusProof; +use ibc_relayer_types::signer::Signer; +use ibc_relayer_types::Height; +use prost::EncodeError; + +use crate::methods::encode::encode_to_any; +use crate::traits::message::CosmosMessage; + +const TYPE_URL: &str = "/ibc.core.connection.v1.MsgConnectionOpenTry"; + +pub struct CosmosConnectionOpenTryMessage { + pub client_id: ClientId, + pub counterparty_client_id: ClientId, + pub counterparty_connection_id: ConnectionId, + pub counterparty_commitment_prefix: CommitmentPrefix, + pub counterparty_versions: Vec, + pub client_state: Any, + pub delay_period: Duration, + pub update_height: Height, + pub proof_init: CommitmentProofBytes, + pub proof_client: CommitmentProofBytes, + pub proof_consensus: ConsensusProof, +} + +impl CosmosMessage for CosmosConnectionOpenTryMessage { + fn counterparty_message_height_for_update_client(&self) -> Option { + Some(self.update_height) + } + + fn encode_protobuf(&self, signer: &Signer) -> Result { + let counterparty = Counterparty { + client_id: self.counterparty_client_id.as_str().to_string(), + prefix: Some(MerklePrefix { + key_prefix: self.counterparty_commitment_prefix.clone().into_vec(), + }), + connection_id: self.counterparty_connection_id.as_str().to_string(), + }; + + #[allow(deprecated)] + let proto_message = ProtoMsgConnectionOpenTry { + client_id: self.client_id.as_str().to_string(), + counterparty: Some(counterparty), + counterparty_versions: self + .counterparty_versions + .iter() + .map(|v| v.clone().into()) + .collect(), + client_state: Some(self.client_state.clone()), + delay_period: self.delay_period.as_nanos() as u64, + proof_height: Some(self.update_height.into()), + proof_init: self.proof_init.clone().into(), + proof_client: self.proof_client.clone().into(), + proof_consensus: self.proof_consensus.proof().clone().into(), + consensus_height: Some(self.proof_consensus.height().into()), + signer: signer.to_string(), + previous_connection_id: "".to_string(), + host_consensus_state_proof: Vec::new(), + }; + + encode_to_any(TYPE_URL, &proto_message) + } +} diff --git a/crates/relayer-cosmos/src/types/messages/mod.rs b/crates/relayer-cosmos/src/types/messages/mod.rs new file mode 100644 index 0000000000..51d91e865c --- /dev/null +++ b/crates/relayer-cosmos/src/types/messages/mod.rs @@ -0,0 +1,4 @@ +pub mod channel; +pub mod client; +pub mod connection; +pub mod packet; diff --git a/crates/relayer-cosmos/src/types/messages/packet/ack.rs b/crates/relayer-cosmos/src/types/messages/packet/ack.rs new file mode 100644 index 0000000000..44ac2c013b --- /dev/null +++ b/crates/relayer-cosmos/src/types/messages/packet/ack.rs @@ -0,0 +1,37 @@ +use ibc_proto::google::protobuf::Any; +use ibc_proto::ibc::core::channel::v1::MsgAcknowledgement as ProtoMsgAcknowledgement; +use ibc_relayer_types::core::ics04_channel::packet::Packet; +use ibc_relayer_types::core::ics23_commitment::commitment::CommitmentProofBytes; +use ibc_relayer_types::signer::Signer; +use ibc_relayer_types::Height; +use prost::EncodeError; + +use crate::methods::encode::encode_to_any; +use crate::traits::message::CosmosMessage; + +const TYPE_URL: &str = "/ibc.core.channel.v1.MsgAcknowledgement"; + +pub struct CosmosAckPacketMessage { + pub packet: Packet, + pub acknowledgement: Vec, + pub update_height: Height, + pub proof_acked: CommitmentProofBytes, +} + +impl CosmosMessage for CosmosAckPacketMessage { + fn counterparty_message_height_for_update_client(&self) -> Option { + Some(self.update_height) + } + + fn encode_protobuf(&self, signer: &Signer) -> Result { + let proto_message = ProtoMsgAcknowledgement { + packet: Some(self.packet.clone().into()), + acknowledgement: self.acknowledgement.clone(), + proof_acked: self.proof_acked.clone().into(), + proof_height: Some(self.update_height.into()), + signer: signer.to_string(), + }; + + encode_to_any(TYPE_URL, &proto_message) + } +} diff --git a/crates/relayer-cosmos/src/types/messages/packet/mod.rs b/crates/relayer-cosmos/src/types/messages/packet/mod.rs new file mode 100644 index 0000000000..237d2334f0 --- /dev/null +++ b/crates/relayer-cosmos/src/types/messages/packet/mod.rs @@ -0,0 +1,3 @@ +pub mod ack; +pub mod receive; +pub mod timeout; diff --git a/crates/relayer-cosmos/src/types/messages/packet/receive.rs b/crates/relayer-cosmos/src/types/messages/packet/receive.rs new file mode 100644 index 0000000000..ca11874e1c --- /dev/null +++ b/crates/relayer-cosmos/src/types/messages/packet/receive.rs @@ -0,0 +1,35 @@ +use ibc_proto::google::protobuf::Any; +use ibc_proto::ibc::core::channel::v1::MsgRecvPacket as ProtoMsgRecvPacket; +use ibc_relayer_types::core::ics04_channel::packet::Packet; +use ibc_relayer_types::core::ics23_commitment::commitment::CommitmentProofBytes; +use ibc_relayer_types::signer::Signer; +use ibc_relayer_types::Height; +use prost::EncodeError; + +use crate::methods::encode::encode_to_any; +use crate::traits::message::CosmosMessage; + +const TYPE_URL: &str = "/ibc.core.channel.v1.MsgRecvPacket"; + +pub struct CosmosReceivePacketMessage { + pub packet: Packet, + pub update_height: Height, + pub proof_commitment: CommitmentProofBytes, +} + +impl CosmosMessage for CosmosReceivePacketMessage { + fn counterparty_message_height_for_update_client(&self) -> Option { + Some(self.update_height) + } + + fn encode_protobuf(&self, signer: &Signer) -> Result { + let proto_message = ProtoMsgRecvPacket { + packet: Some(self.packet.clone().into()), + proof_commitment: self.proof_commitment.clone().into(), + proof_height: Some(self.update_height.into()), + signer: signer.to_string(), + }; + + encode_to_any(TYPE_URL, &proto_message) + } +} diff --git a/crates/relayer-cosmos/src/types/messages/packet/timeout.rs b/crates/relayer-cosmos/src/types/messages/packet/timeout.rs new file mode 100644 index 0000000000..ecec2cdf33 --- /dev/null +++ b/crates/relayer-cosmos/src/types/messages/packet/timeout.rs @@ -0,0 +1,37 @@ +use ibc_proto::google::protobuf::Any; +use ibc_proto::ibc::core::channel::v1::MsgTimeout as ProtoMsgTimeout; +use ibc_relayer_types::core::ics04_channel::packet::{Packet, Sequence}; +use ibc_relayer_types::core::ics23_commitment::commitment::CommitmentProofBytes; +use ibc_relayer_types::signer::Signer; +use ibc_relayer_types::Height; +use prost::EncodeError; + +use crate::methods::encode::encode_to_any; +use crate::traits::message::CosmosMessage; + +const TYPE_URL: &str = "/ibc.core.channel.v1.MsgTimeout"; + +pub struct CosmosTimeoutPacketMessage { + pub packet: Packet, + pub next_sequence_recv: Sequence, + pub update_height: Height, + pub proof_unreceived: CommitmentProofBytes, +} + +impl CosmosMessage for CosmosTimeoutPacketMessage { + fn counterparty_message_height_for_update_client(&self) -> Option { + Some(self.update_height) + } + + fn encode_protobuf(&self, signer: &Signer) -> Result { + let proto_message = ProtoMsgTimeout { + packet: Some(self.packet.clone().into()), + next_sequence_recv: self.next_sequence_recv.into(), + proof_unreceived: self.proof_unreceived.clone().into(), + proof_height: Some(self.update_height.into()), + signer: signer.to_string(), + }; + + encode_to_any(TYPE_URL, &proto_message) + } +} diff --git a/crates/relayer-cosmos/src/types/mod.rs b/crates/relayer-cosmos/src/types/mod.rs new file mode 100644 index 0000000000..69c7426a67 --- /dev/null +++ b/crates/relayer-cosmos/src/types/mod.rs @@ -0,0 +1,9 @@ +pub mod batch; +pub mod channel; +pub mod connection; +pub mod error; +pub mod events; +pub mod messages; +pub mod payloads; +pub mod telemetry; +pub mod tendermint; diff --git a/crates/relayer-cosmos/src/types/payloads/channel.rs b/crates/relayer-cosmos/src/types/payloads/channel.rs new file mode 100644 index 0000000000..4fcca911f3 --- /dev/null +++ b/crates/relayer-cosmos/src/types/payloads/channel.rs @@ -0,0 +1,24 @@ +use ibc_relayer_types::core::ics04_channel::channel::Ordering; +use ibc_relayer_types::core::ics04_channel::version::Version; +use ibc_relayer_types::core::ics23_commitment::commitment::CommitmentProofBytes; +use ibc_relayer_types::core::ics24_host::identifier::ConnectionId; +use ibc_relayer_types::Height; + +pub struct CosmosChannelOpenTryPayload { + pub ordering: Ordering, + pub connection_hops: Vec, + pub version: Version, + pub update_height: Height, + pub proof_init: CommitmentProofBytes, +} + +pub struct CosmosChannelOpenAckPayload { + pub version: Version, + pub update_height: Height, + pub proof_try: CommitmentProofBytes, +} + +pub struct CosmosChannelOpenConfirmPayload { + pub update_height: Height, + pub proof_ack: CommitmentProofBytes, +} diff --git a/crates/relayer-cosmos/src/types/payloads/client.rs b/crates/relayer-cosmos/src/types/payloads/client.rs new file mode 100644 index 0000000000..04019a43f1 --- /dev/null +++ b/crates/relayer-cosmos/src/types/payloads/client.rs @@ -0,0 +1,10 @@ +use crate::types::tendermint::{TendermintClientState, TendermintConsensusState, TendermintHeader}; + +pub struct CosmosUpdateClientPayload { + pub headers: Vec, +} + +pub struct CosmosCreateClientPayload { + pub client_state: TendermintClientState, + pub consensus_state: TendermintConsensusState, +} diff --git a/crates/relayer-cosmos/src/types/payloads/connection.rs b/crates/relayer-cosmos/src/types/payloads/connection.rs new file mode 100644 index 0000000000..333358f76c --- /dev/null +++ b/crates/relayer-cosmos/src/types/payloads/connection.rs @@ -0,0 +1,39 @@ +use core::time::Duration; + +use ibc_relayer_types::core::ics03_connection::version::Version; +use ibc_relayer_types::core::ics23_commitment::commitment::{ + CommitmentPrefix, CommitmentProofBytes, +}; +use ibc_relayer_types::proofs::ConsensusProof; +use ibc_relayer_types::Height; + +use crate::types::tendermint::TendermintClientState; + +pub struct CosmosConnectionOpenInitPayload { + pub commitment_prefix: CommitmentPrefix, +} + +pub struct CosmosConnectionOpenTryPayload { + pub commitment_prefix: CommitmentPrefix, + pub client_state: TendermintClientState, + pub versions: Vec, + pub delay_period: Duration, + pub update_height: Height, + pub proof_init: CommitmentProofBytes, + pub proof_client: CommitmentProofBytes, + pub proof_consensus: ConsensusProof, +} + +pub struct CosmosConnectionOpenAckPayload { + pub client_state: TendermintClientState, + pub version: Version, + pub update_height: Height, + pub proof_try: CommitmentProofBytes, + pub proof_client: CommitmentProofBytes, + pub proof_consensus: ConsensusProof, +} + +pub struct CosmosConnectionOpenConfirmPayload { + pub update_height: Height, + pub proof_ack: CommitmentProofBytes, +} diff --git a/crates/relayer-cosmos/src/types/payloads/mod.rs b/crates/relayer-cosmos/src/types/payloads/mod.rs new file mode 100644 index 0000000000..51d91e865c --- /dev/null +++ b/crates/relayer-cosmos/src/types/payloads/mod.rs @@ -0,0 +1,4 @@ +pub mod channel; +pub mod client; +pub mod connection; +pub mod packet; diff --git a/crates/relayer-cosmos/src/types/payloads/packet.rs b/crates/relayer-cosmos/src/types/payloads/packet.rs new file mode 100644 index 0000000000..d1d53f7948 --- /dev/null +++ b/crates/relayer-cosmos/src/types/payloads/packet.rs @@ -0,0 +1,18 @@ +use ibc_relayer_types::core::ics23_commitment::commitment::CommitmentProofBytes; +use ibc_relayer_types::Height; + +pub struct CosmosReceivePacketPayload { + pub update_height: Height, + pub proof_commitment: CommitmentProofBytes, +} + +pub struct CosmosAckPacketPayload { + pub ack: Vec, + pub update_height: Height, + pub proof_acked: CommitmentProofBytes, +} + +pub struct CosmosTimeoutUnorderedPacketPayload { + pub update_height: Height, + pub proof_unreceived: CommitmentProofBytes, +} diff --git a/crates/relayer-cosmos/src/types/telemetry.rs b/crates/relayer-cosmos/src/types/telemetry.rs new file mode 100644 index 0000000000..0f88e62511 --- /dev/null +++ b/crates/relayer-cosmos/src/types/telemetry.rs @@ -0,0 +1,30 @@ +use alloc::sync::Arc; +use std::collections::HashMap; +use std::sync::Mutex; + +use opentelemetry::{ + global, + metrics::{Counter, Meter, UpDownCounter, ValueRecorder}, +}; + +#[derive(Clone)] +pub struct CosmosTelemetry { + pub meter: Arc, + + pub counters: Arc>>>, + + pub value_recorders: Arc>>>, + + pub updown_counters: Arc>>>, +} + +impl Default for CosmosTelemetry { + fn default() -> Self { + Self { + meter: Arc::new(global::meter("hermes")), + counters: Arc::new(Mutex::new(HashMap::new())), + value_recorders: Arc::new(Mutex::new(HashMap::new())), + updown_counters: Arc::new(Mutex::new(HashMap::new())), + } + } +} diff --git a/crates/relayer-cosmos/src/types/tendermint.rs b/crates/relayer-cosmos/src/types/tendermint.rs new file mode 100644 index 0000000000..2bc78e987a --- /dev/null +++ b/crates/relayer-cosmos/src/types/tendermint.rs @@ -0,0 +1,5 @@ +pub use ibc_relayer_types::clients::ics07_tendermint::client_state::ClientState as TendermintClientState; + +pub use ibc_relayer_types::clients::ics07_tendermint::consensus_state::ConsensusState as TendermintConsensusState; + +pub use ibc_relayer_types::clients::ics07_tendermint::header::Header as TendermintHeader; diff --git a/crates/relayer-mock/Cargo.toml b/crates/relayer-mock/Cargo.toml new file mode 100644 index 0000000000..ed059c89a5 --- /dev/null +++ b/crates/relayer-mock/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "ibc-relayer-mock" +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" +readme = "README.md" +keywords = ["blockchain", "consensus", "cosmos", "ibc", "tendermint"] +repository = "https://github.com/informalsystems/ibc-rs" +authors = ["Informal Systems "] +rust-version = "1.68" +description = """ + TBD +""" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +async-trait = "0.1.56" +eyre = "0.6.8" +flex-error = "0.4.4" +ibc-relayer-components = { version = "0.1.0", path = "../relayer-components" } +ibc-relayer-runtime = { path = "../relayer-runtime" } +tokio = { version = "1.0", features = ["full"] } +tracing = "0.1.36" diff --git a/crates/relayer-mock/src/lib.rs b/crates/relayer-mock/src/lib.rs new file mode 100644 index 0000000000..e2c32d9421 --- /dev/null +++ b/crates/relayer-mock/src/lib.rs @@ -0,0 +1,8 @@ +extern crate alloc; +extern crate std; + +#[forbid(clippy::unwrap_used)] +pub mod relayer_mock; + +#[cfg(test)] +pub mod tests; diff --git a/crates/relayer-mock/src/relayer_mock/base/error.rs b/crates/relayer-mock/src/relayer_mock/base/error.rs new file mode 100644 index 0000000000..2d6f8fcf3c --- /dev/null +++ b/crates/relayer-mock/src/relayer_mock/base/error.rs @@ -0,0 +1,89 @@ +use alloc::string::String; +use alloc::sync::Arc; + +use eyre::Report; +use flex_error::{define_error, TraceError}; +use ibc_relayer_runtime::types::error::Error as TokioError; + +pub type Error = Arc; + +define_error! { + #[derive(Clone, Debug)] + BaseError { + EmptyIterator + | _ | { "empty iterator error" }, + + Generic + [ TraceError ] + | _ | { "generic error" }, + + Tokio + [ TokioError ] + | _ | { "tokio runtime error" }, + + MismatchError + { expected: usize, actual: usize } + | e | { + format_args!("mismatch size for events returned. expected: {}, got: {}", + e.expected, e.actual) + }, + + NoChainState + { id: String, height: u128 } + | e | { + format_args!("no chain state found for chain: {} at height {}", e.id, e.height) + }, + + NoConsensusState + { id: String } + | e | { + format_args!("no consensus state found for client: {}", e.id) + }, + + NoConsensusStateAtHeight + { id: String, height: u128 } + | e | { + format_args!("no consensus state found for client {} at height {}", e.id, e.height) + }, + + ConsensusDivergence + { id: String, height: u128 } + | e | { + format_args!("trying to insert different consensus states for client {} at height {}", e.id, e.height) + }, + + ReceiveWithoutSent + { chain: String, channel_id: String } + | e | { + format_args!("error trying to build RecvPacket from chain `{}` with channel ID `{}`. Source chain Consensus State doesn't have the packet recorded as sent", e.chain, e.channel_id) + }, + + AcknowledgmentWithoutReceived + { chain: String, channel_id: String } + | e | { + format_args!("error trying to build AckPacket from chain `{}` with channel ID `{}`. Destination chain Consensus State doesn't have the packet recorded as received", e.chain, e.channel_id) + }, + + TimeoutWithoutSent + { chain: String, channel_id: String } + | e | { + format_args!("error trying to build TimeoutPacket from chain `{}` with channel ID `{}`. Source chain Consensus State doesn't have the packet recorded as sent", e.chain, e.channel_id) + }, + + TimeoutReceive + { chain: String, height: u128, chain_height: u128, timestamp: u128, current_timestamp: u128 } + | e | { + format_args!("error receiving RecvPacket for chain {}. Packet timeout height {}, chain height {}. Packet timeout timestamp {}, current timestamp {}", e.chain, e.height, e.chain_height, e.timestamp, e.current_timestamp) + }, + + NoClientForChannel + { channel_id: String, chain: String } + | e | { format_args!("error no client mapped to the channel `{}` for chain `{}`", e.channel_id, e.chain) }, + } +} + +impl From for BaseError { + fn from(e: Report) -> Self { + BaseError::generic(e) + } +} diff --git a/crates/relayer-mock/src/relayer_mock/base/impls/chain.rs b/crates/relayer-mock/src/relayer_mock/base/impls/chain.rs new file mode 100644 index 0000000000..f9eae42288 --- /dev/null +++ b/crates/relayer-mock/src/relayer_mock/base/impls/chain.rs @@ -0,0 +1,498 @@ +//! The following types are used for the OfaChainTypes implementation: +//! * For the Height, a wrapper around a u128, referred to as MockHeight. +//! * For the Timestamp, a u128 representing milliseconds is retrieved +//! using a shared clock, MockClock. +//! * For messages, an enum, MockMessage, which identifies +//! RecvPacket, AckPacket, TimeoutPacket, and UpdateClient messages. +//! * The ConsensusState is a set of 4 HashSets used to store which messages +//! have been sent, received, acknowledged, and timed out. +//! * The ChainStatus is a ConsensusState with a Height and a Timestamp. + +use async_trait::async_trait; +use eyre::eyre; +use ibc_relayer_components::chain::traits::client::client_state::CanQueryClientState; +use ibc_relayer_components::chain::traits::components::chain_status_querier::ChainStatusQuerier; +use ibc_relayer_components::chain::traits::components::consensus_state_querier::ConsensusStateQuerier; +use ibc_relayer_components::chain::traits::components::message_sender::MessageSender; +use ibc_relayer_components::chain::traits::components::packet_fields_reader::PacketFieldsReader; +use ibc_relayer_components::chain::traits::logs::event::CanLogChainEvent; +use ibc_relayer_components::chain::traits::logs::packet::CanLogChainPacket; +use ibc_relayer_components::chain::traits::message_builders::ack_packet::{ + CanBuildAckPacketMessage, CanBuildAckPacketPayload, +}; +use ibc_relayer_components::chain::traits::message_builders::receive_packet::{ + CanBuildReceivePacketMessage, CanBuildReceivePacketPayload, +}; +use ibc_relayer_components::chain::traits::message_builders::timeout_unordered_packet::{ + CanBuildTimeoutUnorderedPacketMessage, CanBuildTimeoutUnorderedPacketPayload, +}; +use ibc_relayer_components::chain::traits::queries::received_packet::CanQueryReceivedPacket; +use ibc_relayer_components::chain::traits::queries::write_ack::CanQueryWriteAcknowledgement; +use ibc_relayer_components::chain::traits::types::chain_id::{HasChainId, HasChainIdType}; +use ibc_relayer_components::chain::traits::types::client_state::HasClientStateType; +use ibc_relayer_components::chain::traits::types::consensus_state::HasConsensusStateType; +use ibc_relayer_components::chain::traits::types::event::HasEventType; +use ibc_relayer_components::chain::traits::types::height::{CanIncrementHeight, HasHeightType}; +use ibc_relayer_components::chain::traits::types::ibc::{ + HasCounterpartyMessageHeight, HasIbcChainTypes, +}; +use ibc_relayer_components::chain::traits::types::ibc_events::send_packet::HasSendPacketEvent; +use ibc_relayer_components::chain::traits::types::ibc_events::write_ack::HasWriteAcknowledgementEvent; +use ibc_relayer_components::chain::traits::types::message::{ + CanEstimateMessageSize, HasMessageType, +}; +use ibc_relayer_components::chain::traits::types::packet::HasIbcPacketTypes; +use ibc_relayer_components::chain::traits::types::packets::ack::HasAckPacketPayload; +use ibc_relayer_components::chain::traits::types::packets::receive::HasReceivePacketPayload; +use ibc_relayer_components::chain::traits::types::packets::timeout::HasTimeoutUnorderedPacketPayload; +use ibc_relayer_components::chain::traits::types::status::HasChainStatusType; +use ibc_relayer_components::chain::traits::types::timestamp::HasTimestampType; +use ibc_relayer_components::core::traits::component::HasComponents; +use ibc_relayer_components::core::traits::error::HasErrorType; +use ibc_relayer_components::logger::traits::has_logger::{HasLogger, HasLoggerType}; +use ibc_relayer_components::runtime::traits::runtime::HasRuntime; +use ibc_relayer_runtime::types::error::Error as TokioError; +use ibc_relayer_runtime::types::log::logger::TracingLogger; +use ibc_relayer_runtime::types::log::value::LogValue; + +use crate::relayer_mock::base::error::{BaseError, Error}; +use crate::relayer_mock::base::types::aliases::{ + ChainStatus, ChannelId, ClientId, ConsensusState, MockTimestamp, PortId, Sequence, +}; +use crate::relayer_mock::base::types::chain::MockChainStatus; +use crate::relayer_mock::base::types::events::{Event, SendPacketEvent, WriteAcknowledgementEvent}; +use crate::relayer_mock::base::types::height::Height as MockHeight; +use crate::relayer_mock::base::types::message::Message as MockMessage; +use crate::relayer_mock::base::types::packet::PacketKey; +use crate::relayer_mock::base::types::runtime::MockRuntimeContext; +use crate::relayer_mock::components::MockComponents; +use crate::relayer_mock::contexts::chain::MockChainContext; + +impl HasComponents for MockChainContext { + type Components = MockComponents; +} + +impl HasErrorType for MockChainContext { + type Error = Error; +} + +impl HasRuntime for MockChainContext { + type Runtime = MockRuntimeContext; + + fn runtime(&self) -> &Self::Runtime { + &self.runtime + } + + fn runtime_error(e: TokioError) -> Error { + BaseError::tokio(e).into() + } +} + +impl HasLoggerType for MockChainContext { + type Logger = TracingLogger; +} + +impl HasHeightType for MockChainContext { + type Height = MockHeight; +} + +impl HasEventType for MockChainContext { + type Event = Event; +} + +impl HasTimestampType for MockChainContext { + type Timestamp = MockTimestamp; +} + +impl HasMessageType for MockChainContext { + type Message = MockMessage; +} + +impl HasChainIdType for MockChainContext { + type ChainId = String; +} + +impl HasIbcChainTypes for MockChainContext { + type ClientId = ClientId; + + type ConnectionId = String; + + type ChannelId = ChannelId; + + type PortId = PortId; + + type Sequence = Sequence; +} + +impl HasIbcPacketTypes for MockChainContext { + type IncomingPacket = PacketKey; + + type OutgoingPacket = PacketKey; +} + +impl PacketFieldsReader for MockComponents { + fn incoming_packet_src_channel_id(packet: &PacketKey) -> &ChannelId { + &packet.src_channel_id + } + + fn incoming_packet_src_port(packet: &PacketKey) -> &PortId { + &packet.src_port_id + } + + fn incoming_packet_dst_port(packet: &PacketKey) -> &PortId { + &packet.dst_port_id + } + + fn incoming_packet_dst_channel_id(packet: &PacketKey) -> &ChannelId { + &packet.dst_channel_id + } + + fn incoming_packet_sequence(packet: &PacketKey) -> &Sequence { + &packet.sequence + } + + fn incoming_packet_timeout_height(packet: &PacketKey) -> Option<&MockHeight> { + Some(&packet.timeout_height) + } + + fn incoming_packet_timeout_timestamp(packet: &PacketKey) -> &MockTimestamp { + &packet.timeout_timestamp + } + + fn outgoing_packet_src_channel_id(packet: &PacketKey) -> &ChannelId { + &packet.src_channel_id + } + + fn outgoing_packet_src_port(packet: &PacketKey) -> &PortId { + &packet.src_port_id + } + + fn outgoing_packet_dst_port(packet: &PacketKey) -> &PortId { + &packet.dst_port_id + } + + fn outgoing_packet_dst_channel_id(packet: &PacketKey) -> &ChannelId { + &packet.dst_channel_id + } + + fn outgoing_packet_sequence(packet: &PacketKey) -> &Sequence { + &packet.sequence + } + + fn outgoing_packet_timeout_height(packet: &PacketKey) -> Option<&MockHeight> { + Some(&packet.timeout_height) + } + + fn outgoing_packet_timeout_timestamp(packet: &PacketKey) -> &MockTimestamp { + &packet.timeout_timestamp + } +} + +impl HasWriteAcknowledgementEvent for MockChainContext { + type WriteAcknowledgementEvent = WriteAcknowledgementEvent; + + fn try_extract_write_acknowledgement_event( + event: &Self::Event, + ) -> Option { + match event { + Event::WriteAcknowledgment(h) => Some(WriteAcknowledgementEvent::new(*h)), + _ => None, + } + } +} + +impl HasConsensusStateType for MockChainContext { + type ConsensusState = ConsensusState; +} + +impl HasClientStateType for MockChainContext { + // TODO + type ClientState = (); +} + +impl HasChainStatusType for MockChainContext { + type ChainStatus = ChainStatus; + + fn chain_status_height(status: &Self::ChainStatus) -> &Self::Height { + &status.height + } + + fn chain_status_timestamp(status: &Self::ChainStatus) -> &Self::Timestamp { + &status.timestamp + } +} + +impl HasSendPacketEvent for MockChainContext { + type SendPacketEvent = SendPacketEvent; + + fn try_extract_send_packet_event(event: &Self::Event) -> Option { + match event { + Event::SendPacket(send_packet_event) => Some(send_packet_event.clone()), + _ => None, + } + } + + fn extract_packet_from_send_packet_event( + event: &Self::SendPacketEvent, + ) -> Self::OutgoingPacket { + PacketKey::from(event.clone()) + } +} + +impl HasLogger for MockChainContext { + fn logger(&self) -> &TracingLogger { + &TracingLogger + } +} + +impl CanLogChainEvent for MockChainContext { + fn log_event<'a>(event: &Event) -> LogValue<'_> { + LogValue::Debug(event) + } +} + +impl CanIncrementHeight for MockChainContext { + fn increment_height(height: &Self::Height) -> Result { + Ok(height.increment()) + } +} + +impl CanEstimateMessageSize for MockChainContext { + fn estimate_message_size(_message: &Self::Message) -> Result { + // Only single messages are sent by the Mock Chain + Ok(1) + } +} + +impl HasChainId for MockChainContext { + fn chain_id(&self) -> &Self::ChainId { + &self.name + } +} + +#[async_trait] +impl MessageSender for MockComponents { + async fn send_messages( + chain: &MockChainContext, + messages: Vec, + ) -> Result>, Error> { + chain.process_messages(messages) + } +} + +#[async_trait] +impl ChainStatusQuerier for MockComponents { + async fn query_chain_status(chain: &MockChainContext) -> Result { + let height = chain.get_current_height(); + let state = chain.get_current_state(); + // Since the MockChain only updates manually, the Height is increased by + // 1 everytime the chain status is queried, without changing its state. + chain.new_block()?; + let time = chain.runtime.get_time(); + Ok(MockChainStatus::from((height, time, state))) + } +} + +impl CanLogChainPacket for MockChainContext { + fn log_incoming_packet(packet: &PacketKey) -> LogValue<'_> { + LogValue::Display(packet) + } + + fn log_outgoing_packet(packet: &PacketKey) -> LogValue<'_> { + LogValue::Display(packet) + } +} + +impl HasCounterpartyMessageHeight for MockChainContext { + fn counterparty_message_height_for_update_client(message: &MockMessage) -> Option { + match message { + MockMessage::RecvPacket(h, _) => Some(h.increment()), + MockMessage::AckPacket(h, _) => Some(h.increment()), + MockMessage::TimeoutPacket(h, _) => Some(h.increment()), + _ => None, + } + } +} + +#[async_trait] +impl ConsensusStateQuerier for MockComponents { + async fn query_consensus_state( + chain: &MockChainContext, + client_id: &ClientId, + height: &MockHeight, + ) -> Result { + let client_consensus = + chain.query_consensus_state_at_height(client_id.to_string(), *height)?; + Ok(client_consensus) + } +} + +#[async_trait] +impl CanQueryClientState for MockChainContext { + async fn query_client_state(&self, _client_id: &Self::ClientId) -> Result<(), Self::Error> { + Ok(()) + } +} + +#[async_trait] +impl CanQueryReceivedPacket for MockChainContext { + async fn query_is_packet_received( + &self, + port_id: &Self::PortId, + channel_id: &Self::ChannelId, + sequence: &Self::Sequence, + ) -> Result { + let state = self.get_current_state(); + Ok(state.check_received((port_id.clone(), channel_id.clone(), *sequence))) + } +} + +#[async_trait] +impl CanQueryWriteAcknowledgement for MockChainContext { + async fn query_write_acknowledgement_event( + &self, + packet: &PacketKey, + ) -> Result, Error> { + let received = self.get_received_packet_information( + packet.dst_port_id.clone(), + packet.dst_channel_id.clone(), + packet.sequence, + ); + + if let Some((packet2, height)) = received { + if &packet2 == packet { + Ok(Some(WriteAcknowledgementEvent::new(height))) + } else { + Err(BaseError::generic(eyre!( + "mismatch between packet in state {} and packet: {}", + packet2, + packet + )) + .into()) + } + } else { + Ok(None) + } + } +} + +impl HasReceivePacketPayload for MockChainContext { + type ReceivePacketPayload = MockMessage; +} + +#[async_trait] +impl CanBuildReceivePacketPayload for MockChainContext { + async fn build_receive_packet_payload( + &self, + _client_state: &Self::ClientState, + height: &MockHeight, + packet: &PacketKey, + ) -> Result { + // If the latest state of the source chain doesn't have the packet as sent, return an error. + let state = self.get_current_state(); + if !state.check_sent(( + packet.src_port_id.clone(), + packet.src_channel_id.clone(), + packet.sequence, + )) { + return Err(BaseError::receive_without_sent( + self.name().to_string(), + packet.src_channel_id.to_string(), + ) + .into()); + } + Ok(MockMessage::RecvPacket(*height, packet.clone())) + } +} + +#[async_trait] +impl CanBuildReceivePacketMessage for MockChainContext { + async fn build_receive_packet_message( + &self, + _packet: &PacketKey, + payload: MockMessage, + ) -> Result { + Ok(payload) + } +} + +impl HasAckPacketPayload for MockChainContext { + type AckPacketPayload = MockMessage; +} + +#[async_trait] +impl CanBuildAckPacketPayload for MockChainContext { + async fn build_ack_packet_payload( + &self, + _client_state: &Self::ClientState, + height: &MockHeight, + packet: &PacketKey, + _ack: &WriteAcknowledgementEvent, + ) -> Result { + // If the latest state of the destination chain doesn't have the packet as received, return an error. + let state = self.get_current_state(); + + if !state.check_received(( + packet.dst_port_id.clone(), + packet.dst_channel_id.clone(), + packet.sequence, + )) { + return Err(BaseError::acknowledgment_without_received( + self.name().to_string(), + packet.dst_channel_id.to_string(), + ) + .into()); + } + + Ok(MockMessage::AckPacket(*height, packet.clone())) + } +} + +#[async_trait] +impl CanBuildAckPacketMessage for MockChainContext { + async fn build_ack_packet_message( + &self, + _packet: &PacketKey, + payload: MockMessage, + ) -> Result { + Ok(payload) + } +} + +impl HasTimeoutUnorderedPacketPayload for MockChainContext { + type TimeoutUnorderedPacketPayload = MockMessage; +} + +#[async_trait] +impl CanBuildTimeoutUnorderedPacketPayload for MockChainContext { + async fn build_timeout_unordered_packet_payload( + &self, + _client_state: &Self::ClientState, + height: &MockHeight, + packet: &PacketKey, + ) -> Result { + let state = self.get_current_state(); + let current_timestamp = self.runtime.get_time(); + + if !state.check_timeout(packet.clone(), *height, current_timestamp) { + return Err(BaseError::timeout_without_sent( + self.name().to_string(), + packet.src_channel_id.to_string(), + ) + .into()); + } + + Ok(MockMessage::TimeoutPacket(*height, packet.clone())) + } +} + +#[async_trait] +impl CanBuildTimeoutUnorderedPacketMessage for MockChainContext { + async fn build_timeout_unordered_packet_message( + &self, + _packet: &PacketKey, + payload: MockMessage, + ) -> Result { + Ok(payload) + } +} diff --git a/crates/relayer-mock/src/relayer_mock/base/impls/mod.rs b/crates/relayer-mock/src/relayer_mock/base/impls/mod.rs new file mode 100644 index 0000000000..d8d5e5ef66 --- /dev/null +++ b/crates/relayer-mock/src/relayer_mock/base/impls/mod.rs @@ -0,0 +1,2 @@ +pub mod chain; +pub mod relay; diff --git a/crates/relayer-mock/src/relayer_mock/base/impls/relay.rs b/crates/relayer-mock/src/relayer_mock/base/impls/relay.rs new file mode 100644 index 0000000000..a51c7c0ca3 --- /dev/null +++ b/crates/relayer-mock/src/relayer_mock/base/impls/relay.rs @@ -0,0 +1,133 @@ +use alloc::boxed::Box; +use alloc::string::ToString; +use alloc::vec::Vec; +use ibc_relayer_components::core::traits::component::HasComponents; +use ibc_relayer_components::core::traits::error::HasErrorType; +use ibc_relayer_components::logger::traits::has_logger::{HasLogger, HasLoggerType}; +use ibc_relayer_components::relay::traits::chains::HasRelayChains; +use ibc_relayer_components::relay::traits::components::update_client_message_builder::UpdateClientMessageBuilder; +use ibc_relayer_components::relay::traits::packet_lock::HasPacketLock; +use ibc_relayer_components::relay::traits::target::{DestinationTarget, SourceTarget}; +use ibc_relayer_components::runtime::traits::runtime::HasRuntime; +use ibc_relayer_runtime::types::log::logger::TracingLogger; +use std::vec; + +use async_trait::async_trait; +use ibc_relayer_runtime::types::error::Error as TokioError; + +use crate::relayer_mock::base::error::{BaseError, Error}; +use crate::relayer_mock::base::types::aliases::ClientId; +use crate::relayer_mock::base::types::height::Height as MockHeight; +use crate::relayer_mock::base::types::message::Message as MockMessage; +use crate::relayer_mock::base::types::packet::PacketKey; +use crate::relayer_mock::base::types::runtime::MockRuntimeContext; +use crate::relayer_mock::components::MockComponents; +use crate::relayer_mock::contexts::chain::MockChainContext; +use crate::relayer_mock::contexts::relay::MockRelayContext; + +impl HasComponents for MockRelayContext { + type Components = MockComponents; +} + +impl HasErrorType for MockRelayContext { + type Error = Error; +} + +impl HasRuntime for MockRelayContext { + type Runtime = MockRuntimeContext; + + fn runtime(&self) -> &Self::Runtime { + &self.runtime + } + + fn runtime_error(e: TokioError) -> Error { + BaseError::tokio(e).into() + } +} + +impl HasLoggerType for MockRelayContext { + type Logger = TracingLogger; +} + +impl HasLogger for MockRelayContext { + fn logger(&self) -> &TracingLogger { + &TracingLogger + } +} + +impl HasRelayChains for MockRelayContext { + type Packet = PacketKey; + + type SrcChain = MockChainContext; + + type DstChain = MockChainContext; + + fn src_chain_error(e: Error) -> Self::Error { + e + } + + fn dst_chain_error(e: Error) -> Self::Error { + e + } + + fn src_chain(&self) -> &MockChainContext { + &self.src_chain + } + + fn dst_chain(&self) -> &MockChainContext { + &self.dst_chain + } + + fn src_client_id(&self) -> &ClientId { + self.dst_to_src_client() + } + + fn dst_client_id(&self) -> &ClientId { + self.src_to_dst_client() + } +} + +pub struct MockBuildUpdateClientMessage; + +#[async_trait] +impl UpdateClientMessageBuilder for MockBuildUpdateClientMessage { + async fn build_update_client_messages( + context: &MockRelayContext, + _target: SourceTarget, + height: &MockHeight, + ) -> Result, Error> { + let state = context.dst_chain.query_state_at_height(*height)?; + Ok(vec![MockMessage::UpdateClient( + context.src_client_id().to_string(), + *height, + state, + )]) + } +} + +#[async_trait] +impl UpdateClientMessageBuilder + for MockBuildUpdateClientMessage +{ + async fn build_update_client_messages( + context: &MockRelayContext, + _target: DestinationTarget, + height: &MockHeight, + ) -> Result, Error> { + let state = context.src_chain.query_state_at_height(*height)?; + Ok(vec![MockMessage::UpdateClient( + context.dst_client_id().to_string(), + *height, + state, + )]) + } +} + +#[async_trait] +impl HasPacketLock for MockRelayContext { + type PacketLock<'a> = (); + + async fn try_acquire_packet_lock<'a>(&'a self, _packet: &'a PacketKey) -> Option<()> { + Some(()) + } +} diff --git a/crates/relayer-mock/src/relayer_mock/base/mod.rs b/crates/relayer-mock/src/relayer_mock/base/mod.rs new file mode 100644 index 0000000000..6e127e1512 --- /dev/null +++ b/crates/relayer-mock/src/relayer_mock/base/mod.rs @@ -0,0 +1,3 @@ +pub mod error; +pub mod impls; +pub mod types; diff --git a/crates/relayer-mock/src/relayer_mock/base/types/aliases.rs b/crates/relayer-mock/src/relayer_mock/base/types/aliases.rs new file mode 100644 index 0000000000..1ba87d29d6 --- /dev/null +++ b/crates/relayer-mock/src/relayer_mock/base/types/aliases.rs @@ -0,0 +1,38 @@ +use std::collections::HashMap; +use std::convert::From; +use std::fmt::{self, Display}; + +use super::height::Height; +use crate::relayer_mock::base::types::chain::MockChainStatus; +use crate::relayer_mock::base::types::state::State; + +pub type PacketUID = (PortId, ChannelId, Sequence); +pub type ConsensusState = State; +pub type ChainState = State; +pub type ClientId = String; +pub type ChannelId = String; +pub type PortId = String; +pub type ChainStatus = MockChainStatus; +pub type Sequence = u128; +pub type StateStore = HashMap; + +#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord)] +pub struct MockTimestamp(pub u128); + +impl Display for MockTimestamp { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl Default for MockTimestamp { + fn default() -> Self { + MockTimestamp(1000) + } +} + +impl From for MockTimestamp { + fn from(u: u128) -> Self { + MockTimestamp(u) + } +} diff --git a/crates/relayer-mock/src/relayer_mock/base/types/chain.rs b/crates/relayer-mock/src/relayer_mock/base/types/chain.rs new file mode 100644 index 0000000000..8fd4b18a6a --- /dev/null +++ b/crates/relayer-mock/src/relayer_mock/base/types/chain.rs @@ -0,0 +1,30 @@ +use crate::relayer_mock::base::types::aliases::MockTimestamp; +use crate::relayer_mock::base::types::height::Height; +use crate::relayer_mock::base::types::state::State; + +#[derive(Clone, Debug)] +pub struct MockChainStatus { + pub height: Height, + pub timestamp: MockTimestamp, + pub state: State, +} + +impl MockChainStatus { + pub fn new(height: Height, timestamp: MockTimestamp, state: State) -> Self { + Self { + height, + timestamp, + state, + } + } +} + +impl From<(Height, MockTimestamp, State)> for MockChainStatus { + fn from(s: (Height, MockTimestamp, State)) -> Self { + MockChainStatus { + height: s.0, + timestamp: s.1, + state: s.2, + } + } +} diff --git a/crates/relayer-mock/src/relayer_mock/base/types/events.rs b/crates/relayer-mock/src/relayer_mock/base/types/events.rs new file mode 100644 index 0000000000..956066e5ff --- /dev/null +++ b/crates/relayer-mock/src/relayer_mock/base/types/events.rs @@ -0,0 +1,51 @@ +use crate::relayer_mock::base::types::aliases::MockTimestamp; +use crate::relayer_mock::base::types::height::Height; + +#[derive(Debug, PartialEq)] +pub enum Event { + RecvPacket(Height), + WriteAcknowledgment(Height), + SendPacket(SendPacketEvent), +} + +#[derive(Debug)] +pub struct WriteAcknowledgementEvent(pub Height); + +impl WriteAcknowledgementEvent { + pub fn new(h: Height) -> Self { + WriteAcknowledgementEvent(h) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct SendPacketEvent { + pub src_channel_id: String, + pub src_port_id: String, + pub dst_channel_id: String, + pub dst_port_id: String, + pub sequence: u128, + pub timeout_height: Height, + pub timeout_timestamp: MockTimestamp, +} + +impl SendPacketEvent { + pub fn new( + src_channel_id: String, + src_port_id: String, + dst_channel_id: String, + dst_port_id: String, + sequence: u128, + timeout_height: Height, + timeout_timestamp: MockTimestamp, + ) -> Self { + Self { + src_channel_id, + src_port_id, + dst_channel_id, + dst_port_id, + sequence, + timeout_height, + timeout_timestamp, + } + } +} diff --git a/crates/relayer-mock/src/relayer_mock/base/types/height.rs b/crates/relayer-mock/src/relayer_mock/base/types/height.rs new file mode 100644 index 0000000000..536786e9d2 --- /dev/null +++ b/crates/relayer-mock/src/relayer_mock/base/types/height.rs @@ -0,0 +1,29 @@ +use std::fmt::Display; + +#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct Height(pub u128); + +impl Height { + pub fn increment(&self) -> Self { + Height(self.0 + 1) + } +} + +impl Display for Height { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Height({})", self.0)?; + Ok(()) + } +} + +impl From for Height { + fn from(v: u128) -> Self { + Height(v) + } +} + +impl Default for Height { + fn default() -> Self { + Height(1) + } +} diff --git a/crates/relayer-mock/src/relayer_mock/base/types/message.rs b/crates/relayer-mock/src/relayer_mock/base/types/message.rs new file mode 100644 index 0000000000..a49971bbf4 --- /dev/null +++ b/crates/relayer-mock/src/relayer_mock/base/types/message.rs @@ -0,0 +1,26 @@ +use std::fmt::{Display, Formatter, Result}; + +use crate::relayer_mock::base::types::aliases::ChainState; +use crate::relayer_mock::base::types::aliases::ClientId; +use crate::relayer_mock::base::types::{height::Height, packet::PacketKey}; + +#[derive(Debug)] +pub enum Message { + RecvPacket(Height, PacketKey), + AckPacket(Height, PacketKey), + TimeoutPacket(Height, PacketKey), + UpdateClient(ClientId, Height, ChainState), +} + +impl Display for Message { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self { + Self::RecvPacket(h, p) => { + write!(f, "RecvPacket:{h}: {p}") + } + Self::AckPacket(h, p) => write!(f, "AckPacket:{h}: {p}"), + Self::TimeoutPacket(h, p) => write!(f, "TimeoutPacket:{h}: {p}"), + Self::UpdateClient(from, h, s) => write!(f, "{from}|UpdateClient:{h}: {s:?}"), + } + } +} diff --git a/crates/relayer-mock/src/relayer_mock/base/types/mod.rs b/crates/relayer-mock/src/relayer_mock/base/types/mod.rs new file mode 100644 index 0000000000..182ce20108 --- /dev/null +++ b/crates/relayer-mock/src/relayer_mock/base/types/mod.rs @@ -0,0 +1,8 @@ +pub mod aliases; +pub mod chain; +pub mod events; +pub mod height; +pub mod message; +pub mod packet; +pub mod runtime; +pub mod state; diff --git a/crates/relayer-mock/src/relayer_mock/base/types/packet.rs b/crates/relayer-mock/src/relayer_mock/base/types/packet.rs new file mode 100644 index 0000000000..b15481c565 --- /dev/null +++ b/crates/relayer-mock/src/relayer_mock/base/types/packet.rs @@ -0,0 +1,60 @@ +use alloc::string::String; +use std::fmt::Display; + +use super::events::SendPacketEvent; +use crate::relayer_mock::base::types::aliases::MockTimestamp; +use crate::relayer_mock::base::types::height::Height; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct PacketKey { + pub src_channel_id: String, + pub src_port_id: String, + pub dst_channel_id: String, + pub dst_port_id: String, + pub sequence: u128, + pub timeout_height: Height, + pub timeout_timestamp: MockTimestamp, +} + +impl Display for PacketKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "PacketKey{{ src_channel_id: {}, dst_channel_id: {}, src_port_id: {}, dst_port_id: {}, sequence: {}, timeout_height: {}, timeout_timestamp: {} }}", self.src_channel_id, self.dst_channel_id, self.src_port_id, self.dst_port_id, self.sequence, self.timeout_height, self.timeout_timestamp)?; + Ok(()) + } +} + +impl From for PacketKey { + fn from(e: SendPacketEvent) -> Self { + PacketKey { + src_channel_id: e.src_channel_id, + src_port_id: e.src_port_id, + dst_channel_id: e.dst_channel_id, + dst_port_id: e.dst_port_id, + sequence: e.sequence, + timeout_height: e.timeout_height, + timeout_timestamp: e.timeout_timestamp, + } + } +} + +impl PacketKey { + pub fn new( + src_channel_id: String, + src_port_id: String, + dst_channel_id: String, + dst_port_id: String, + sequence: u128, + timeout_height: Height, + timeout_timestamp: MockTimestamp, + ) -> Self { + Self { + src_channel_id, + src_port_id, + dst_channel_id, + dst_port_id, + sequence, + timeout_height, + timeout_timestamp, + } + } +} diff --git a/crates/relayer-mock/src/relayer_mock/base/types/runtime.rs b/crates/relayer-mock/src/relayer_mock/base/types/runtime.rs new file mode 100644 index 0000000000..38552798e1 --- /dev/null +++ b/crates/relayer-mock/src/relayer_mock/base/types/runtime.rs @@ -0,0 +1,63 @@ +use alloc::boxed::Box; +use alloc::sync::Arc; +use core::time::Duration; +use ibc_relayer_components::core::traits::error::HasErrorType; +use ibc_relayer_components::runtime::traits::sleep::CanSleep; +use ibc_relayer_components::runtime::traits::time::HasTime; + +use async_trait::async_trait; +use ibc_relayer_runtime::types::error::Error as TokioError; + +use crate::relayer_mock::base::types::aliases::MockTimestamp; +use crate::relayer_mock::util::clock::MockClock; + +pub struct MockRuntimeContext { + pub clock: Arc, +} + +impl MockRuntimeContext { + pub fn new(clock: Arc) -> Self { + Self { clock } + } + + pub fn get_time(&self) -> MockTimestamp { + self.clock.get_timestamp() + } +} + +impl Clone for MockRuntimeContext { + fn clone(&self) -> Self { + let clock = self.clock.clone(); + Self::new(clock) + } +} + +impl HasErrorType for MockRuntimeContext { + type Error = TokioError; +} + +#[async_trait] +impl CanSleep for MockRuntimeContext { + async fn sleep(&self, duration: Duration) { + // Increment the shared MockClock by the duration is milliseconds. + if self + .clock + .increment_timestamp(MockTimestamp(duration.as_millis())) + .is_err() + { + tracing::warn!("MockClock failed to sleep for {}ms", duration.as_millis()); + } + } +} + +impl HasTime for MockRuntimeContext { + type Time = MockTimestamp; + + fn now(&self) -> Self::Time { + self.get_time() + } + + fn duration_since(current_time: &Self::Time, other_time: &Self::Time) -> Duration { + Duration::from_millis((current_time.0 - other_time.0) as u64) + } +} diff --git a/crates/relayer-mock/src/relayer_mock/base/types/state.rs b/crates/relayer-mock/src/relayer_mock/base/types/state.rs new file mode 100644 index 0000000000..4a4a00394d --- /dev/null +++ b/crates/relayer-mock/src/relayer_mock/base/types/state.rs @@ -0,0 +1,119 @@ +use std::{collections::HashMap, fmt::Display}; + +use super::aliases::MockTimestamp; +use crate::relayer_mock::base::types::aliases::PacketUID; +use crate::relayer_mock::base::types::height::Height; +use crate::relayer_mock::base::types::packet::PacketKey; + +/// A snapshot of the mock chain's state at a point in time. +#[derive(Clone, Default, Debug, PartialEq, Eq)] +pub struct State { + /// The packets that the mock chain has sent. + sent_packets: HashMap, + /// The packets that the mock chain has received. + recv_packets: HashMap, + /// The ack_packets that the mock chain has received. + ack_packets: HashMap, +} + +impl Display for State { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "Sent packets:")?; + for key in self.sent_packets.keys() { + let packet = self + .sent_packets + .get(key) + .expect("error retrieving packet and height from sent_packets"); + writeln!( + f, + "\tPacket({}, {}, {}) with sequence {} and height {}", + key.0, key.1, key.2, packet.0.sequence, packet.1 + )?; + } + writeln!(f, "Received packets:")?; + for key in self.recv_packets.keys() { + let packet = self + .recv_packets + .get(key) + .expect("error retrieving packet and height from recv_packets"); + writeln!( + f, + "\tPacket({}, {}, {}) with sequence {} and height {}", + key.0, key.1, key.2, packet.0.sequence, packet.1 + )?; + } + writeln!(f, "Acknowledged packets:")?; + for key in self.ack_packets.keys() { + let packet = self + .ack_packets + .get(key) + .expect("error retrieving packet and height from ack_packets"); + writeln!( + f, + "\tPacket({}, {}, {}) with sequence {} and height {}", + key.0, key.1, key.2, packet.0.sequence, packet.1 + )?; + } + Ok(()) + } +} + +impl State { + pub fn check_sent(&self, packet_uid: PacketUID) -> bool { + self.sent_packets.get(&packet_uid).is_some() + } + + pub fn check_received(&self, packet_uid: PacketUID) -> bool { + self.recv_packets.get(&packet_uid).is_some() + } + + pub fn check_acknowledged(&self, packet_uid: PacketUID) -> bool { + self.ack_packets.get(&packet_uid).is_some() + } + + /// Checks that the given packet has timed out by comparing whether + /// the packet's timeout timestamp has exceeded the chain's current + /// timestamp or the packet's timeout height has exceeded the chain's + /// height. + pub fn check_timeout( + &self, + packet: PacketKey, + current_height: Height, + current_timestamp: MockTimestamp, + ) -> bool { + // A packet has not timed out if its timeout height has not exceeded the chain's + // height AND its timeout timestamp has not exceeded the chain's timestamp. + if current_height <= packet.timeout_height && current_timestamp <= packet.timeout_timestamp + { + return false; + } + + // also check that the packet has not been previously received + !self.check_received(( + packet.dst_port_id.clone(), + packet.dst_channel_id.clone(), + packet.sequence, + )) + } + + pub fn update_sent(&mut self, packet_uid: PacketUID, packet: PacketKey, height: Height) { + self.sent_packets.insert(packet_uid, (packet, height)); + } + + pub fn update_received(&mut self, packet_uid: PacketUID, packet: PacketKey, height: Height) { + self.recv_packets.insert(packet_uid, (packet, height)); + } + + pub fn update_acknowledged( + &mut self, + packet_uid: PacketUID, + packet: PacketKey, + height: Height, + ) { + self.ack_packets.insert(packet_uid, (packet, height)); + } + + pub fn get_received(&self, packet_uid: PacketUID) -> Option<&(PacketKey, Height)> { + self.recv_packets.get(&packet_uid) + } +} diff --git a/crates/relayer-mock/src/relayer_mock/components.rs b/crates/relayer-mock/src/relayer_mock/components.rs new file mode 100644 index 0000000000..6f7590c654 --- /dev/null +++ b/crates/relayer-mock/src/relayer_mock/components.rs @@ -0,0 +1,55 @@ +use ibc_relayer_components::relay::components::message_senders::chain_sender::SendIbcMessagesToChain; +use ibc_relayer_components::relay::components::message_senders::update_client::SendIbcMessagesWithUpdateClient; +use ibc_relayer_components::relay::components::update_client::skip::SkipUpdateClient; +use ibc_relayer_components::relay::components::update_client::wait::WaitUpdateClient; +use ibc_relayer_components::relay::components::packet_relayers::ack::base_ack_packet::BaseAckPacketRelayer; +use ibc_relayer_components::relay::components::packet_relayers::general::full_relay::FullCycleRelayer; +use ibc_relayer_components::relay::components::packet_relayers::receive::base_receive_packet::BaseReceivePacketRelayer; +use ibc_relayer_components::relay::components::packet_relayers::receive::skip_received_packet::SkipReceivedPacketRelayer; +use ibc_relayer_components::relay::components::packet_relayers::timeout_unordered::timeout_unordered_packet::BaseTimeoutUnorderedPacketRelayer; +use ibc_relayer_components::relay::traits::components::ibc_message_sender::{MainSink, IbcMessageSenderComponent}; +use ibc_relayer_components::relay::traits::components::update_client_message_builder::UpdateClientMessageBuilderComponent; +use ibc_relayer_components::relay::traits::components::packet_relayer::PacketRelayerComponent; +use ibc_relayer_components::relay::traits::components::packet_relayers::ack_packet::AckPacketRelayerComponent; +use ibc_relayer_components::relay::traits::components::packet_relayers::receive_packet::ReceivePacketRelayerComponnent; +use ibc_relayer_components::relay::traits::components::packet_relayers::timeout_unordered_packet::TimeoutUnorderedPacketRelayerComponent; + +use crate::relayer_mock::base::impls::relay::MockBuildUpdateClientMessage; + +pub struct MockComponents; + +ibc_relayer_components::delegate_component!( + IbcMessageSenderComponent, + MockComponents, + SendIbcMessagesWithUpdateClient, +); + +ibc_relayer_components::delegate_component!( + PacketRelayerComponent, + MockComponents, + FullCycleRelayer, +); + +ibc_relayer_components::delegate_component!( + ReceivePacketRelayerComponnent, + MockComponents, + SkipReceivedPacketRelayer, +); + +ibc_relayer_components::delegate_component!( + AckPacketRelayerComponent, + MockComponents, + BaseAckPacketRelayer, +); + +ibc_relayer_components::delegate_component!( + TimeoutUnorderedPacketRelayerComponent, + MockComponents, + BaseTimeoutUnorderedPacketRelayer, +); + +ibc_relayer_components::delegate_component!( + UpdateClientMessageBuilderComponent, + MockComponents, + SkipUpdateClient>, +); diff --git a/crates/relayer-mock/src/relayer_mock/contexts/chain.rs b/crates/relayer-mock/src/relayer_mock/contexts/chain.rs new file mode 100644 index 0000000000..0979716824 --- /dev/null +++ b/crates/relayer-mock/src/relayer_mock/contexts/chain.rs @@ -0,0 +1,446 @@ +//! A HashMap is used to represent the State of the +//! chain. +//! The current_state is the ChainState at all Heights, up to the latest Height. +//! The consensus_states is a HashMap>. +//! This is used to check the state of a specific client, at a +//! specific height. +//! Usually the consensus_states would use the root hashes of a Merle Tree, +//! but since the MockChain is used for testing and will have a small number +//! of states, the whole state is used. This avoids needing to implement +//! Merkle Trees and Proofs. + +use alloc::string::String; +use std::collections::hash_map::Entry; +use std::sync::Mutex; +use std::vec; +use std::{collections::HashMap, sync::Arc}; + +use eyre::eyre; + +use crate::relayer_mock::base::error::{BaseError, Error}; +use crate::relayer_mock::base::types::aliases::{ + ChainState, ChannelId, ClientId, MockTimestamp, PortId, Sequence, StateStore, +}; +use crate::relayer_mock::base::types::events::Event; +use crate::relayer_mock::base::types::height::Height; +use crate::relayer_mock::base::types::message::Message as MockMessage; +use crate::relayer_mock::base::types::runtime::MockRuntimeContext; +use crate::relayer_mock::base::types::{ + height::Height as MockHeight, packet::PacketKey, state::State, +}; +use crate::relayer_mock::util::clock::MockClock; +use crate::relayer_mock::util::mutex::MutexUtil; + +pub struct MockChainContext { + pub name: String, + pub past_chain_states: Arc>, + pub current_height: Arc>, + pub current_state: Arc>, + pub consensus_states: Arc>>, + pub channel_to_client: Arc>>, + pub runtime: MockRuntimeContext, +} + +impl MockChainContext { + pub fn new(name: String, clock: Arc) -> Self { + let runtime = MockRuntimeContext::new(clock); + let chain_state = State::default(); + let initial_state: StateStore = + HashMap::from([(MockHeight::default(), chain_state.clone())]); + Self { + name, + past_chain_states: Arc::new(Mutex::new(initial_state)), + current_height: Arc::new(Mutex::new(Height(1))), + current_state: Arc::new(Mutex::new(chain_state)), + consensus_states: Arc::new(Mutex::new(HashMap::new())), + channel_to_client: Arc::new(Mutex::new(HashMap::new())), + runtime, + } + } + + pub fn name(&self) -> &String { + &self.name + } + + pub fn runtime(&self) -> &MockRuntimeContext { + &self.runtime + } + + // Get the current height of the chain. + pub fn get_current_height(&self) -> MockHeight { + let locked_current_height = self.current_height.acquire_mutex(); + *locked_current_height + } + + /// Get the current state of the chain. + pub fn get_current_state(&self) -> State { + let locked_current_state = self.current_state.acquire_mutex(); + let state = locked_current_state; + state.clone() + } + + /// Get the client ID from a channel ID. + pub fn get_client_from_channel(&self, channel_id: &ChannelId) -> Option { + let locked_channel_to_client = self.channel_to_client.acquire_mutex(); + locked_channel_to_client.get(channel_id).cloned() + } + + /// Query the chain state at a given Height. This is used to see which receive and + /// acknowledgment messages have been processed by the Mock Chain. + pub fn query_state_at_height(&self, height: MockHeight) -> Result { + let locked_past_chain_states = self.past_chain_states.acquire_mutex(); + let state = locked_past_chain_states + .get(&height) + .ok_or_else(|| BaseError::no_chain_state(self.name().to_string(), height.0))?; + Ok(state.clone()) + } + + /// Query the consensus state of a Client at a given height. + /// Refer to the `queryConsensusState` of ICS002. + pub fn query_consensus_state_at_height( + &self, + client_id: ClientId, + height: MockHeight, + ) -> Result { + let locked_consensus_states = self.consensus_states.acquire_mutex(); + let client_consensus_states = locked_consensus_states + .get(&client_id) + .ok_or_else(|| BaseError::no_consensus_state(client_id.clone()))?; + let client_consensus_state = client_consensus_states + .get(&height) + .ok_or_else(|| BaseError::no_consensus_state_at_height(client_id, height.0))?; + Ok(client_consensus_state.clone()) + } + + /// In order to get a client ID from a channel ID, the mapping must be manually + /// registered for the MockChain. + pub fn map_channel_to_client(&self, channel_id: ChannelId, client_id: ClientId) { + let mut locked_channel_to_client = self.channel_to_client.acquire_mutex(); + locked_channel_to_client.insert(channel_id, client_id); + } + + /// Insert a new Consensus State for a given Client at a given Height. + /// + /// Returns an error if there is already a consensus state for the client at the given height + /// which is different from the given state; a chain is not allowed to have two different + /// consensus states at the same height. + /// + /// This is used for the `updateClient` of ICS002. It is considered an act of misbehaviour + /// when a chain attempts to update an already-existing consensus state with a different value. + pub fn insert_consensus_state( + &self, + client_id: ClientId, + height: MockHeight, + state: ChainState, + ) -> Result<(), Error> { + let mut locked_consensus_states = self.consensus_states.acquire_mutex(); + let client_consensus_states = match locked_consensus_states.get(&client_id) { + Some(ccs) => { + let mut new_client_consensus_states = ccs.clone(); + match new_client_consensus_states.entry(height) { + Entry::Occupied(o) => { + // Check if the existing Consensus State at the given height differs + // from the one passed. + if o.get() != &state { + return Err(BaseError::consensus_divergence(client_id, height.0).into()); + } + } + Entry::Vacant(_) => { + new_client_consensus_states.insert(height, state); + } + }; + new_client_consensus_states + } + // If the Client doesn't have any Consensus State, create one with the given + // state as initial Consensus State. + None => HashMap::from([(height, state)]), + }; + // Update the Consensus States of the Chain. + locked_consensus_states.insert(client_id, client_consensus_states); + + Ok(()) + } + + /// Insert the current_state at Height + 1. This is used to advance the chain's Height by 1 + /// without changing its state. + pub fn new_block(&self) -> Result<(), Error> { + // Retrieve the current state + let current_state = self.get_current_state(); + + // Update the current_state of the Chain, which will increase the Height by 1. + self.update_current_state(current_state)?; + + Ok(()) + } + + /// Sending a packet adds a new ChainState with the sent packet information + /// at a Height + 1. + pub fn send_packet(&self, height: Height, packet: PacketKey) -> Result<(), Error> { + // Retrieve the current_state and update it with the newly sent packet + let mut new_state = self.get_current_state(); + + new_state.update_sent( + ( + packet.src_port_id.clone(), + packet.src_channel_id.clone(), + packet.sequence, + ), + packet, + height, + ); + + // Update the current_state of the Chain + self.update_current_state(new_state)?; + + Ok(()) + } + + /// Before receiving a packet, the consensus state is used to verify that the packet + /// was sent by the source chain. And the packet timeout is verified. + /// Receiving a packet adds a new ChainState with the received packet information + /// at a Height + 1. + pub fn receive_packet( + &self, + height: Height, + packet: PacketKey, + mut current_state: State, + ) -> Result { + // Verify via the consensus state that the packet was sent by the source chain. + let client_id = self + .get_client_from_channel(&packet.dst_channel_id) + .ok_or_else(|| { + BaseError::no_client_for_channel( + packet.src_channel_id.clone(), + self.name().to_string(), + ) + })?; + + let client_consensus = + self.query_consensus_state_at_height(client_id, height.increment())?; + + if !client_consensus.check_sent(( + packet.src_port_id.clone(), + packet.src_channel_id.clone(), + packet.sequence, + )) { + return Err(BaseError::generic(eyre!( + "chain `{}` got a RecvPacket, but client state doesn't have the packet as sent", + self.name() + )) + .into()); + } + + // Check that the packet is not timed out. Current height < packet timeout height. + let current_height = self.get_current_height(); + let current_time = self.runtime.get_time(); + + if current_state.check_timeout(packet.clone(), current_height, current_time.clone()) { + return Err(BaseError::timeout_receive( + self.name().to_string(), + packet.timeout_height.0, + current_height.0, + packet.timeout_timestamp.0, + current_time.0, + ) + .into()); + } + + // Update the state with the newly received packet + // This will not commit the updated state to the chain + current_state.update_received( + ( + packet.dst_port_id.clone(), + packet.dst_channel_id.clone(), + packet.sequence, + ), + packet, + height, + ); + + Ok(current_state) + } + + /// Receiving an acknowledgement adds a new ChainState with the received acknowledgement + /// information at a Height + 1. + pub fn acknowledge_packet( + &self, + height: Height, + packet: PacketKey, + mut current_state: State, + ) -> Result { + // Verify that with the consensus state that the packet was received by the destination chain. + let client_id = self + .get_client_from_channel(&packet.src_channel_id) + .ok_or_else(|| { + BaseError::no_client_for_channel( + packet.src_channel_id.clone(), + self.name().to_string(), + ) + })?; + + let client_consensus = + self.query_consensus_state_at_height(client_id, height.increment())?; + + if !client_consensus.check_received(( + packet.dst_port_id.clone(), + packet.dst_channel_id.clone(), + packet.sequence, + )) { + return Err(BaseError::generic(eyre!( + "chain `{}` got a AckPacket, but client state doesn't have the packet as received", + self.name() + )) + .into()); + } + + // Update the current state with the newly received acknowledgement + current_state.update_acknowledged( + ( + packet.src_port_id.clone(), + packet.src_channel_id.clone(), + packet.sequence, + ), + packet, + height, + ); + + Ok(current_state) + } + + /// Receiving a timed out packet adds a new ChainState with the timed out packet + /// information at a Height + 1. + pub fn timeout_packet( + &self, + height: Height, + packet: PacketKey, + current_state: State, + ) -> Result { + // Verify that with the consensus state that the packet was not received by the destination chain. + let client_id = self + .get_client_from_channel(&packet.src_channel_id) + .ok_or_else(|| { + BaseError::no_client_for_channel( + packet.src_channel_id.clone(), + self.name().to_string(), + ) + })?; + + let client_consensus = + self.query_consensus_state_at_height(client_id, height.increment())?; + + if client_consensus.check_received(( + packet.dst_port_id.clone(), + packet.dst_channel_id.clone(), + packet.sequence, + )) { + return Err(BaseError::generic(eyre!( + "chain `{}` got a TimeoutPacket, but client state received the packet as received", + self.name() + )) + .into()); + } + + Ok(current_state) + } + + pub fn get_received_packet_information( + &self, + port_id: PortId, + channel_id: ChannelId, + sequence: Sequence, + ) -> Option<(PacketKey, Height)> { + let state = self.get_current_state(); + state.get_received((port_id, channel_id, sequence)).cloned() + } + + #[allow(clippy::too_many_arguments)] + pub fn build_send_packet( + &self, + src_channel_id: String, + src_port_id: String, + dst_channel_id: String, + dst_port_id: String, + sequence: u128, + timeout_height: Height, + timeout_timestamp: MockTimestamp, + ) -> PacketKey { + PacketKey::new( + src_channel_id, + src_port_id, + dst_channel_id, + dst_port_id, + sequence, + timeout_height, + timeout_timestamp, + ) + } + + /// Update the chain's current_state and past_chain_states at the same time to ensure + /// they are always synchronized. Both the state and runtime clock are incremented by + /// 1000 milliseconds. + /// + /// The MockChain must have one and only one ChainState at every height. + fn update_current_state(&self, state: State) -> Result<(), Error> { + let latest_height = self.get_current_height(); + let new_height = latest_height.increment(); + + // Update current state + let mut locked_current_state = self.current_state.acquire_mutex(); + *locked_current_state = state; + + // Update current height. + let mut locked_current_height = self.current_height.acquire_mutex(); + *locked_current_height = new_height; + + self.runtime + .clock + .increment_timestamp(MockTimestamp::default())?; + + // After inserting the new state in the current_state, update the past_chain_states + // at the given height. + let mut locked_past_chain_states = self.past_chain_states.acquire_mutex(); + locked_past_chain_states.insert(new_height, locked_current_state.clone()); + + Ok(()) + } + + /// If the message is a `SendPacket`, update the received packets, + /// and add a `RecvPacket` event to the returned array of events. + /// If the message is an `AckPacket`, update the received acknowledgment + /// packets. + /// If the message is an `UpdateClient`, update the consensus state. + /// + /// When a RecvPacket and AckPacket are received, verify that the client + /// state has respectively sent the message and received the message. + /// + /// The chain state will only be updated if all messages are processed + /// successfully. + pub fn process_messages(&self, messages: Vec) -> Result>, Error> { + let mut res = vec![]; + let mut current_state = self.get_current_state(); + + for m in messages { + match m { + MockMessage::RecvPacket(height, packet) => { + current_state = self.receive_packet(height, packet, current_state)?; + res.push(vec![Event::WriteAcknowledgment(height)]); + } + MockMessage::AckPacket(height, packet) => { + current_state = self.acknowledge_packet(height, packet, current_state)?; + res.push(vec![]); + } + MockMessage::UpdateClient(client_id, height, state) => { + self.insert_consensus_state(client_id, height, state)?; + res.push(vec![]); + } + MockMessage::TimeoutPacket(height, packet) => { + current_state = self.timeout_packet(height, packet, current_state)?; + } + } + } + + self.update_current_state(current_state)?; + + Ok(res) + } +} diff --git a/crates/relayer-mock/src/relayer_mock/contexts/mod.rs b/crates/relayer-mock/src/relayer_mock/contexts/mod.rs new file mode 100644 index 0000000000..d8d5e5ef66 --- /dev/null +++ b/crates/relayer-mock/src/relayer_mock/contexts/mod.rs @@ -0,0 +1,2 @@ +pub mod chain; +pub mod relay; diff --git a/crates/relayer-mock/src/relayer_mock/contexts/relay.rs b/crates/relayer-mock/src/relayer_mock/contexts/relay.rs new file mode 100644 index 0000000000..086c6ed41d --- /dev/null +++ b/crates/relayer-mock/src/relayer_mock/contexts/relay.rs @@ -0,0 +1,47 @@ +use alloc::string::String; +use std::sync::Arc; + +use crate::relayer_mock::base::types::runtime::MockRuntimeContext; +use crate::relayer_mock::contexts::chain::MockChainContext; + +pub struct MockRelayContext { + pub src_chain: Arc, + pub dst_chain: Arc, + pub src_to_dst_client: String, + pub dst_to_src_client: String, + pub runtime: MockRuntimeContext, +} + +impl MockRelayContext { + pub fn new( + src_chain: Arc, + dst_chain: Arc, + src_to_dst_client: String, + dst_to_src_client: String, + runtime: MockRuntimeContext, + ) -> Self { + Self { + src_chain, + dst_chain, + src_to_dst_client, + dst_to_src_client, + runtime, + } + } + + pub fn src_chain(&self) -> &Arc { + &self.src_chain + } + + pub fn dst_chain(&self) -> &Arc { + &self.dst_chain + } + + pub fn src_to_dst_client(&self) -> &String { + &self.src_to_dst_client + } + + pub fn dst_to_src_client(&self) -> &String { + &self.dst_to_src_client + } +} diff --git a/crates/relayer-mock/src/relayer_mock/mod.rs b/crates/relayer-mock/src/relayer_mock/mod.rs new file mode 100644 index 0000000000..9629c8c502 --- /dev/null +++ b/crates/relayer-mock/src/relayer_mock/mod.rs @@ -0,0 +1,4 @@ +pub mod base; +pub mod components; +pub mod contexts; +pub mod util; diff --git a/crates/relayer-mock/src/relayer_mock/util/clock.rs b/crates/relayer-mock/src/relayer_mock/util/clock.rs new file mode 100644 index 0000000000..715e510773 --- /dev/null +++ b/crates/relayer-mock/src/relayer_mock/util/clock.rs @@ -0,0 +1,48 @@ +//! MockClock is a simple structure which allows multiple +//! entities to safely access a shared timestamp, represented by +//! a u128. The timestamp needs to be manually incremented. + +use std::sync::{Arc, Mutex}; + +use eyre::eyre; + +use crate::relayer_mock::base::error::{BaseError, Error}; +use crate::relayer_mock::base::types::aliases::MockTimestamp; +use crate::relayer_mock::util::mutex::MutexUtil; + +pub struct MockClock { + timestamp: Arc>, +} + +impl Default for MockClock { + fn default() -> Self { + Self { + timestamp: Arc::new(Mutex::new(MockTimestamp(1))), + } + } +} + +impl MockClock { + pub fn increment_timestamp(&self, duration: MockTimestamp) -> Result<(), Error> { + let mut locked_timestamp = self.timestamp.acquire_mutex(); + *locked_timestamp = locked_timestamp + .0 + .checked_add(duration.0) + .ok_or_else(|| { + BaseError::generic(eyre!( + "overflow when adding {} to {}", + locked_timestamp, + duration, + )) + })? + .into(); + + Ok(()) + } + + pub fn get_timestamp(&self) -> MockTimestamp { + let locked_timestamp = self.timestamp.acquire_mutex(); + + (*locked_timestamp).clone() + } +} diff --git a/crates/relayer-mock/src/relayer_mock/util/mod.rs b/crates/relayer-mock/src/relayer_mock/util/mod.rs new file mode 100644 index 0000000000..b1c8d39e24 --- /dev/null +++ b/crates/relayer-mock/src/relayer_mock/util/mod.rs @@ -0,0 +1,2 @@ +pub mod clock; +pub mod mutex; diff --git a/crates/relayer-mock/src/relayer_mock/util/mutex.rs b/crates/relayer-mock/src/relayer_mock/util/mutex.rs new file mode 100644 index 0000000000..dd843a68d1 --- /dev/null +++ b/crates/relayer-mock/src/relayer_mock/util/mutex.rs @@ -0,0 +1,17 @@ +//! MutexUtil is a helper trait to simplify the error handling +//! when locking a Mutex. + +use std::sync::{Arc, Mutex, MutexGuard}; + +pub trait MutexUtil { + fn acquire_mutex(&self) -> MutexGuard; +} + +impl MutexUtil for Arc> { + fn acquire_mutex(&self) -> MutexGuard { + match self.lock() { + Ok(locked_mutex) => locked_mutex, + Err(e) => panic!("poisoned mutex: {e}"), + } + } +} diff --git a/crates/relayer-mock/src/tests/mock.rs b/crates/relayer-mock/src/tests/mock.rs new file mode 100644 index 0000000000..fa10e8d50b --- /dev/null +++ b/crates/relayer-mock/src/tests/mock.rs @@ -0,0 +1,458 @@ +use alloc::string::String; +use ibc_relayer_components::chain::traits::queries::write_ack::CanQueryWriteAcknowledgement; +use ibc_relayer_components::relay::traits::chains::HasRelayChains; +use std::time::Duration; + +use ibc_relayer_components::relay::traits::components::packet_relayer::CanRelayPacket; +use ibc_relayer_components::runtime::traits::sleep::CanSleep; +use tracing::info; + +use crate::relayer_mock::base::error::Error; +use crate::relayer_mock::base::types::aliases::MockTimestamp; +use crate::relayer_mock::base::types::events::Event; +use crate::relayer_mock::base::types::height::Height as MockHeight; +use crate::relayer_mock::base::types::message::Message as MockMessage; +use crate::tests::util::context::build_mock_relay_context; + +#[tokio::test] +async fn test_mock_chain_relay() -> Result<(), Error> { + let (relay_context, src_chain, dst_chain) = build_mock_relay_context(); + + let src_channel_id = "channel-0".to_owned(); + let dst_channel_id = "channel-1".to_owned(); + + let source_client_id = relay_context.src_client_id().clone(); + let destination_client_id = relay_context.dst_client_id().clone(); + + src_chain.map_channel_to_client(src_channel_id.clone(), source_client_id); + dst_chain.map_channel_to_client(dst_channel_id.clone(), destination_client_id); + + let packet = src_chain.build_send_packet( + src_channel_id, + String::from("transfer"), + dst_channel_id, + String::from("transfer"), + 1, + MockHeight(10), + MockTimestamp(60000), + ); + + { + info!("Check that the packet has not yet been received"); + + let state = dst_chain.get_current_state(); + + assert!( + !state.check_received(( + packet.dst_port_id.clone(), + packet.dst_channel_id.clone(), + packet.sequence + )), + "Packet already received on destination chain before relaying it" + ); + } + + let height = src_chain.get_current_height(); + + // Chain submits the transaction to be relayed + src_chain.send_packet(height, packet.clone())?; + + let events = relay_context.relay_packet(&packet).await; + + assert!(events.is_ok(), "{}", events.err().unwrap()); + + { + info!("Check that the packet has been received by the destination chain"); + + let state = dst_chain.get_current_state(); + + assert!( + state.check_received(( + packet.dst_port_id.clone(), + packet.dst_channel_id.clone(), + packet.sequence + )), + "Packet not received on destination chain" + ); + } + + { + info!("Check that the acknowledgment has been received by the source chain"); + + let state = src_chain.get_current_state(); + + assert!( + state.check_acknowledged((packet.src_port_id, packet.src_channel_id, packet.sequence)), + "Acknowledgment not found on source chain" + ); + } + + Ok(()) +} + +#[tokio::test] +async fn test_mock_chain_timeout_timestamp() -> Result<(), Error> { + let (relay_context, src_chain, dst_chain) = build_mock_relay_context(); + + let src_channel_id = "channel-0".to_owned(); + let dst_channel_id = "channel-1".to_owned(); + + let source_client_id = relay_context.src_client_id().clone(); + let destination_client_id = relay_context.dst_client_id().clone(); + + src_chain.map_channel_to_client(src_channel_id.clone(), source_client_id); + dst_chain.map_channel_to_client(dst_channel_id.clone(), destination_client_id); + + let packet = src_chain.build_send_packet( + src_channel_id, + String::from("transfer"), + dst_channel_id, + String::from("transfer"), + 1, + MockHeight(10), + MockTimestamp(60000), + ); + + { + info!("Check that the packet has not yet been received"); + + let state = dst_chain.get_current_state(); + + assert!( + !state.check_received(( + packet.dst_port_id.clone(), + packet.dst_channel_id.clone(), + packet.sequence + )), + "Packet already received on destination chain before relaying it" + ); + } + + let src_height = src_chain.get_current_height(); + let runtime = &relay_context.runtime; + + src_chain.send_packet(src_height, packet.clone())?; + + // Sleep enough to trigger timeout from timestamp timeout + runtime.sleep(Duration::from_millis(70000)).await; + + let events = relay_context.relay_packet(&packet).await; + + assert!(events.is_ok(), "{}", events.err().unwrap()); + + { + info!("Check that the packet has not been received by the destination chain"); + + let state = dst_chain.get_current_state(); + + assert!( + !state.check_received(( + packet.dst_port_id.clone(), + packet.dst_channel_id.clone(), + packet.sequence + )), + "Packet received on destination chain, but should have timed out" + ); + } + + { + info!("Check that the timeout packet been received by the source chain"); + + let state = src_chain.get_current_state(); + let elapsed_time = runtime.get_time(); + + assert!( + state.check_timeout(packet, src_height, elapsed_time), + "Packet should be registered as timed out" + ); + } + + Ok(()) +} + +#[tokio::test] +async fn test_mock_chain_timeout_height() -> Result<(), Error> { + let (relay_context, src_chain, dst_chain) = build_mock_relay_context(); + + let src_channel_id = "channel-0".to_owned(); + let dst_channel_id = "channel-1".to_owned(); + + let source_client_id = relay_context.src_client_id().clone(); + let destination_client_id = relay_context.dst_client_id().clone(); + + src_chain.map_channel_to_client(src_channel_id.clone(), source_client_id); + dst_chain.map_channel_to_client(dst_channel_id.clone(), destination_client_id); + + let packet = src_chain.build_send_packet( + src_channel_id, + String::from("transfer"), + dst_channel_id, + String::from("transfer"), + 1, + MockHeight(3), + MockTimestamp(60000), + ); + + { + info!("Check that the packet has not yet been received"); + + let state = dst_chain.get_current_state(); + + assert!( + !state.check_received(( + packet.dst_port_id.clone(), + packet.dst_channel_id.clone(), + packet.sequence + )), + "Packet already received on destination chain before relaying it" + ); + } + + let src_height = src_chain.get_current_height(); + + src_chain.send_packet(src_height, packet.clone())?; + + // Increase height of destination chain to trigger Height timeout + for _ in 0..3 { + dst_chain.new_block()?; + } + + let events = relay_context.relay_packet(&packet).await; + + assert!(events.is_ok(), "{}", events.err().unwrap()); + + { + info!("Check that the packet has been received by the destination chain"); + + let state = dst_chain.get_current_state(); + + assert!( + !state.check_received(( + packet.dst_port_id.clone(), + packet.dst_channel_id.clone(), + packet.sequence + )), + "Packet received on destination chain, but should have timed out" + ); + } + + { + info!("Check that the timeout packet has been received by the source chain"); + + let state = src_chain.get_current_state(); + let dst_height = dst_chain.get_current_height(); + let runtime = &relay_context.runtime; + let elapsed_time = runtime.get_time(); + + assert!( + state.check_timeout(packet, dst_height, elapsed_time), + "Packet should be registered as timed out" + ); + } + + Ok(()) +} + +#[tokio::test] +async fn test_mock_chain_query_write_ack() -> Result<(), Error> { + let (relay_context, src_chain, dst_chain) = build_mock_relay_context(); + + let src_channel_id = "channel-0".to_owned(); + let dst_channel_id = "channel-1".to_owned(); + + let source_client_id = relay_context.src_client_id().clone(); + let src_port_id = String::from("transfer"); + let destination_client_id = relay_context.dst_client_id().clone(); + let dst_port_id = String::from("transfer"); + + src_chain.map_channel_to_client(src_channel_id.clone(), source_client_id); + dst_chain.map_channel_to_client(dst_channel_id.clone(), destination_client_id); + + let packet = src_chain.build_send_packet( + src_channel_id.clone(), + src_port_id.clone(), + dst_channel_id.clone(), + dst_port_id.clone(), + 1, + MockHeight(10), + MockTimestamp(60000), + ); + + { + info!("Check that the packet has not yet been received"); + + let state = dst_chain.get_current_state(); + + assert!( + !state.check_received(( + packet.dst_port_id.clone(), + packet.dst_channel_id.clone(), + packet.sequence + )), + "Packet already received on destination chain before relaying it" + ); + + info!("Check that no WriteAcknowledgmentEvent is returned by query_write_ack"); + + let write_ack = dst_chain.query_write_acknowledgement_event(&packet).await; + assert!( + write_ack.is_ok(), + "query_write_acknowledgement_event returned an error" + ); + assert!( + write_ack.unwrap().is_none(), + "WriteAcknowlegmentEvent should be None as the chain hasn't received the packet yet" + ); + } + + let height = src_chain.get_current_height(); + + src_chain.send_packet(height, packet.clone())?; + + let events = relay_context.relay_packet(&packet).await; + + assert!(events.is_ok(), "{}", events.err().unwrap()); + + { + info!("Check that the packet has been received by the destination chain"); + + let state = dst_chain.get_current_state(); + + assert!( + state.check_received(( + packet.dst_port_id.clone(), + packet.dst_channel_id.clone(), + packet.sequence + )), + "Packet not received on destination chain" + ); + + info!("Check that a WriteAcknowledgmentEvent is returned by query_write_ack"); + + let write_ack = dst_chain.query_write_acknowledgement_event(&packet).await; + assert!( + write_ack.is_ok(), + "query_write_acknowledgement_event returned an error" + ); + assert!(write_ack.unwrap().is_some(), "A WriteAcknowlegmentEvent should be returned by query_write_acknowledgement_event since the chain received the packet"); + } + + { + info!("Check that the acknowledgment has been received by the source chain"); + + let state = src_chain.get_current_state(); + + assert!( + state.check_acknowledged((packet.src_port_id, packet.src_channel_id, packet.sequence)), + "Acknowledgment not found on source chain" + ); + } + + Ok(()) +} + +#[tokio::test] +async fn test_mock_chain_process_update_client_message() -> Result<(), Error> { + let (relay_context, src_chain, dst_chain) = build_mock_relay_context(); + + let src_channel_id = "channel-0".to_owned(); + let dst_channel_id = "channel-1".to_owned(); + + let source_client_id = relay_context.src_client_id().clone(); + let destination_client_id = relay_context.dst_client_id().clone(); + + src_chain.map_channel_to_client(src_channel_id, source_client_id.clone()); + dst_chain.map_channel_to_client(dst_channel_id, destination_client_id); + + let src_height = src_chain.get_current_height(); + let src_state = src_chain.get_current_state(); + + let update_client_message = vec![MockMessage::UpdateClient( + source_client_id.clone(), + src_height, + src_state, + )]; + + info!("Check that no consensus states have been added"); + + let src_consensus_state = + src_chain.query_consensus_state_at_height(source_client_id.clone(), src_height); + + assert!( + src_consensus_state.is_err(), + "Found a consensus state where there should have been none." + ); + + let events = src_chain.process_messages(update_client_message)?; + + assert_eq!( + events, + vec![vec![]], + "Found an Event where there should have been none." + ); + + let src_consensus_state = + src_chain.query_consensus_state_at_height(source_client_id, src_height); + + assert!( + src_consensus_state.is_ok(), + "Expected a consensus state, but found none." + ); + + Ok(()) +} + +#[tokio::test] +async fn test_mock_chain_process_recv_packet() -> Result<(), Error> { + let (relay_context, src_chain, dst_chain) = build_mock_relay_context(); + + let src_channel_id = "channel-0".to_owned(); + let dst_channel_id = "channel-1".to_owned(); + + let source_client_id = relay_context.src_client_id().clone(); + let destination_client_id = relay_context.dst_client_id().clone(); + + src_chain.map_channel_to_client(dst_channel_id.clone(), source_client_id.clone()); + dst_chain.map_channel_to_client(dst_channel_id.clone(), destination_client_id); + + let packet = src_chain.build_send_packet( + src_channel_id, + String::from("transfer"), + dst_channel_id, + String::from("transfer"), + 1, + MockHeight(10), + MockTimestamp(60000), + ); + + let src_height = src_chain.get_current_height(); + + src_chain.send_packet(src_height, packet.clone())?; + + let src_state = src_chain.get_current_state(); + + let recv_packet_message = vec![ + MockMessage::UpdateClient(source_client_id, src_height.increment(), src_state), + MockMessage::RecvPacket(src_height, packet), + ]; + + let events = src_chain.process_messages(recv_packet_message)?; + + assert_eq!( + events.len(), + 2, + "Expected `process_messages` to return 2 events" + ); + assert_eq!( + events.first(), + Some(&vec![]), + "Expected first event returned from processing UpdateClient message to be empty" + ); + assert_eq!( + events.last(), + Some(&vec![Event::WriteAcknowledgment(src_height)]), + "Expected last event return from processing RecvPacket message to contain a WriteAcknowledgment" + ); + + Ok(()) +} diff --git a/crates/relayer-mock/src/tests/mod.rs b/crates/relayer-mock/src/tests/mod.rs new file mode 100644 index 0000000000..3dad5bd93e --- /dev/null +++ b/crates/relayer-mock/src/tests/mod.rs @@ -0,0 +1,2 @@ +pub mod mock; +pub mod util; diff --git a/crates/relayer-mock/src/tests/util/context.rs b/crates/relayer-mock/src/tests/util/context.rs new file mode 100644 index 0000000000..73720756d4 --- /dev/null +++ b/crates/relayer-mock/src/tests/util/context.rs @@ -0,0 +1,27 @@ +use alloc::string::{String, ToString}; +use std::sync::Arc; + +use crate::relayer_mock::base::types::runtime::MockRuntimeContext; +use crate::relayer_mock::contexts::chain::MockChainContext; +use crate::relayer_mock::contexts::relay::MockRelayContext; +use crate::relayer_mock::util::clock::MockClock; + +pub fn build_mock_relay_context() -> ( + MockRelayContext, + Arc, + Arc, +) { + let clock = Arc::new(MockClock::default()); + let runtime = MockRuntimeContext::new(clock.clone()); + let src_chain = Arc::new(MockChainContext::new("chain1".to_string(), clock.clone())); + let dst_chain = Arc::new(MockChainContext::new("chain2".to_string(), clock)); + let relay = MockRelayContext::new( + src_chain.clone(), + dst_chain.clone(), + String::from("client_src_to_dst"), + String::from("client_dst_to_src"), + runtime, + ); + + (relay, src_chain, dst_chain) +} diff --git a/crates/relayer-mock/src/tests/util/mod.rs b/crates/relayer-mock/src/tests/util/mod.rs new file mode 100644 index 0000000000..9efb2ab19f --- /dev/null +++ b/crates/relayer-mock/src/tests/util/mod.rs @@ -0,0 +1 @@ +pub mod context; diff --git a/crates/relayer-runtime/Cargo.toml b/crates/relayer-runtime/Cargo.toml new file mode 100644 index 0000000000..a3426d292c --- /dev/null +++ b/crates/relayer-runtime/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "ibc-relayer-runtime" +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" +readme = "README.md" +keywords = ["blockchain", "consensus", "cosmos", "ibc", "tendermint"] +repository = "https://github.com/informalsystems/ibc-rs" +authors = ["Informal Systems "] +rust-version = "1.68" +description = """ + Implementation of an IBC Relayer in Rust, as a library +""" + +[package.metadata.docs.rs] +all-features = true + +[features] + +[dependencies] +async-trait = "0.1.56" +futures = "0.3" +tokio = "1.24" +tokio-stream = "0.1" +tracing = "0.1.36" +flex-error = { version = "0.4.4", default-features = false } +ibc-relayer-all-in-one = { version = "0.1.0", path = "../relayer-all-in-one" } +ibc-relayer-components = { version = "0.1.0", path = "../relayer-components" } +ibc-relayer-components-extra = { version = "0.1.0", path = "../relayer-components-extra" } diff --git a/crates/relayer-runtime/src/impls/logger.rs b/crates/relayer-runtime/src/impls/logger.rs new file mode 100644 index 0000000000..42e399fa35 --- /dev/null +++ b/crates/relayer-runtime/src/impls/logger.rs @@ -0,0 +1,116 @@ +use core::fmt::{Debug, Display}; +use ibc_relayer_components::logger::traits::level::{ + HasLogLevel, LevelDebug, LevelError, LevelInfo, LevelTrace, LevelWarn, +}; +use ibc_relayer_components::logger::traits::logger::BaseLogger; +use tracing::{debug, error, event_enabled, info, trace, warn, Level}; + +use crate::types::log::entries::LogEntries; +use crate::types::log::level::LogLevel; +use crate::types::log::logger::TracingLogger; +use crate::types::log::value::LogValue; + +impl BaseLogger for TracingLogger { + type Log<'a, 'r> = LogEntries<'a>; + + type LogValue<'a> = LogValue<'a>; + + type LogLevel = LogLevel; + + fn new_log<'a>( + &'a self, + level: LogLevel, + message: &str, + build_log: impl for<'r> FnOnce(&'r Self::Log<'a, 'r>), + ) { + match level { + LogLevel::Trace => { + if event_enabled!(Level::TRACE) { + let log: LogEntries<'_> = LogEntries::default(); + build_log(&log); + trace!(message = message, details = log.to_string()) + } + } + LogLevel::Debug => { + if event_enabled!(Level::DEBUG) { + let log = LogEntries::default(); + build_log(&log); + debug!(message = message, details = log.to_string()) + } + } + LogLevel::Info => { + if event_enabled!(Level::INFO) { + let log = LogEntries::default(); + build_log(&log); + info!(message = message, details = log.to_string()) + } + } + LogLevel::Warn => { + if event_enabled!(Level::WARN) { + let log = LogEntries::default(); + build_log(&log); + warn!(warning = message, details = log.to_string()) + } + } + LogLevel::Error => { + if event_enabled!(Level::ERROR) { + let log = LogEntries::default(); + build_log(&log); + error!(message = message, details = log.to_string()) + } + } + } + } + + fn log_field<'a, 'b, 'r>(log: &LogEntries<'a>, key: &'b str, value: LogValue<'a>) + where + 'b: 'a, + { + log.fields.borrow_mut().push((key, value)) + } + + fn display_value(value: &T) -> LogValue<'_> + where + T: Display, + { + LogValue::Display(value) + } + + fn debug_value(value: &T) -> LogValue<'_> + where + T: Debug, + { + LogValue::Debug(value) + } + + fn list_values<'a>(values: &'a [Self::LogValue<'a>]) -> Self::LogValue<'a> { + LogValue::List(values) + } + + fn map_values<'a>(build_log: impl for<'s> FnOnce(&'s Self::Log<'a, 's>)) -> LogValue<'a> { + let in_log = LogEntries::default(); + build_log(&in_log); + let values = in_log.fields.into_inner(); + LogValue::Nested(values) + } +} + +impl HasLogLevel for TracingLogger { + const LEVEL: LogLevel = LogLevel::Trace; +} + +impl HasLogLevel for TracingLogger { + const LEVEL: LogLevel = LogLevel::Debug; +} + +impl HasLogLevel for TracingLogger { + const LEVEL: LogLevel = LogLevel::Info; +} + +impl HasLogLevel for TracingLogger { + const LEVEL: LogLevel = LogLevel::Warn; +} + +impl HasLogLevel for TracingLogger { + const LEVEL: LogLevel = LogLevel::Error; +} diff --git a/crates/relayer-runtime/src/impls/mod.rs b/crates/relayer-runtime/src/impls/mod.rs new file mode 100644 index 0000000000..947c7ea081 --- /dev/null +++ b/crates/relayer-runtime/src/impls/mod.rs @@ -0,0 +1,2 @@ +pub mod logger; +pub mod runtime; diff --git a/crates/relayer-runtime/src/impls/runtime/channel.rs b/crates/relayer-runtime/src/impls/runtime/channel.rs new file mode 100644 index 0000000000..8daf361f60 --- /dev/null +++ b/crates/relayer-runtime/src/impls/runtime/channel.rs @@ -0,0 +1,120 @@ +use core::pin::Pin; + +use async_trait::async_trait; +use futures::Stream; +use ibc_relayer_components::core::traits::sync::Async; +use ibc_relayer_components_extra::runtime::traits::channel::{ + CanCloneSender, CanCreateChannels, CanStreamReceiver, CanUseChannels, HasChannelTypes, +}; +use ibc_relayer_components_extra::runtime::traits::channel_once::{ + CanCreateChannelsOnce, CanUseChannelsOnce, HasChannelOnceTypes, +}; +use tokio::sync::{mpsc, oneshot}; +use tokio_stream::wrappers::UnboundedReceiverStream; + +use crate::types::error::Error; +use crate::types::runtime::TokioRuntimeContext; + +impl HasChannelTypes for TokioRuntimeContext { + type Sender = mpsc::UnboundedSender + where + T: Async; + + type Receiver = mpsc::UnboundedReceiver + where + T: Async; +} + +impl HasChannelOnceTypes for TokioRuntimeContext { + type SenderOnce = oneshot::Sender + where + T: Async; + + type ReceiverOnce = oneshot::Receiver + where + T: Async; +} + +impl CanCreateChannels for TokioRuntimeContext { + fn new_channel() -> (Self::Sender, Self::Receiver) + where + T: Async, + { + mpsc::unbounded_channel() + } +} + +impl CanCreateChannelsOnce for TokioRuntimeContext { + fn new_channel_once() -> (Self::SenderOnce, Self::ReceiverOnce) + where + T: Async, + { + let (sender, receiver) = oneshot::channel(); + (sender, receiver) + } +} + +#[async_trait] +impl CanUseChannels for TokioRuntimeContext { + fn send(sender: &Self::Sender, value: T) -> Result<(), Self::Error> + where + T: Async, + { + sender.send(value).map_err(|_| Error::channel_closed()) + } + + async fn receive(receiver: &mut Self::Receiver) -> Result + where + T: Async, + { + receiver.recv().await.ok_or_else(Error::channel_closed) + } + + fn try_receive(receiver: &mut Self::Receiver) -> Result, Self::Error> + where + T: Async, + { + match receiver.try_recv() { + Ok(batch) => Ok(Some(batch)), + Err(mpsc::error::TryRecvError::Empty) => Ok(None), + Err(mpsc::error::TryRecvError::Disconnected) => Err(Error::channel_closed()), + } + } +} + +#[async_trait] +impl CanUseChannelsOnce for TokioRuntimeContext { + fn send_once(sender: Self::SenderOnce, value: T) -> Result<(), Self::Error> + where + T: Async, + { + sender.send(value).map_err(|_| Error::channel_closed()) + } + + async fn receive_once(receiver: Self::ReceiverOnce) -> Result + where + T: Async, + { + receiver.await.map_err(|_| Error::channel_closed()) + } +} + +impl CanStreamReceiver for TokioRuntimeContext { + fn receiver_to_stream( + receiver: Self::Receiver, + ) -> Pin + Send + 'static>> + where + T: Async, + { + Box::pin(UnboundedReceiverStream::new(receiver)) + } +} + +impl CanCloneSender for TokioRuntimeContext { + fn clone_sender(sender: &Self::Sender) -> Self::Sender + where + T: Async, + { + sender.clone() + } +} diff --git a/crates/relayer-runtime/src/impls/runtime/error.rs b/crates/relayer-runtime/src/impls/runtime/error.rs new file mode 100644 index 0000000000..b1224eb474 --- /dev/null +++ b/crates/relayer-runtime/src/impls/runtime/error.rs @@ -0,0 +1,8 @@ +use ibc_relayer_components::core::traits::error::HasErrorType; + +use crate::types::error::Error; +use crate::types::runtime::TokioRuntimeContext; + +impl HasErrorType for TokioRuntimeContext { + type Error = Error; +} diff --git a/crates/relayer-runtime/src/impls/runtime/mod.rs b/crates/relayer-runtime/src/impls/runtime/mod.rs new file mode 100644 index 0000000000..ce5b734481 --- /dev/null +++ b/crates/relayer-runtime/src/impls/runtime/mod.rs @@ -0,0 +1,6 @@ +pub mod channel; +pub mod error; +pub mod mutex; +pub mod sleep; +pub mod spawn; +pub mod time; diff --git a/crates/relayer-runtime/src/impls/runtime/mutex.rs b/crates/relayer-runtime/src/impls/runtime/mutex.rs new file mode 100644 index 0000000000..6d132784b5 --- /dev/null +++ b/crates/relayer-runtime/src/impls/runtime/mutex.rs @@ -0,0 +1,21 @@ +use async_trait::async_trait; +use futures::lock::{Mutex, MutexGuard}; +use ibc_relayer_components::core::traits::sync::Async; +use ibc_relayer_components::runtime::traits::mutex::HasMutex; + +use crate::types::runtime::TokioRuntimeContext; + +#[async_trait] +impl HasMutex for TokioRuntimeContext { + type Mutex = Mutex; + + type MutexGuard<'a, T: Async> = MutexGuard<'a, T>; + + fn new_mutex(item: T) -> Self::Mutex { + Mutex::new(item) + } + + async fn acquire_mutex<'a, T: Async>(mutex: &'a Self::Mutex) -> Self::MutexGuard<'a, T> { + mutex.lock().await + } +} diff --git a/crates/relayer-runtime/src/impls/runtime/sleep.rs b/crates/relayer-runtime/src/impls/runtime/sleep.rs new file mode 100644 index 0000000000..aa2ebd61de --- /dev/null +++ b/crates/relayer-runtime/src/impls/runtime/sleep.rs @@ -0,0 +1,13 @@ +use async_trait::async_trait; +use core::time::Duration; +use ibc_relayer_components::runtime::traits::sleep::CanSleep; +use tokio::time::sleep; + +use crate::types::runtime::TokioRuntimeContext; + +#[async_trait] +impl CanSleep for TokioRuntimeContext { + async fn sleep(&self, duration: Duration) { + sleep(duration).await; + } +} diff --git a/crates/relayer-runtime/src/impls/runtime/spawn.rs b/crates/relayer-runtime/src/impls/runtime/spawn.rs new file mode 100644 index 0000000000..ef41c8f126 --- /dev/null +++ b/crates/relayer-runtime/src/impls/runtime/spawn.rs @@ -0,0 +1,39 @@ +use core::future::Future; +use core::pin::Pin; +use ibc_relayer_components_extra::runtime::traits::spawn::{HasSpawner, Spawner, TaskHandle}; + +use crate::types::runtime::TokioRuntimeContext; +use crate::types::task::TokioTaskHandle; + +impl HasSpawner for TokioRuntimeContext { + type Spawner = Self; + + fn spawner(&self) -> Self::Spawner { + self.clone() + } +} + +impl Spawner for TokioRuntimeContext { + fn spawn(&self, task: F) -> Box + where + F: Future + Send + 'static, + F::Output: Send + 'static, + { + let join_handle = self.runtime.spawn(async move { + task.await; + }); + Box::new(TokioTaskHandle(join_handle)) + } +} + +impl TaskHandle for TokioTaskHandle { + fn abort(self: Box) { + self.0.abort(); + } + + fn into_future(self: Box) -> Pin + Send + 'static>> { + Box::pin(async move { + let _ = self.0.await; + }) + } +} diff --git a/crates/relayer-runtime/src/impls/runtime/time.rs b/crates/relayer-runtime/src/impls/runtime/time.rs new file mode 100644 index 0000000000..ad5bbdb0c4 --- /dev/null +++ b/crates/relayer-runtime/src/impls/runtime/time.rs @@ -0,0 +1,18 @@ +use core::time::Duration; +use std::time::Instant; + +use ibc_relayer_components::runtime::traits::time::HasTime; + +use crate::types::runtime::TokioRuntimeContext; + +impl HasTime for TokioRuntimeContext { + type Time = Instant; + + fn now(&self) -> Instant { + Instant::now() + } + + fn duration_since(time: &Instant, other: &Instant) -> Duration { + time.duration_since(*other) + } +} diff --git a/crates/relayer-runtime/src/lib.rs b/crates/relayer-runtime/src/lib.rs new file mode 100644 index 0000000000..f7c9d4edbf --- /dev/null +++ b/crates/relayer-runtime/src/lib.rs @@ -0,0 +1,4 @@ +extern crate alloc; + +pub mod impls; +pub mod types; diff --git a/crates/relayer-runtime/src/types/error.rs b/crates/relayer-runtime/src/types/error.rs new file mode 100644 index 0000000000..a21c45b893 --- /dev/null +++ b/crates/relayer-runtime/src/types/error.rs @@ -0,0 +1,12 @@ +use flex_error::define_error; + +define_error! { + #[derive(Clone, Debug)] + Error { + ChannelClosed + | _ | { "unexpected closure of internal rust channels" }, + + PoisonedLock + | _ | { "poisoned mutex lock" }, + } +} diff --git a/crates/relayer-runtime/src/types/log/entries.rs b/crates/relayer-runtime/src/types/log/entries.rs new file mode 100644 index 0000000000..7e5ed547e8 --- /dev/null +++ b/crates/relayer-runtime/src/types/log/entries.rs @@ -0,0 +1,17 @@ +use core::cell::RefCell; +use core::fmt::{self, Display}; + +use crate::types::log::value::LogValue; + +#[derive(Default)] +pub struct LogEntries<'a> { + pub fields: RefCell)>>, +} + +impl<'a> Display for LogEntries<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_map() + .entries(self.fields.borrow().iter().map(|&(k, ref v)| (k, v))) + .finish() + } +} diff --git a/crates/relayer-runtime/src/types/log/level.rs b/crates/relayer-runtime/src/types/log/level.rs new file mode 100644 index 0000000000..7a787b1564 --- /dev/null +++ b/crates/relayer-runtime/src/types/log/level.rs @@ -0,0 +1,14 @@ +#[derive(Clone)] +pub enum LogLevel { + Trace, + Debug, + Info, + Warn, + Error, +} + +impl Default for LogLevel { + fn default() -> Self { + Self::Info + } +} diff --git a/crates/relayer-runtime/src/types/log/logger.rs b/crates/relayer-runtime/src/types/log/logger.rs new file mode 100644 index 0000000000..f273ce82d5 --- /dev/null +++ b/crates/relayer-runtime/src/types/log/logger.rs @@ -0,0 +1 @@ +pub struct TracingLogger; diff --git a/crates/relayer-runtime/src/types/log/mod.rs b/crates/relayer-runtime/src/types/log/mod.rs new file mode 100644 index 0000000000..99d681c4d4 --- /dev/null +++ b/crates/relayer-runtime/src/types/log/mod.rs @@ -0,0 +1,4 @@ +pub mod entries; +pub mod level; +pub mod logger; +pub mod value; diff --git a/crates/relayer-runtime/src/types/log/value.rs b/crates/relayer-runtime/src/types/log/value.rs new file mode 100644 index 0000000000..fd9f0ebfbf --- /dev/null +++ b/crates/relayer-runtime/src/types/log/value.rs @@ -0,0 +1,22 @@ +use core::fmt::{self, Debug, Display}; + +pub enum LogValue<'a> { + Display(&'a dyn Display), + Debug(&'a dyn Debug), + List(&'a [LogValue<'a>]), + Nested(Vec<(&'a str, LogValue<'a>)>), +} + +impl<'a> Debug for LogValue<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Display(value) => Display::fmt(*value, f), + Self::Debug(value) => Debug::fmt(*value, f), + Self::List(values) => f.debug_list().entries(values.iter()).finish(), + Self::Nested(values) => f + .debug_map() + .entries(values.iter().map(|&(k, ref v)| (k, v))) + .finish(), + } + } +} diff --git a/crates/relayer-runtime/src/types/mod.rs b/crates/relayer-runtime/src/types/mod.rs new file mode 100644 index 0000000000..7ba4bf4763 --- /dev/null +++ b/crates/relayer-runtime/src/types/mod.rs @@ -0,0 +1,4 @@ +pub mod error; +pub mod log; +pub mod runtime; +pub mod task; diff --git a/crates/relayer-runtime/src/types/runtime.rs b/crates/relayer-runtime/src/types/runtime.rs new file mode 100644 index 0000000000..28bbe04a2c --- /dev/null +++ b/crates/relayer-runtime/src/types/runtime.rs @@ -0,0 +1,13 @@ +use alloc::sync::Arc; +use tokio::runtime::Runtime; + +#[derive(Clone)] +pub struct TokioRuntimeContext { + pub runtime: Arc, +} + +impl TokioRuntimeContext { + pub fn new(runtime: Arc) -> Self { + Self { runtime } + } +} diff --git a/crates/relayer-runtime/src/types/task.rs b/crates/relayer-runtime/src/types/task.rs new file mode 100644 index 0000000000..c7c23e3032 --- /dev/null +++ b/crates/relayer-runtime/src/types/task.rs @@ -0,0 +1,3 @@ +use tokio::task::JoinHandle; + +pub struct TokioTaskHandle(pub JoinHandle<()>); diff --git a/crates/relayer/src/channel.rs b/crates/relayer/src/channel.rs index 84bce4470e..d0adc7141d 100644 --- a/crates/relayer/src/channel.rs +++ b/crates/relayer/src/channel.rs @@ -437,7 +437,7 @@ impl Channel { self.b_side.version.as_ref() } - fn a_channel(&self, channel_id: Option<&ChannelId>) -> Result { + pub fn a_channel(&self, channel_id: Option<&ChannelId>) -> Result { if let Some(id) = channel_id { self.a_chain() .query_channel( @@ -455,7 +455,7 @@ impl Channel { } } - fn b_channel(&self, channel_id: Option<&ChannelId>) -> Result { + pub fn b_channel(&self, channel_id: Option<&ChannelId>) -> Result { if let Some(id) = channel_id { self.b_chain() .query_channel( diff --git a/crates/relayer/src/client_state.rs b/crates/relayer/src/client_state.rs index 8c5eff288b..e478a36795 100644 --- a/crates/relayer/src/client_state.rs +++ b/crates/relayer/src/client_state.rs @@ -50,6 +50,7 @@ impl UpgradeOptions for AnyUpgradeOptions {} #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(tag = "type")] +#[non_exhaustive] pub enum AnyClientState { Tendermint(TmClientState), diff --git a/crates/relayer/src/event.rs b/crates/relayer/src/event.rs index 7d3e958505..884c7096f1 100644 --- a/crates/relayer/src/event.rs +++ b/crates/relayer/src/event.rs @@ -298,7 +298,9 @@ pub fn timeout_packet_try_from_abci_event( .map_err(|_| ChannelError::abci_conversion_failed(abci_event.kind.clone())) } -fn client_extract_attributes_from_tx(event: &AbciEvent) -> Result { +pub fn client_extract_attributes_from_tx( + event: &AbciEvent, +) -> Result { let mut attr = ClientAttributes::default(); for tag in &event.attributes { diff --git a/crates/relayer/src/event/source/rpc/extract.rs b/crates/relayer/src/event/source/rpc/extract.rs index 7df189cd38..96ae71aff7 100644 --- a/crates/relayer/src/event/source/rpc/extract.rs +++ b/crates/relayer/src/event/source/rpc/extract.rs @@ -9,6 +9,7 @@ use crate::telemetry; use crate::event::{ibc_event_try_from_abci_event, IbcEventWithHeight}; +#[allow(unused_variables)] pub fn extract_events( _chain_id: &ChainId, height: Height, diff --git a/crates/relayer/src/light_client.rs b/crates/relayer/src/light_client.rs index 5f19bba177..4a98aa6acb 100644 --- a/crates/relayer/src/light_client.rs +++ b/crates/relayer/src/light_client.rs @@ -81,6 +81,7 @@ pub fn decode_header(header_bytes: &[u8]) -> Result, Error> { #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] #[allow(clippy::large_enum_variant)] +#[non_exhaustive] pub enum AnyHeader { Tendermint(TendermintHeader), } diff --git a/docs/architecture/adr-012-ibc-relayer-v2.md b/docs/architecture/adr-012-ibc-relayer-v2.md new file mode 100644 index 0000000000..b25f773bd0 --- /dev/null +++ b/docs/architecture/adr-012-ibc-relayer-v2.md @@ -0,0 +1,1029 @@ +# ADR 012: New Relayer Architecture + +# Changelog + +* 2022-11-10: Initial Draft + +# Context and Problem Statement + +The current Hermes relayer, hereby called v1 relayer in this document, was +implemented at a time when the Cosmos ecosystem was still very new, and the +idea of cross-chain communication with IBC was still a novel concept. The Hermes relayer +has made a monumental contribution toward the success of IBC and Cosmos, and it +is the most widely used IBC relayer in the Cosmos ecosystem today. + +The adoption of Interchain and IBC continues to grow, and IBC traffic is +increasing at an exponential pace. From our experience in developing and +operating the Hermes relayer, we have learned many valuable lessons about +the challenges of building a reliable IBC relayer for a modern Interchain +ecosystem. + +In this section, we will share some of the challenges that the v1 Hermes +relayer faces, and the motivation for why there is a need to rethink about +the IBC relayer architecture. + +## Message Batching + +A naive relayer can easily perform IBC relaying for a single packet by building +the relevant proofs on one chain and submit the messages to the counterparty +chain. However such relaying is slow and wasteful of fees, as the relayer +can only relay one packet in each transaction. + +As a result, batching mechanics are introduced to batch multiple messages for +one chain into a single transaction. This significantly complicates the relayer +design, and is the main source of complexity in the current relayer. + +Ideally, the core relayer logic should be kept simple and implemented as close +to a naive relayer. Additional logics like message batching are cross-cutting +concerns that should be implemented in a way that _augments_ the core logic, +not to obscure it. + +## Error Retry + +In a distributed system, the relaying operations may fail due to external +factors, such as a network disconnection and full node going down. Therefore +the relayer needs to implement retry logic to resubmit a relay message to +a chain when it fails. + +However when working the batching mechanics, the retry logic becomes +significantly more complicated. Since batched messages in a transaction would +all fail when one message fails, it is unclear what caused the errors, +and how the retry should be done. + +The current relayer rely on ad-hoc retry mechanisms at various levels of +code. There is no clear understanding of how the errors propagate, and +we are playing whack-a-mole to hammer down errors when relaying fail. + +Ideally, the error retry should be done at the level of relaying for a single +packet. This is so that the retry operations can be done independently of each +packet. + +## Update Client Mechanics + +The relayer needs multiple strategies of when to submit `UpdateClient` messages +inside the transactions it submitted to a chain. For IBC messages like +`RecvPacket` to be accepted by the chain, an `UpdateClient` message with the +corresponding height must already be processed either before the current +transaction, or in the messages before the current message in the current +transaction. + +When redundant `UpdateClient` messages of the same height are submitted, +the subsequent messages are ignored, but the relayer still have to pay for the +fee. Therefore to be cost effective, the relayer should avoid sending +multiple `UpdateClient` messages whenever it is possible. + +When combined with the message batching mechanics, the update client subsystem +has to decide whether to prepend an `UpdateClient` message to the current +batched transaction. + +When combined with the error retry mechanics, the update client subsystem has +to determine whether it is safe to skip prepending the `UpdateClient` message +to the current batch, if the `UpdateClient` message is prepended to the +previous batch but failed. + +The current relayer architecture is not very flexible at using different +strategies to update clients. Therefore it can fail in unexpected ways +when transactions with `UpdateClient` fail. The relayer is also inefficient +due to messages forming dependencies to the transaction with `UpdateClient`, +making it difficult to submit multiple transactions in parallel. + +## Prioritized Mempools + +The upcoming changes for prioritized mempools in Tendermint breaks the current +batching mechanics of submitting multiple transactions in parallel. Because a +chain can now process the transactions in any order, it is possible to break +the original ordering and have a transaction without `UpdateClient` to be +processed before the transaction containing `UpdateClient`. + +To workaround the limitation, we have to come up with new strategies on how to +batch transactions with `UpdateClient` messages. A naive approach would be +to prepend the `UpdateClient` message to _all_ transactions, which can be +wasteful in cost. + +A better strategy would be to focus on _throughput_ rather than latency. +That can be done by submitting `UpdateClient` transactions independently, +and only batch and submit IBC messages in parallel when the `UpdateClient` +transaction is committed. + +The problem of choosing a suitable strategy is that the current relayer +architecture is tightly coupled with a specific strategy. Therefore it is +very difficult to change the relaying strategy, or experiment on better +strategies. + +Ideally, the core logic of the relayer should be independent of the strategies +of how the messages are batched and how the `UpdateClient` messages are +prepended. + +## Multi-Wallet Batching + +The current relayer architecture is hardcoded with one global wallet per chain, +and it is difficult to use a different wallet for sending messages without +spawning an entirely separate `ChainHandle` instance together with the +surrounding wiring. This makes it challenging for the relayer to adopt more +efficient message batching strategies that make use of multiple wallets. + +Cosmos chains are configured with soft limits of how large a single transaction +can be. As a result, the relayer cannot simply batch unbounded number of +messages in a single transaction. This creates a problem when the relayer +needs to relay many IBC packets at the same time. The transaction bottleneck +means that messages have to be queued and be sent one after another. + +A further complication of the message queue is that each transaction is +assigned an account sequence that must be greater than the sequence used in the +previous transaction. If multiple transactions are submitted to the mempool, +a transaction with lower sequence number will get rejected if it gets processed +after another transaction with a higher sequence number. This issue is further +aggravated with the introduction of prioritized mempools. + +The safest way to workaround this problem is to ensure that only one +transaction is being processed by the mempool at any time. However this will +also introduce additional latency, as new transaction can only be submitted +after the previous transaction is committed to a block, meaning that the +next block has to be skipped and the next transaction can be committed +earliest in the block after. + +A better strategy would be to also use multiple wallets at the same time, +so that each wallet can submit different transactions in parallel without +blocking each others. + +To do this, the relayer architecture needs to allow customization of wallets +used for relaying, and lazily instantiate the signer fields and signatures +depending on the availability of parallel wallets. + +## Async Pipeline + +The current relayer architecture was implemented before async Rust become +stabilized. As a result it uses threads for concurrency, which limits the +number of concurrent processes that can run. + +Since spawning new threads are expensive, the current relayer spawns a worker +thread for each relay path as identified by source and destination +channels/chains. When combined with the batching and retry operations, this +means that the packet worker has to do ad hoc multiplexing strategies to +process multiple IBC packets in parallel within a single thread. This results +in non-linear control flow and makes the relaying code difficult to understand. + +With async Rust available now, a better approach is to redesign the packet +worker such that they can be run in multiple async tasks. For instance, the +relaying of each packet can be done in separate async tasks, with each having +their own retry logic. The message batching operation can be done in separate +async tasks, and communicate with the individual packet relaying tasks through +message passing. + +## Multi-Versioned Protobuf Definitions + +The relayer currently uses [`prost_build`](https://docs.rs/prost-build/) to +generate Rust structs from the protobuf definitions provided by the Go projects +`tendermint`, `cosmos-sdk`, and `ibc-go`. + +When running a Cosmos chain, it is trivial to use a single set of protobuf +definition, as the chain is acting as the provider for the protobuf-based +services. However as a client, the relayer acts as a client to interact with +many chains, and has to support relaying between different chains, even if +they use incompatible versions of protobuf definitions. + +The use of a single set of protobuf definitions cause issues when supporting +chains that do not use the same protobuf definitions. To support newer chains, +the relayer must update the protobuf definitions to support the new features. +But if the new protobuf definitions contain breaking changes, it would break +relayer support for chains that are still using the older definitions. + +A better strategy is to generate _multiple_ protobuf definitions for each +major versions, and have them co-exist within the relayer. However a major +challenge with this approach is that the different protobuf definitions +are considered different types by Rust, even if they are structurally +equivalent. + +For example, if a Rust code is written to work with the `Height` struct +from one version of the protobuf definitions, Rust will reject the code +if we pass it a value with the `Height` type from another version of the +protobuf definitions, even if between the two versions, the fields in +`Height` are exactly the same. + +In this case, it is also not possible to use feature flags to switch between +different protobuf versions, because the relayer may need _both_ versions +during runtime. + +A more appropriate solution is to treat all protobuf types as _abstract_, +and write _generic_ code that work with any protobuf type that satisfy +higher level constraints. + +For example, a Rust code can be written to be generic over any `Height` +type, provided that the type implements `Eq`, `Ord`, and `Clone`. With this, +the code can be used with different sets of protobuf definitions, even if +there are other protobuf structs that are incompatible but are unused by +the given code. + +## Multi-Versioned RPC APIs + +Cosmos chain implementations do not only provide protobuf definitions, but +also offer RPC and GRPC services. However similar to the protobuf definitions, +the RPC APIs can change and break in subtle ways. + +A common form of breakage is when the APIs require additional input fields or +return additional/less fields in the response. Another form of breakage is on +subtle changes in the JSON scheme and causing mismatch in field names. + +With the current relayer design, there is often no way to solve it on the +relayer side, other than reporting the errors and hope that the upstream +APIs revert the changes. + +A more appropriate design is to allow the API clients to be customized and +overridden depending on the chains. So if a particular API has breaking +changes, there can be two version of API clients to choose from. + +## Support for Non-SDK Chains + +Cosmos chains that are based on Cosmos SDK offer a fairly standard set of +GRPC APIs by reusing what has already been implemented in Cosmos SDK. +But with the decoupling offered by Tendermint and ABCI, there are also +Cosmos chains that are not implemented using Go and Cosmos SDK. + +These non-SDK chains may not necessary offer the same GRPC APIs or protobuf +definitions. So the relayer may break in unexpected ways when relaying from +non-SDK chains. + +While non-SDK chains may be incentivized to preserve the same behavior as +SDK chains, this puts unnecessary burden to the chain developers. Furthermore, +not all chains are compelled to use Hermes as the IBC relayer, and Hermes +may lose its competitive advantage if there are relayers that can work better +with non-SDK chains. + +For the long term health of the Cosmos ecosystem, it is also worthwhile to +encourage alternative implementations of Cosmos SDK and IBC in languages +other than Go. For this, the Hermes relayer should be made customizable so that +non-SDK chain developers can more easily customize the relayer to work with +their custom chains. + +## Support for Non-Cosmos Chains + +The success of IBC and the growth of Interchain introduce new use cases that +the v1 relayer did not sufficiently focus on. For example, we have received +many feature requests from Substrate to modify the Hermes code base to better +support relaying between a Cosmos chain and a Substrate chain. + +The biggest challenge with the current relayer architecture is to extend it +to work with non-Cosmos chains like Substrate. In particular, the relayer +data structures are tightly coupled with Cosmos-related protobuf definitions +and RPC APIs. But non-Cosmos chains are not obligated to use protobuf or +GRPC. + +The relayer code is also tightly coupled with concrete data structures, making +it difficult to extend the data structures to work differently for non-Cosmos +chains. This problem is seen most notably in the definition of sum types like +`AnyClientState`, in attempt to have global types but still support variants +of the concrete data structures. + +All these means that the core relayer code and global types have to go through +significant changes to support a new non-Cosmos chain. Due to conflicts and +additional dependencies, this often result in forks of the relayer just so +that changes can be made without interfering with the core relayer development. + +The primary goal for us with a new relayer design is to have a minimal set of +assumptions on how an IBC-like relaying should work, and expose the relayer +core logic as a libary so that non-Cosmos chains can use the library to develop +custom relayers without having to fork the entire relayer. + +## Support for mutiple versions of Cosmos chain + +(todo) + +## Support for multiple batching strategies + +(todo) + +## Async Concurrency + +(todo) + +## Type safety for differentiation of identifiers from different chains + +(todo) + +## Code correctness and formal verification + +(todo) + +# Decision + +## Development Strategy + +The Hermes v2 relayer is designed from the top down with a new architecture +that is compatible with the existing code base. This reduces the risk of having +a complete rewrite from the ground up, which may take too long and may fail +to deliver. + +We progress toward the relayer v2 design with an MVP, called relayer v1.5, +which adds a small number of experimental features to the existing v1 relayer +without replacing existing features of the v1 relayer. In contrast, a v2 +relayer is expected to supercede the majority of features of the v1 relayer +with new and improved code. + +For the purpose of the architecture re-design, all the new code being +developed are targeted toward the relayer v2. But the new code will be +usable in the form of experimental features when the relayer v1.5 is +released. Both the v1 relayer and the new relayer will co-exist from +v1.5 onward, until the v2 relayer is released. + +In the relayer v1.5 MVP, the new relayer only re-implements the packet +worker and the transaction sender. With that, the new relayer does not +depend on the `RelayPath` and `Link` data types, as well as the `send_tx` +methods in the v1 relayer. In contrast, the new relayer will initially +continue to rely on the `ForeignClient` and `ChainHandle` datatypes +to perform queries and processing of messages. + +## Architecture Overview + +A full description of the relayer v2 architecture is too much to be described +in this ADR. Instead, readers are encouraged to read the full documentation +from the generated Cargo docs for the new relayer crates. + +At its core, the v2 relayer makes use of a new programming technique, called +_context-generic programming_ (CGP), to implement the relayer components in a +modular fashion. CGP turns OOP methods into modular components by replacing +the concrete `Self` type with a _generic_ `Context` type. Using the special +properties of Rust's trait system, CGP allows component implementations to +add new constraints and requirements to the `Context` type through `where` +clauses inside `impl` blocks, by which the constraints would automatically +propagate to the top level without having to repeatedly specify the +constraints at every level of the code. + +### Relayer Framework + +Using CGP, the core logic of the new relayer is implemented as a fully abstract +library crate called `ibc-relayer-components`, with no dependency on external +crates except for primitive crates, such as using the `async-trait` crate to +support async functions inside traits. In addition, the relayer framework is +developed with `#![no_std]` enabled. By having almost no external dependencies, +the relayer can be ported to various restrictive environments, such as +Wasm, the Substrate runtime, and symbolic execution environments. + +Since the relayer framework is fully abstract, it also does not depend on +the concrete type definitions of the IBC constructs, including primitives +like height. Instead, the types are declared as abstract associated types in +traits like `HasChainTypes` `HasRelayTypes`: + +```rust +trait HasChainTypes { + type Height; + type Timestamp; + type Message; + type Event; + /* ... */ +} + +trait HasRelayTypes { + type SrcChain: HasChainTypes; + type DstChain: HasChainTypes; + type Packet; + /* ... */ +} +``` + +Readers are encouraged to refer to the documentation and source code for +the `ibc-relayer-components` crate to see the full definitions. +Since the type definitions are abstract, different chain implementations +are free to make use of custom types to instantiate the relayer +with. For example, a Cosmos chain implementation can use a `Height` type +that contains a revision number, while a mock chain implementation can use +`u64` as height. + +The use of abstract types is most useful in places where chain implementations +need different concrete types by necessity, such as the types for message, +event, consensus state, and signer keys. In relayer v1, if users need to +customize the implementation of these types, they would typically have to +submit a pull request to apply the changes to everyone, or keep a long fork of +the relayer code. With relayer v2, a Cosmos relayer can use only Cosmos-specific +types, without having to customize the types to handle non-Cosmos use cases. + +The use of abstract types also solves the problem of having multiple versions +of protobuf definitions for different versions of Cosmos chains. In relayer v1, +the message types are tightly coupled with the protobuf definitions generated +in `ibc-proto` and `tendermint-proto`. In relayer v2, it is possible to have +multiple versions of the generated protobuf modules to co-exist, and implement +different versions of relayers for different versions of Cosmos chains. Although +this would still result in code duplication in the Cosmos-specific +implementations, the duplication would only involve low-level operations such as +protobuf encodings, and it would not affect the core logic of the relayer or +other users of the relayer framework. + +### Update client message builder + +The use of CGP allows the relayer framework to break down complex relaying +logic into smaller pieces of components. As an example, the component +for building UpdateClient messages have the following trait: + +```rust +pub trait UpdateClientMessageBuilder +where + Relay: HasRelayTypes, + Target: ChainTarget, +{ + async fn build_update_client_messages( + context: &Relay, + _target: Target, + height: &Height, + ) -> Result>, Relay::Error>; +} +``` + +The trait allows an update client message builder component to be implemented +generically over a relay context `Relay` and a chain target `Target` +(for targetting either the source or destination chain). Using that, we can for +example define a `SkipUpdateClient` component as follows: + +```rust +pub struct SkipUpdateClient(PhantomData); + +impl + UpdateClientMessageBuilder + for SkipUpdateClient +where /* ... */ +{ /* ... */ } +``` + +The `SkipUpdateClient` component is a _middleware_ component that wraps around +an inner update client message builder, and skips calling the inner component +if it finds that a client update at the given height had already been done +before on the target chain. In general, a middleware component is a component +that wraps around another component implementing the same trait, and alters +the input and output of the wrapped component. + +The trait also allows an update client message builder component to be +implemented with a concrete relay context, such as a Cosmos-specific +update client message builder: + +```rust +pub struct BuildCosmosUpdateClientMessage; + +impl + UpdateClientMessageBuilder + for BuildCosmosUpdateClientMessage +where /* ... */ +{ /* ... */ } +``` + +The component `BuildCosmosUpdateClientMessage` is implemented to work with +a concrete relay context `CosmosRelayContext`, so it cannot be used with +other non-Cosmos relay contexts. However, it can be composed with other +generic components like `SkipUpdateClient` and form a component like: + +```rust +type ChosenUpdateClientMessageBuilder = + SkipUpdateClient>; +``` + +Above we have a declarative type alias of a component +`ChosenUpdateClientMessageBuilder`, which is composed of three smaller +components. When this is used, the component will first use `SkipUpdateClient` +to check whether the client has already been updated, it then uses +`WaitUpdateClient` to wait for the counterparty chain's height +to increase beyond the target height, then uses `BuildCosmosUpdateClientMessage` +to build the Cosmos-specific update client message. + +Having generic components like `SkipUpdateClient` and `WaitUpdateClient` +means that a context-specific component like `BuildCosmosUpdateClientMessage` can +opt to focus on only implementing the low-level logic of building a +Cosmos-specific UpdateClient message. On the other hands, users of the relayer +framework can also _opt-out_ of using a middleware component like `WaitUpdateClient`, +or they can also define new middleware components +to customize the UpdateClient logic. + +### IBC message sender + +A use case for having modular components is the ability for relayers to customize +on different strategies for sending IBC messages. For instance, a message sender +for a minimal relayer can be: + +```rust +type MinimalIbcMessageSender = + SendIbcMessagesWithUpdateClient; +``` + +The `MinimalIbcMessageSender` decleared above uses `SendIbcMessagesWithUpdateClient` +to prepend UpdateClient messages to the front of messages being sent, and +`SendIbcMessagesToChain` sends the messages from the relay context to the chain +context. + +On the other hand, in a full-featured relayer, a batch message worker could be +used to batch multiple messages being sent within a timeframe into a +single message batch: + +```rust +type FullIbcMessageSender = SendMessagesToBatchWorker; + +type IbcMessageSenderForBatchWorker = + SendIbcMessagesWithUpdateClient; +``` + +In the above declaration, the relay context would use +`SendMessagesToBatchWorker` to send the IBC messages to the batch worker using +an MPSC channel. Inside the batch worker, it would then bundle multiple batches +of messages and send them together using +`SendIbcMessagesWithUpdateClient`. Through this, the +batched version of the message sender can save cost and performance on sending +IBC messages, because the relayer would only attach one UpdateClient message +alongside multiple messages that are batched together. + +With the declarative nature of context-generic programming, users would be able +to easily customize on different strategies of sending IBC messages, +as well as building update client messages. CGP also helps propagate the +additional constraints of components to the concrete context implementer. +For instance, if `SendMessagesToBatchWorker` is used, the relay context is +required to provide MPSC channels that can be used for sending messages to the +batch worker. On the other hand, if only `MinimalIbcMessageSender` is used, +the relay context can remove the burden of having to provide an MPSC channel. + +### Async Concurrency + +The v1 relayer was developed at a time when Rust's async/await infrastructure +was not yet ready for use. As a result, the v1 relayer uses thread-based +concurrency, by spawning limited number of threads and manually multiplexing +multiple tasks in each thread. + +As time moves forward, the async/await feature in Rust has become mature enough, +and the new relayer is able to make use of async tasks to manage the concurrency +for relaying packets. At its core, the relaying of packets is done through the +following interface: + +```rust +trait PacketRelayer +where + Relay: HasRelayTypes, +{ + async fn relay_packet(relay: &Relay, packet: &Relay::Packet) -> + Result<(), Relay::Error>; +} +``` + +The `PacketRelayer` trait allows the handling of a single IBC packet at a time. +When multiple IBC packets need to be relayed at the same time, the relayer +framework allows multiple async tasks to be spawned at the same time, with each +task sharing the same relay context but doing the relaying for different packets. + +To optimize for efficiency, the relay context can switch the strategy for +batching messages and transactions at the lower layers without affecting the +logic for the packet relayers themselves. From the perspective of the packet +relayer implementation, the relay context appears to be exclusively owned by the +packet relayer, and it is not aware of other concurrent tasks running. + +#### Optimization Layers + +The relayer framework uses multiple layers of optimizations to improve the +efficiency of relaying IBC packets. The first layer performs message +batching per relay context, by collecting messages being sent over a relay +context within a time frame and sending them all as a single batch if it does not +exceed the batch size limit. This layer does the batching per relay context, +because it allows the use of the `SendIbcMessagesWithUpdateClient` component to +add update client messages to the batched messages, which has to be tied to a +specific relay context. + +The second layer performs message batching per chain context. It would collect +all messages being sent to a chain within a time frame, and send them all in +a single batch if it does not exceed the batch size limit. The batching +mechanics are the same, but is done at the chain level, as opposed to at the relayer level. +As a result, it is able to batch IBC messages coming from multiple counterparty chains. + +The third layer performs batching at the _transaction_ context. It provides a +_nonce allocator_ interface to allow a limited number of messages to be sent +at the same time as other transactions. Since Cosmos chains require strict order in +the use of monotonically-increasing account sequences as nonces, the nonce +allocator needs to be conservative in the number of nonces to be allocated in +parallel. This is because if an earlier transaction failed to be broadcasted, +later transactions that use the higher-numbered nonces may fail as well. When +a nonce mismatch error happens, the nonce allocator also needs to be smart +enough to refresh the cached nonce sequences so that the correct nonces can be +allocated to the next messages. + +It is also worth noting that all optimization layers offered by the relayer +framework are _optional_. This means that it is possible to opt-out of the +optimizations that we discussed earlier, or introduce different ways to optimize +the relaying. For example, if the relayer is used to relay +non-Cosmos chains, or if future Cosmos SDK chains allow parallel nonces to be +used, then one can easily swap with a different nonce allocator that is better +suited for the given nonce logic. The relayer framework also provides a _naive_ +nonce allocator, which only allows one transaction to be sent at a time using +a mutually exclusive nonce. + +The relayer framework also allows for easy addition of new optimization layers. +For example, we can consider adding a _signer allocator_ layer, which +multiplexes the sending of parallel transactions using multiple signer wallets. +Adding such a layer would require transaction contexts that use it to provide +a _list_ of signers, as compared to a single signer. On the other hand, +transaction contexts that do not need such an optimization are not affected and +would only have to provide a single signer. + +More information about the various optimization techniques are available in the +relayer wiki for +[concurrency architectures](https://github.com/informalsystems/hermes/wiki/Relayer-V2-Concurrency-Architectures). + +### Error Handling + +With the async task-based concurrency, the new relayer has simpler error +handling logic than the v1 relayer. At minimum, there are two places where retry +logic needs to be implemented. The first is at the packet relayer layer, where +if any part of the relaying operation fails, the packet relayer will retry +the whole process of relaying that packet. This is done by having a separate +`RetryRelayer` component that specifically handles the retry logic: + +```rust +type ChosenPacketRelayer = RetryRelayer; +``` + +In the above example, the `FullCycleRelayer` is the core packet relayer that +performs the actual packet relaying logic. It is wrapped by `RetryRelayer`, +which calls `FullCycleRelayer::relay_packet()` in an attempt to relay the +IBC packet. If the operation fails and returns an error, +`RetryRelayer` checks on whether the error is retryable, and if so it calls the inner +relayer again. As the relayer framework also keeps the error type generic, +a concrete relay context can provide custom error types as well as provide +methods for the `RetryRelayer` to determine whether an error is retryable. + +Inside the `FullCycleRelayer`, it always starts the relaying from the beginning +of the packet lifecycle, which is to relay a `RecvPacket`, then an `AckPacket` or +a `TimeoutPacket`. It does not check for what is the current relaying state of the +packet, because this is done by separate components such as +`SkipReceivedPacketRelayer`, which would skip relaying a `RecvPacket` if the chain +has already received the packet before. This helps keep the core relaying logic simple, +while still providing a robust retry mechanism that allows the retry operation +to resume at an appropriate stage. + +The second layer of the retry logic is at the transaction layer with the nonce allocator. +When sending of a transaction fails, the nonce allocator can check whether +the failure is caused by nonce mismatch errors. If so, it can retry sending +the transaction with a refreshed nonce, without having to propagate the error +all the way back to `RetryRelayer`. + +### Model Checking + +The retry logic at the transaction layer can be more complicated than one +imagines. This is because failure of sending transactions may be local and +temporary, and the relayer may receive transaction failures even if a transaction +is eventually committed, such as due to insufficient waiting time before timing +out, or failure on the full node or network while the transaction has already +been broadcasted. If the sending of a successful transaction is incorrectly +identified as a failure, it may result in cascading failures being accumulated +in the relayer, such as repeatedly retrying the sending of transactions with +invalidated nonces. + +Because of this, a lot of rigorous testing is required to ensure that the +combined logic of retrying to send packets and transactions is sound. A good +way to test that is to build a model of the concurrent system and test all +possible states using model checking tools like +[TLA+](https://lamport.azurewebsites.net/tla/tla.html) and +[Apalache](https://apalache.informal.systems/). +On the other +hand, since the relayer framework itself is fully abstract, it is also possble +to treat the relayer framework as a model. This can be potentially done by using +model checking tools for Rust, such as +[Kani](https://github.com/model-checking/kani) and +[Prusti](https://github.com/viperproject/prusti-dev). If that is possible, +it could significantly reduce the effort of model checking, since there +wouldn't be a need to re-implement the relayer logic in a separate language. + +Although we are still in the research phase of exploring the feasibility of +doing model checking in Rust, the abstract nature of the relayer framework +increases the chance of the tools being a good fit. In particular, the +relayer framwork supports `no_std` and is not tied to a specific async runtime. +As a result, fewer problems arise in a symbolic execution environment like Kani, +which does not support std and async runtimes like Tokio. + +### All-In-One Traits + +The relayer framework makes use of context-generic programming to define +dozens of component interfaces and implementations. Although that provides a +lot of flexibility for context implementers to mix and match components as +they see fit, it also requires them to have a deeper understanding of how +each component works and how to combine them using context-generic programming. + +As an analogy, a computer is made of many modular components, such as CPU, RAM, +and storage. With well-defined hardware interfaces, anyone can, in theory, +assemble their own computer with the exact hardware specs that they prefer. +However, even though having modular hardware components is very useful, the +majority of consumers would prefer _not_ to assemble their own computer, or to +understand how each hardware component works. Instead, consumers prefer to have +pre-assembled computers that are designed to fit specific use cases, such as +gaming laptops or smartphones with good cameras, and choose a model that matches +closest to their use cases. + +To help normal users to build custom relayers with minimal effort, the relayer +framework offers _all-in-one_ traits that have most parts of the relayer +pre-configured as _presets_. The relayer framework currently offers two presets: +minimal and full-featured. + +The minimal preset, as its name implies, offers an abstract implementation of a +minimal relayer, which only performs the core logic of relaying with no +additional complexity. Because the relayer is minimal, there are fewer requirements +that the concrete contexts need to implement. As a result, it takes the least +effort for users to build a minimal relayer that targets custom chains like +solomachines, or custom environments like Wasm. + +On the other hand, the full preset includes all available components that the +relayer framework offers, such as message batching, parallel transactions, +packet filtering, retrying of errors, and telemetry. While these features are not +essential for a minimal relayer to work, they are useful for operating +production relayers that require high reliability and efficiency. As a tradeoff, +since the full relayer includes more components with complex logic, it also +imposes more requirements on the concrete context implementation, and there may +be more potential for subtle bugs to be found. + +The minimal preset requires implementers of custom relay contexts to implement +the _one-for-all_ traits such as `OfaBaseChain` and `OfaBaseRelay`. +In addition to that, the full preset requires implementers to also implement +traits like `OfaFullChain` and `OfaFullRelay`. An example use of this is +demonstrated in the later section using the Cosmos chain context. + +### Limitations of All-In-One Traits + +Since the relayer framework only offers two presets, there is a gap between +the minimal preset and the full preset. As a result, users cannot selectively +choose only specific features from the full relayer, such as enabling only +the message batching feature without telemetry. The choice of us imposing +these restrictions is _deliberate_, as we want to keep the all-in-one traits +simple and have a smooth learning curve. + +It is worth noting that even though the relayer components defined using +context-generic programming are fully modular, the all-in-one traits are +designed to be _rigid_ and _specialized_. Observant readers may notice that +the all-in-one traits are similar to the monolithic traits that are +commonly found in Rust code, such as `ChainHandle` in relayer v1. +A common issue of such monolithic traits is that they often become the center +of gravity during development, and grow to contain too many methods that become +difficult to be decoupled and maintained separately. +At the expense of user convenience, the all-in-one traits suffer similar +limitations as the monolithic traits of being very complex and inflexible. +However, the main difference is that the all-in-one traits are nothing but a +thin layer of glue code around the actual components, which are defined as +separate traits. Hence it is possible to opt out of using the all-in-one traits, +and implement the components either directly using CGP, or using other +approaches. + +If users want to mix and match specific features of the relayer, they can +instead bypass the all-in-one traits and use the relayer components directly +with CGP. Similarly, if users want to implement custom components, such as +custom logic for building UpdateClient messages, they should skip the all-in-one +traits and implement the custom logic directly using CGP. + +In the future, the relayer framework may offer more variants of all-in-one +traits that are catered for specific use cases. For example, we may define +multiple specialized relayers that _prioritize_ differently on how packets +should be relayed, such as based on incentivized packet fees or how long the +packet has stayed idle. + +### Cosmos Relayer + +The all-in-one traits offered by the relayer framework serves as a starter +pack for building custom relayers. To dogfood the all-in-one traits, we create +the `ibc-relayer-cosmos` crate which offers the Cosmos-specific +implementation of the relayer. + +Following the available all-in-one presets, the `ibc-relayer-cosmos` +crate offers two variants of Cosmos chains, minimal and full, defined as follows: + +```rust +pub struct MinCosmosChainContext { + pub handle: ChainHandle, + pub signer: Signer, + pub tx_config: TxConfig, + pub key_entry: KeyEntry, +} + +pub struct FullCosmosChainContext { + pub handle: ChainHandle, + pub signer: Signer, + pub tx_config: TxConfig, + pub key_entry: KeyEntry, + pub batch_channel: CosmosBatchChannel, + pub telemetry: CosmosTelemetry, +} +``` + +Compared to the `MinCosmosChainContext`, the `FullCosmosChainContext` +contains two additional fields, `batch_channel` and `telemetry`. +This is because the full preset makes use of the batched message sender and +telemetry components, which requires the chain context to provide the batch +channels and telemetry context. Hence, if a user wants to use the full-featured +Cosmos relayer, they would also have to instantiate and provide the additional +parameters when constructing the chain context. + +The Cosmos chain context is implemented as an MVP for the relayer v1.5. As a +result, it delegates most of the chain operations to the existing `ChainHandle` +code in relayer v1. As we progress toward the v2 relayer, the Cosmos chain +context is expected to eventually remove its dependency on `ChainHandle` and +directly hold low-level fields such as `grpc_address`. + +To make use of the Cosmos chain context for relaying between Cosmos chains, +we implement the one-for-all traits for our Cosmos chain contexts roughly as +follows: + +```rust +impl OfaBaseChain for MinCosmosChainContext +{ + type Preset = MinimalPreset; + + type Error = CosmosError; + + type Height = CosmosHeight; + + type Timestamp = CosmosTimestamp; + + type Message = CosmosMessage; + + type Event = CosmosEvent; + + type ChainStatus = CosmosChainStatus; + + /* ... */ + + async fn query_chain_status(&self) -> Result { + /* ... */ + } + + async fn send_messages( + &self, + messages: Vec, + ) -> Result>, CosmosError> { + /* ... */ + } + + /* ... */ +} +``` + +For demonstration purposes, the above code is slightly simplified from the actual +Cosmos chain implementation. Readers are encouraged to refer to the +`ibc-relayer-cosmos` itself to see the full implementation details. + +In the `OfaBaseChain` implementation for `MinCosmosChainContext`, we first +define the `Preset` type attribute to `MinimalPreset`, indicating that we +only want to build a minimal relayer with our concrete context. We then bind +the abstract types such as `Error` and `Height` to the Cosmos-specific types +such as `CosmosError` and `CosmosHeight`. Finally, we implement the abstract +methods that are required to be provided by the concrete chain implementation, +such as `query_chain_status` and `send_messages`. + +We would also do the same implementation for the concrete relay contexts, such +as implementing `OfaBaseRelay` for `MinCosmosRelayContext`, which would contain two +`MinCosmosChainContext`s - one for the source Cosmos chain, and one for the +destination Cosmos chain. Once the traits are implemented, the relayer +methods would automatically be implemented by wrapping the relay context +inside `OfaRelayWrapper`, such as follows: + +```rust +let relayer = OfaRelayWrapper::new(MinCosmosRelayContext::new(/* ... */)); +let packet = /* ... */; + +// PacketRelayer::relay_packet is automatically implemented +relayer.relay_packet(packet); +``` + +The wrapper types like `OfaRelayWrapper` are newtype wrappers that help provide +automatic implementation of the relayer traits provided by the relayer framework. +The reason it is needed is to avoid having conflicting trait implementations +if we try to implement it without the wrapper types. Aside from that, the two +steps of implementing the one-for-all traits and then wrapping the values inside +the one-for-all wrappers are sufficient for us to build a fully customized +relayer from the relayer framework. + +## Development plan toward relayer v1.5 + +We are slowly progressing toward finishing the relayer v1.5 MVP. At the current +stage, we have finished a full implementation of the `PacketRelayer` +and tested the successful relaying of a single IBC packet for Cosmos chains. +To make the code ready for an initial v1.5 MVP release, the following work still +needs to be completed: + +### IBC Event Source + +To support relaying of multiple packets, the relayer framework needs to define +an event source interface to listen to incoming IBC packets and then spawn +new tasks to relay the packets using `PacketRelayer`. The Cosmos MVP for this +will make use of the event subscription code provided by the v1 relayer +supervisor to implement the event source. + +### Transaction Context + +The current MVP makes use of the existing `ChainHandle`'s `send_messages` method +to submit messages as transactions to the chain. In order to implement custom +strategies for submitting transactions, we are implementing a new transaction +context as an additional layer below the chain context to handle the submission +of messages as transactions to the blockchain. + +Due to the v2 relayer having different concurrency semantics from the v1 relayer, +most of the messages sent by the new relayer would get queued up and be sent +sequentially if they are sent using the existing `ChainHandle`. As a result, +the transaction context is needed for the new relayer to demonstrate a measurable +improvement in performance from the v1 relayer. + +### CLI Integration + +The new relayer will be made available by adding it as an experimental +subcommand in the current relayer CLI. For example, the following CLI could +be introduced to start the new relayer: + +```bash +hermes new-relay start +``` + +The new relayer CLI would have to implement the logic for loading the relayer +config in the current format, and then constructing the Cosmos relay context +based on that. + +The initial new relayer MVP would initially lack many features from the current +v1 relayer, such as packet clearing, robust retry logic, and handshakes. As a +result, it is meant to be experimental and not used in production. The CLI +may be conditionally enabled from an `experimental` feature flag, so that +official releases of the Hermes relayer do not expose the CLI to be accidentally +used by relayer operators. + +## Development plan toward relayer v2 + +To progress the relayer v1.5 MVP toward relayer v2, there are several key +milestones that we need to reach: + +### Remove Dependency on `ForeignClient` + +The relayer v1.5 MVP currently relies on `ForeignClient` to perform operations +such as building UpdateClient messages. As a tradeoff, this restricts the +Cosmos relay context to be usable with two Cosmos chains. To support relaying +between a Cosmos chain and a non-Cosmos chain, it is necessary to remove +the dependency on `ForeignClient`, as non-Cosmos chains would otherwise +have to implement `ChainHandle` in order to support the heterogenous relaying. + +### Remove Dependency on `ChainHandle` + +The relayer v1.5 MVP currently relies on `ChainHandle` for the majority of chain +operations, such as performing queries and building merkle proofs. However as +`ChainHandle` has a thread-based concurrency, it can only handle one operation +at a time. With the transaction context, the new relayer is able to parallelize +the query operations from the sending of transactions. However, the query +part of the chain remains a bottleneck, and may impact the performance of the +new relayer. + +As a result, the new Cosmos relayer needs to remove its dependency on +`ChainHandle` so that it can perform concurrent queries to the chain. This +would also allow the relayer framework to implement proper caching layers +to reduce the traffic on the full node. + +### Heterogenous Relaying MVP + +The new relayer needs to demonstrate that the new architecture is sufficient +to support relaying between different types of chains, such as Cosmos to +Substrate relaying. To show that, we need to implement at least one non-Cosmos +chain context and implement a relay context that supports two different +chain contexts. + +An MVP candidate for heterogenous relaying is to build a mock solomachine chain +context. Because the solomachine spec is relatively simple, it should require +less effort to build a solomachine chain context from scratch. Furthermore, +as the solomachine light client is already officially supported in ibc-go, +we can test solomachine relaying without having to use a custom Cosmos chain +with custom light clients. + +Once the mock solomachine chain context is implemented, it would be possible to +write integration tests to relay IBC packets between an in-memory solomachine +with a live Cosmos chain. + +### Multiple Strategies for Concurrent Transactions + +A key goal for relayer v2 is to support the submission of concurrent +transactions with upcoming major changes for Cosmos chains, in particular +prioritized mempool and ABCI++. Depending on the chain implementation, +it may not be possible to submit parallel transactions with different nonces. + +To mitigate this, the relayer may need different strategies for allocating nonces. +For example, future Cosmos SDK chains may offer parallel lanes of account +sequences, so that parallel transactions can make use of nonces from different +lanes. + +An alternative strategy would be to multiplex the sending of transactions between +multiple signers. However, as this may impose an operational burden on relayer +operators, the relayer may need to automate the process of managing multiple +wallets, such as with the use of the fee grant module. + +The relayer v1.5 MVP offers a nonce allocator interface as a starting point for +implementing different nonce allocation strategies. However, it currently +only implements a naive nonce allocator which does _not_ support parallel +transactions. In contrast, the relayer v2 would need to have multiple nonce +allocators implemented, and test them thoroughly with new versions of +Cosmos chains. + +The relayer v1.5 MVP will not expose interfaces that support sending +transactions with multiple signers. Since such features also require significant +effort in the form of proper UX design, it is left as a task for relayer v2 to implement. + + +# Status + +Proposed + +# Consequences + +# Positive + +# Negative + +# Neutral + +# References diff --git a/docs/architecture/adr-template.md b/docs/architecture/adr-template.md index 28a5ecfbbc..c16657ebfb 100644 --- a/docs/architecture/adr-template.md +++ b/docs/architecture/adr-template.md @@ -5,7 +5,7 @@ ## Context -> This section contains all the context one needs to understand the current state, and why there is a problem. It should be as succinct as possible and introduce the high level idea behind the solution. +> This section contains all the context one needs to understand the current state, and why there is a problem. It should be as succinct as possible and introduce the high level idea behind the solution. ## Decision > This section explains all of the details of the proposed solution, including implementation details. diff --git a/tools/integration-test/Cargo.toml b/tools/integration-test/Cargo.toml index c45e531f5f..26ed6a1bd6 100644 --- a/tools/integration-test/Cargo.toml +++ b/tools/integration-test/Cargo.toml @@ -15,12 +15,18 @@ publish = false [dependencies] ibc-relayer-types = { path = "../../crates/relayer-types" } ibc-relayer = { path = "../../crates/relayer" } +ibc-relayer-all-in-one = { path = "../../crates/relayer-all-in-one" } +ibc-relayer-components = { version = "0.1.0", path = "../../crates/relayer-components" } +ibc-relayer-components-extra = { version = "0.1.0", path = "../../crates/relayer-components-extra" } +ibc-relayer-runtime = { path = "../../crates/relayer-runtime" } +ibc-relayer-cosmos = { path = "../../crates/relayer-cosmos" } ibc-test-framework = { path = "../test-framework" } http = "0.2.9" prost = { version = "0.11" } serde_json = "1" time = "0.3" +tokio = { version = "1.0", features = ["rt"] } toml = "0.7" tonic = { version = "0.9", features = ["tls", "tls-roots"] } serde = "1.0.166" diff --git a/tools/integration-test/src/bin/test_setup_with_binary_channel.rs b/tools/integration-test/src/bin/test_setup_with_binary_channel.rs index d2710d2ee9..4fd7147538 100644 --- a/tools/integration-test/src/bin/test_setup_with_binary_channel.rs +++ b/tools/integration-test/src/bin/test_setup_with_binary_channel.rs @@ -54,13 +54,7 @@ impl TestOverrides for Test { } impl BinaryChannelTest for Test { - fn run( - &self, - _config: &TestConfig, - _relayer: RelayerDriver, - _chains: ConnectedChains, - _channel: ConnectedChannel, - ) -> Result<(), Error> { + fn run(&self, _relayer: RelayerDriver, _context: &Context) -> Result<(), Error> { suspend() } } diff --git a/tools/integration-test/src/bin/test_setup_with_fee_enabled_binary_channel.rs b/tools/integration-test/src/bin/test_setup_with_fee_enabled_binary_channel.rs index 77172a4c44..76aba3aaf7 100644 --- a/tools/integration-test/src/bin/test_setup_with_fee_enabled_binary_channel.rs +++ b/tools/integration-test/src/bin/test_setup_with_fee_enabled_binary_channel.rs @@ -59,13 +59,7 @@ impl TestOverrides for Test { } impl BinaryChannelTest for Test { - fn run( - &self, - _config: &TestConfig, - _relayer: RelayerDriver, - _chains: ConnectedChains, - _channel: ConnectedChannel, - ) -> Result<(), Error> { + fn run(&self, _relayer: RelayerDriver, _context: &Context) -> Result<(), Error> { suspend() } } diff --git a/tools/integration-test/src/mbt/transfer.rs b/tools/integration-test/src/mbt/transfer.rs index b2156a04da..6dd985eba5 100644 --- a/tools/integration-test/src/mbt/transfer.rs +++ b/tools/integration-test/src/mbt/transfer.rs @@ -6,6 +6,7 @@ use ibc_relayer::config::{ ModeConfig, Packets as ConfigPackets, }; +use ibc_test_framework::framework::next::chain::{HasTwoChains, HasTwoChannels}; use ibc_test_framework::prelude::*; use ibc_test_framework::types::tagged::mono::Tagged; @@ -168,13 +169,12 @@ impl TestOverrides for IbcTransferMBT { } impl BinaryChannelTest for IbcTransferMBT { - fn run( - &self, - _config: &TestConfig, - relayer: RelayerDriver, - chains: ConnectedChains, - channels: ConnectedChannel, - ) -> Result<(), Error> { + fn run(&self, relayer: RelayerDriver, context: &Context) -> Result<(), Error> + where + Context: HasTwoChains + HasTwoChannels, + { + let chains = context.chains(); + let channels = context.channel(); // relayer is spawned let mut supervisor = Some(relayer.spawn_supervisor()?); @@ -191,7 +191,7 @@ impl BinaryChannelTest for IbcTransferMBT { amount, } => { info!("[LocalTransfer] Init"); - let node: Tagged = get_chain(&chains, *chain_id); + let node: Tagged = get_chain(chains, *chain_id); super::handlers::local_transfer_handler( node, *source, *target, *denom, *amount, )?; @@ -217,7 +217,7 @@ impl BinaryChannelTest for IbcTransferMBT { assert!( super::utils::get_committed_packets_at_src( &chains.handle_a, - &channels + channels )? .is_empty(), "no packets present" @@ -226,14 +226,14 @@ impl BinaryChannelTest for IbcTransferMBT { super::handlers::ibc_transfer_send_packet( chains.node_a.as_ref(), chains.node_b.as_ref(), - &channels, + channels, packet, )?; assert_eq!( super::utils::get_committed_packets_at_src( &chains.handle_a, - &channels, + channels, )? .len(), 1, @@ -279,7 +279,7 @@ impl BinaryChannelTest for IbcTransferMBT { super::handlers::ibc_transfer_receive_packet( chains.node_a.as_ref(), chains.node_b.as_ref(), - &channels, + channels, packet, )?; assert_eq!( @@ -302,7 +302,7 @@ impl BinaryChannelTest for IbcTransferMBT { assert_eq!( super::utils::get_acknowledged_packets_at_dst( &chains.handle_a, - &channels + channels )? .len(), 1, @@ -322,7 +322,7 @@ impl BinaryChannelTest for IbcTransferMBT { assert!( super::utils::get_committed_packets_at_src( &chains.handle_a, - &channels + channels )? .is_empty(), "commitment is completed" diff --git a/tools/integration-test/src/tests/clean_workers.rs b/tools/integration-test/src/tests/clean_workers.rs index b69637780a..69a671956b 100644 --- a/tools/integration-test/src/tests/clean_workers.rs +++ b/tools/integration-test/src/tests/clean_workers.rs @@ -1,5 +1,6 @@ use ibc_relayer::config::{self, ModeConfig}; use ibc_relayer::object::ObjectType; +use ibc_test_framework::framework::next::chain::{CanSpawnRelayer, HasTwoChains, HasTwoChannels}; use ibc_test_framework::relayer::channel::init_channel; use ibc_test_framework::{prelude::*, util::random::random_u128_range}; @@ -41,13 +42,12 @@ impl TestOverrides for CleanPacketWorkersTest { } impl BinaryChannelTest for CleanPacketWorkersTest { - fn run( - &self, - _config: &TestConfig, - relayer: RelayerDriver, - chains: ConnectedChains, - channel: ConnectedChannel, - ) -> Result<(), Error> { + fn run(&self, relayer: RelayerDriver, context: &Context) -> Result<(), Error> + where + Context: HasTwoChains + HasTwoChannels + CanSpawnRelayer, + { + let chains = context.chains(); + let channel = context.channel(); let denom_a = chains.node_a.denom(); let wallet_a = chains.node_a.wallets().user1().cloned(); @@ -104,13 +104,12 @@ impl TestOverrides for CleanChannelWorkersTest { } impl BinaryChannelTest for CleanChannelWorkersTest { - fn run( - &self, - _config: &TestConfig, - relayer: RelayerDriver, - chains: ConnectedChains, - channel: ConnectedChannel, - ) -> Result<(), Error> { + fn run(&self, relayer: RelayerDriver, context: &Context) -> Result<(), Error> + where + Context: HasTwoChains + HasTwoChannels + CanSpawnRelayer, + { + let chains = context.chains(); + let channel = context.channel(); let supervisor = relayer.spawn_supervisor()?; // Optimistically send chan-open-init to B, without a connection available yet on A diff --git a/tools/integration-test/src/tests/clear_packet.rs b/tools/integration-test/src/tests/clear_packet.rs index 9eef3f6c26..c36133f167 100644 --- a/tools/integration-test/src/tests/clear_packet.rs +++ b/tools/integration-test/src/tests/clear_packet.rs @@ -1,5 +1,6 @@ use std::thread; +use ibc_test_framework::framework::next::chain::{CanSpawnRelayer, HasTwoChains, HasTwoChannels}; use ibc_test_framework::prelude::*; use ibc_test_framework::util::random::random_u128_range; @@ -56,13 +57,12 @@ impl TestOverrides for ClearPacketRecoveryTest { } impl BinaryChannelTest for ClearPacketTest { - fn run( - &self, - _config: &TestConfig, - relayer: RelayerDriver, - chains: ConnectedChains, - channel: ConnectedChannel, - ) -> Result<(), Error> { + fn run(&self, _relayer: RelayerDriver, context: &Context) -> Result<(), Error> + where + Context: HasTwoChains + HasTwoChannels + CanSpawnRelayer, + { + let chains = context.chains(); + let channel = context.channel(); let denom_a = chains.node_a.denom(); let wallet_a = chains.node_a.wallets().user1().cloned(); @@ -91,7 +91,7 @@ impl BinaryChannelTest for ClearPacketTest { sleep(Duration::from_secs(1)); // Spawn the supervisor only after the first IBC trasnfer - relayer.with_supervisor(|| { + context.with_supervisor(|| { sleep(Duration::from_secs(1)); let amount2 = denom_a.with_amount(random_u128_range(1000, 5000)); @@ -132,13 +132,12 @@ impl BinaryChannelTest for ClearPacketTest { } impl BinaryChannelTest for ClearPacketRecoveryTest { - fn run( - &self, - _config: &TestConfig, - relayer: RelayerDriver, - chains: ConnectedChains, - channel: ConnectedChannel, - ) -> Result<(), Error> { + fn run(&self, _relayer: RelayerDriver, context: &Context) -> Result<(), Error> + where + Context: HasTwoChains + HasTwoChannels + CanSpawnRelayer, + { + let chains = context.chains(); + let channel = context.channel(); let denom_a = chains.node_a.denom(); let denom_b1 = chains.node_b.denom(); @@ -170,7 +169,7 @@ impl BinaryChannelTest for ClearPacketRecoveryTest { &denom_a, )?; - relayer.with_supervisor(|| { + context.with_supervisor(|| { chains.node_b.chain_driver().assert_eventual_wallet_amount( &wallet_b.address(), &denom_b2.with_amount(amount1).as_ref(), @@ -202,13 +201,13 @@ impl TestOverrides for ClearPacketNoScanTest { } impl BinaryChannelTest for ClearPacketNoScanTest { - fn run( - &self, - _config: &TestConfig, - relayer: RelayerDriver, - chains: ConnectedChains, - channel: ConnectedChannel, - ) -> Result<(), Error> { + fn run(&self, _relayer: RelayerDriver, context: &Context) -> Result<(), Error> + where + Context: HasTwoChains + HasTwoChannels + CanSpawnRelayer, + { + let chains = context.chains(); + let channel = context.channel(); + let denom_a = chains.node_a.denom(); let wallet_a = chains.node_a.wallets().user1().cloned(); @@ -237,7 +236,7 @@ impl BinaryChannelTest for ClearPacketNoScanTest { thread::sleep(Duration::from_secs(5)); - relayer.with_supervisor(|| { + context.with_supervisor(|| { info!("Assert clear on start does not trigger"); chains.node_a.chain_driver().assert_eventual_wallet_amount( &wallet_a.address(), diff --git a/tools/integration-test/src/tests/client_filter.rs b/tools/integration-test/src/tests/client_filter.rs index 3378bfcc14..cdaec47a0c 100644 --- a/tools/integration-test/src/tests/client_filter.rs +++ b/tools/integration-test/src/tests/client_filter.rs @@ -27,6 +27,7 @@ use ibc_relayer::foreign_client::CreateOptions; use ibc_relayer::object::ObjectType; use ibc_relayer_types::clients::ics07_tendermint::client_state::ClientState as TmClientState; +use ibc_test_framework::framework::next::chain::HasTwoChains; use ibc_test_framework::prelude::*; struct ClientFilterBlocksConnectionTest; @@ -58,6 +59,10 @@ impl TestOverrides for ClientFilterBlocksConnectionTest { trust_threshold: Some(TrustThreshold::TWO_THIRDS), } } + + fn should_spawn_supervisor(&self) -> bool { + false + } } impl TestOverrides for ClientFilterAllowsConnectionTest { @@ -76,16 +81,18 @@ impl TestOverrides for ClientFilterAllowsConnectionTest { trust_threshold: Some(TrustThreshold::ONE_THIRD), } } + + fn should_spawn_supervisor(&self) -> bool { + false + } } impl BinaryChannelTest for ClientFilterBlocksConnectionTest { - fn run( - &self, - _config: &TestConfig, - relayer: RelayerDriver, - chains: ConnectedChains, - _channel: ConnectedChannel, - ) -> Result<(), Error> { + fn run(&self, relayer: RelayerDriver, context: &Context) -> Result<(), Error> + where + Context: HasTwoChains, + { + let chains = context.chains().clone(); let mut policy = FilterPolicy::default(); let client_id = chains.foreign_clients.client_a_to_b.id(); @@ -116,13 +123,11 @@ impl BinaryChannelTest for ClientFilterBlocksConnectionTest { } impl BinaryChannelTest for ClientFilterAllowsConnectionTest { - fn run( - &self, - _config: &TestConfig, - relayer: RelayerDriver, - chains: ConnectedChains, - _channel: ConnectedChannel, - ) -> Result<(), Error> { + fn run(&self, relayer: RelayerDriver, context: &Context) -> Result<(), Error> + where + Context: HasTwoChains, + { + let chains = context.chains().clone(); let mut policy = FilterPolicy::default(); let client_id = chains.foreign_clients.client_a_to_b.id(); diff --git a/tools/integration-test/src/tests/connection_delay.rs b/tools/integration-test/src/tests/connection_delay.rs index db73cfe7ee..948b9f8ad3 100644 --- a/tools/integration-test/src/tests/connection_delay.rs +++ b/tools/integration-test/src/tests/connection_delay.rs @@ -1,4 +1,5 @@ use core::time::Duration; +use ibc_test_framework::framework::next::chain::{CanSpawnRelayer, HasTwoChains, HasTwoChannels}; use time::OffsetDateTime; use ibc_test_framework::ibc::denom::derive_ibc_denom; @@ -18,86 +19,89 @@ impl TestOverrides for ConnectionDelayTest { fn connection_delay(&self) -> Duration { CONNECTION_DELAY } + + fn should_spawn_supervisor(&self) -> bool { + false + } } impl BinaryChannelTest for ConnectionDelayTest { - fn run( - &self, - _config: &TestConfig, - relayer: RelayerDriver, - chains: ConnectedChains, - channel: ConnectedChannel, - ) -> Result<(), Error> { - relayer.with_supervisor(|| { - let denom_a = chains.node_a.denom(); - - let wallet_a = chains.node_a.wallets().user1().cloned(); - let wallet_b = chains.node_b.wallets().user1().cloned(); - - let balance_a = chains - .node_a - .chain_driver() - .query_balance(&wallet_a.address(), &denom_a)?; - - let a_to_b_amount = random_u128_range(1000, 5000); - - info!( - "Sending IBC transfer from chain {} to chain {} with amount of {} {}", - chains.chain_id_a(), - chains.chain_id_b(), - a_to_b_amount, - denom_a - ); - - chains.node_a.chain_driver().ibc_transfer_token( - &channel.port_a.as_ref(), - &channel.channel_id_a.as_ref(), - &wallet_a.as_ref(), - &wallet_b.address(), - &denom_a.with_amount(a_to_b_amount).as_ref(), - )?; - - let time1 = OffsetDateTime::now_utc(); - - let denom_b = derive_ibc_denom( - &channel.port_b.as_ref(), - &channel.channel_id_b.as_ref(), - &denom_a, - )?; - - info!( - "Waiting for user on chain B to receive IBC transferred amount of {} {}", - a_to_b_amount, denom_b - ); - - chains.node_a.chain_driver().assert_eventual_wallet_amount( - &wallet_a.address(), - &(balance_a - a_to_b_amount).as_ref(), - )?; - - chains.node_b.chain_driver().assert_eventual_wallet_amount( - &wallet_b.address(), - &denom_b.with_amount(a_to_b_amount).as_ref(), - )?; - - info!( - "successfully performed IBC transfer from chain {} to chain {}", - chains.chain_id_a(), - chains.chain_id_b(), - ); - - let time2 = OffsetDateTime::now_utc(); - - assert_gt( - &format!( - "Expect IBC transfer to only be successfull after {}s", - CONNECTION_DELAY.as_secs() - ), - &(time2 - time1).try_into().unwrap(), - &CONNECTION_DELAY, - )?; - - Ok(()) - }) + fn run(&self, _relayer: RelayerDriver, context: &Context) -> Result<(), Error> + where + Context: HasTwoChains + HasTwoChannels + CanSpawnRelayer, + { + let _handle = context.spawn_relayer()?; + let chains = context.chains(); + let channel = context.channel(); + + let denom_a = chains.node_a.denom(); + + let wallet_a = chains.node_a.wallets().user1().cloned(); + let wallet_b = chains.node_b.wallets().user1().cloned(); + + let balance_a = chains + .node_a + .chain_driver() + .query_balance(&wallet_a.address(), &denom_a)?; + + let a_to_b_amount = random_u128_range(1000, 5000); + + info!( + "Sending IBC transfer from chain {} to chain {} with amount of {} {}", + chains.chain_id_a(), + chains.chain_id_b(), + a_to_b_amount, + denom_a + ); + + chains.node_a.chain_driver().ibc_transfer_token( + &channel.port_a.as_ref(), + &channel.channel_id_a.as_ref(), + &wallet_a.as_ref(), + &wallet_b.address(), + &denom_a.with_amount(a_to_b_amount).as_ref(), + )?; + + let time1 = OffsetDateTime::now_utc(); + + let denom_b = derive_ibc_denom( + &channel.port_b.as_ref(), + &channel.channel_id_b.as_ref(), + &denom_a, + )?; + + info!( + "Waiting for user on chain B to receive IBC transferred amount of {} {}", + a_to_b_amount, denom_b + ); + + chains.node_a.chain_driver().assert_eventual_wallet_amount( + &wallet_a.address(), + &(balance_a - a_to_b_amount).as_ref(), + )?; + + chains.node_b.chain_driver().assert_eventual_wallet_amount( + &wallet_b.address(), + &denom_b.with_amount(a_to_b_amount).as_ref(), + )?; + + info!( + "successfully performed IBC transfer from chain {} to chain {}", + chains.chain_id_a(), + chains.chain_id_b(), + ); + + let time2 = OffsetDateTime::now_utc(); + + assert_gt( + &format!( + "Expect IBC transfer to only be successfull after {}s", + CONNECTION_DELAY.as_secs() + ), + &(time2 - time1).try_into().unwrap(), + &CONNECTION_DELAY, + )?; + + Ok(()) } } diff --git a/tools/integration-test/src/tests/denom_trace.rs b/tools/integration-test/src/tests/denom_trace.rs index 5c53a22f1a..bdeb7a16ae 100644 --- a/tools/integration-test/src/tests/denom_trace.rs +++ b/tools/integration-test/src/tests/denom_trace.rs @@ -1,3 +1,6 @@ +use ibc_test_framework::framework::next::chain::{ + CanShutdown, CanSpawnRelayer, HasTwoChains, HasTwoChannels, +}; use ibc_test_framework::ibc::denom::derive_ibc_denom; use ibc_test_framework::prelude::*; @@ -8,19 +11,23 @@ fn test_ibc_denom_trace() -> Result<(), Error> { pub struct IbcDenomTraceTest; -impl TestOverrides for IbcDenomTraceTest {} +impl TestOverrides for IbcDenomTraceTest { + fn should_spawn_supervisor(&self) -> bool { + false + } +} /// In order to test the denom_trace at first transfer IBC tokens from Chain A /// to Chain B, and then retrieving the trace hash of the transfered tokens. /// The trace hash is used to query the denom_trace and the result is verified. impl BinaryChannelTest for IbcDenomTraceTest { - fn run( - &self, - _config: &TestConfig, - _relayer: RelayerDriver, - chains: ConnectedChains, - channel: ConnectedChannel, - ) -> Result<(), Error> { + fn run(&self, _relayer: RelayerDriver, context: &Context) -> Result<(), Error> + where + Context: HasTwoChains + HasTwoChannels + CanSpawnRelayer + CanShutdown, + { + let handle = context.spawn_relayer()?; + let chains = context.chains(); + let channel = context.channel(); let a_to_b_amount: u64 = 1234; let denom_a = chains.node_a.denom(); @@ -87,6 +94,8 @@ impl BinaryChannelTest for IbcDenomTraceTest { &denom_a.value().as_str().to_string(), )?; + context.shutdown(handle); + Ok(()) } } diff --git a/tools/integration-test/src/tests/error_events.rs b/tools/integration-test/src/tests/error_events.rs index 24602d0122..a10554e082 100644 --- a/tools/integration-test/src/tests/error_events.rs +++ b/tools/integration-test/src/tests/error_events.rs @@ -1,5 +1,6 @@ use ibc_relayer::chain::tracking::TrackedMsgs; use ibc_relayer_types::events::IbcEvent; +use ibc_test_framework::framework::next::chain::{HasTwoChains, HasTwoChannels}; use ibc_test_framework::prelude::*; use ibc_test_framework::relayer::transfer::build_transfer_message; @@ -10,16 +11,19 @@ fn test_error_events() -> Result<(), Error> { pub struct ErrorEventsTest; -impl TestOverrides for ErrorEventsTest {} +impl TestOverrides for ErrorEventsTest { + fn should_spawn_supervisor(&self) -> bool { + false + } +} impl BinaryChannelTest for ErrorEventsTest { - fn run( - &self, - _config: &TestConfig, - _relayer: RelayerDriver, - chains: ConnectedChains, - channel: ConnectedChannel, - ) -> Result<(), Error> { + fn run(&self, _relayer: RelayerDriver, context: &Context) -> Result<(), Error> + where + Context: HasTwoChains + HasTwoChannels, + { + let chains = context.chains(); + let channel = context.channel(); let denom_a = chains.node_a.denom(); let wallet_a = chains.node_a.wallets().user1().cloned(); diff --git a/tools/integration-test/src/tests/example.rs b/tools/integration-test/src/tests/example.rs index f46c7a209d..ae490bbdf4 100644 --- a/tools/integration-test/src/tests/example.rs +++ b/tools/integration-test/src/tests/example.rs @@ -15,13 +15,7 @@ pub struct ExampleTest; impl TestOverrides for ExampleTest {} impl BinaryChannelTest for ExampleTest { - fn run( - &self, - _config: &TestConfig, - _relayer: RelayerDriver, - _chains: ConnectedChains, - _channel: ConnectedChannel, - ) -> Result<(), Error> { + fn run(&self, _relayer: RelayerDriver, _context: &Context) -> Result<(), Error> { suspend() } } diff --git a/tools/integration-test/src/tests/execute_schedule.rs b/tools/integration-test/src/tests/execute_schedule.rs index 1dd6b5262a..391fb5c9d6 100644 --- a/tools/integration-test/src/tests/execute_schedule.rs +++ b/tools/integration-test/src/tests/execute_schedule.rs @@ -12,6 +12,7 @@ //! later found in the pending queue), but all of the subsequent messages should //! exist in the pending queue. +use ibc_test_framework::framework::next::chain::{HasTwoChains, HasTwoChannels}; use ibc_test_framework::prelude::*; use ibc_test_framework::util::random::random_u128_range; @@ -34,13 +35,12 @@ impl TestOverrides for ExecuteScheduleTest { } impl BinaryChannelTest for ExecuteScheduleTest { - fn run( - &self, - _config: &TestConfig, - _relayer: RelayerDriver, - chains: ConnectedChains, - channel: ConnectedChannel, - ) -> Result<(), Error> { + fn run(&self, _relayer: RelayerDriver, context: &Context) -> Result<(), Error> + where + Context: HasTwoChains + HasTwoChannels, + { + let chains = context.chains(); + let channel = context.channel(); let amount1 = random_u128_range(1000, 5000); let chain_a_link_opts = LinkParameters { diff --git a/tools/integration-test/src/tests/fee/auto_forward_relayer.rs b/tools/integration-test/src/tests/fee/auto_forward_relayer.rs index 7d0b6bdcc3..aeed6fa7e9 100644 --- a/tools/integration-test/src/tests/fee/auto_forward_relayer.rs +++ b/tools/integration-test/src/tests/fee/auto_forward_relayer.rs @@ -10,6 +10,7 @@ //! correct parties involved in the transaction. use ibc_relayer_types::core::ics04_channel::version::Version; +use ibc_test_framework::framework::next::chain::{HasTwoChains, HasTwoChannels}; use ibc_test_framework::prelude::*; use ibc_test_framework::util::random::random_u128_range; @@ -31,13 +32,12 @@ impl TestOverrides for AutoForwardRelayerTest { } impl BinaryChannelTest for AutoForwardRelayerTest { - fn run( - &self, - _config: &TestConfig, - _relayer: RelayerDriver, - chains: ConnectedChains, - channel: ConnectedChannel, - ) -> Result<(), Error> { + fn run(&self, _relayer: RelayerDriver, context: &Context) -> Result<(), Error> + where + Context: HasTwoChains + HasTwoChannels, + { + let chains = context.chains(); + let channel = context.channel(); let chain_driver_a = chains.node_a.chain_driver(); let chain_driver_b = chains.node_b.chain_driver(); diff --git a/tools/integration-test/src/tests/fee/filter_fees.rs b/tools/integration-test/src/tests/fee/filter_fees.rs index 64a4c00504..b02bbe5d3a 100644 --- a/tools/integration-test/src/tests/fee/filter_fees.rs +++ b/tools/integration-test/src/tests/fee/filter_fees.rs @@ -3,6 +3,7 @@ use std::collections::HashMap; use ibc_relayer::config::filter::{ChannelPolicy, FeePolicy, FilterPattern, MinFee}; use ibc_relayer::config::PacketFilter; use ibc_relayer_types::core::ics04_channel::version::Version; +use ibc_test_framework::framework::next::chain::{HasTwoChains, HasTwoChannels}; use ibc_test_framework::prelude::*; use ibc_test_framework::util::random::random_u128_range; @@ -37,13 +38,12 @@ impl TestOverrides for FilterIncentivizedFeesRelayerTest { } impl BinaryChannelTest for FilterIncentivizedFeesRelayerTest { - fn run( - &self, - _config: &TestConfig, - _relayer: RelayerDriver, - chains: ConnectedChains, - channel: ConnectedChannel, - ) -> Result<(), Error> { + fn run(&self, _relayer: RelayerDriver, context: &Context) -> Result<(), Error> + where + Context: HasTwoChains + HasTwoChannels, + { + let chains = context.chains(); + let channel = context.channel(); let chain_driver_a = chains.node_a.chain_driver(); let chain_driver_b = chains.node_b.chain_driver(); @@ -185,13 +185,12 @@ impl TestOverrides for FilterByChannelIncentivizedFeesRelayerTest { } impl BinaryChannelTest for FilterByChannelIncentivizedFeesRelayerTest { - fn run( - &self, - _config: &TestConfig, - _relayer: RelayerDriver, - chains: ConnectedChains, - channel: ConnectedChannel, - ) -> Result<(), Error> { + fn run(&self, _relayer: RelayerDriver, context: &Context) -> Result<(), Error> + where + Context: HasTwoChains + HasTwoChannels, + { + let chains = context.chains(); + let channel = context.channel(); let chain_driver_a = chains.node_a.chain_driver(); let chain_driver_b = chains.node_b.chain_driver(); diff --git a/tools/integration-test/src/tests/fee/forward_relayer.rs b/tools/integration-test/src/tests/fee/forward_relayer.rs index f5dc0695fc..eeb7289ad8 100644 --- a/tools/integration-test/src/tests/fee/forward_relayer.rs +++ b/tools/integration-test/src/tests/fee/forward_relayer.rs @@ -9,6 +9,7 @@ //! correct parties involved in the transaction. use ibc_relayer_types::core::ics04_channel::version::Version; +use ibc_test_framework::framework::next::chain::{HasTwoChains, HasTwoChannels}; use ibc_test_framework::prelude::*; use ibc_test_framework::util::random::random_u128_range; @@ -26,13 +27,12 @@ impl TestOverrides for ForwardRelayerTest { } impl BinaryChannelTest for ForwardRelayerTest { - fn run( - &self, - _config: &TestConfig, - _relayer: RelayerDriver, - chains: ConnectedChains, - channel: ConnectedChannel, - ) -> Result<(), Error> { + fn run(&self, _relayer: RelayerDriver, context: &Context) -> Result<(), Error> + where + Context: HasTwoChains + HasTwoChannels, + { + let chains = context.chains(); + let channel = context.channel(); let chain_driver_a = chains.node_a.chain_driver(); let chain_driver_b = chains.node_b.chain_driver(); diff --git a/tools/integration-test/src/tests/fee/no_forward_relayer.rs b/tools/integration-test/src/tests/fee/no_forward_relayer.rs index f623126b55..f9765c57b1 100644 --- a/tools/integration-test/src/tests/fee/no_forward_relayer.rs +++ b/tools/integration-test/src/tests/fee/no_forward_relayer.rs @@ -11,6 +11,7 @@ //! refunded to the payer. use ibc_relayer_types::core::ics04_channel::version::Version; +use ibc_test_framework::framework::next::chain::{HasTwoChains, HasTwoChannels}; use ibc_test_framework::prelude::*; use ibc_test_framework::util::random::random_u128_range; @@ -48,13 +49,12 @@ impl TestOverrides for InvalidForwardRelayerTest { } impl BinaryChannelTest for NoForwardRelayerTest { - fn run( - &self, - _config: &TestConfig, - _relayer: RelayerDriver, - chains: ConnectedChains, - channel: ConnectedChannel, - ) -> Result<(), Error> { + fn run(&self, _relayer: RelayerDriver, context: &Context) -> Result<(), Error> + where + Context: HasTwoChains + HasTwoChannels, + { + let chains = context.chains(); + let channel = context.channel(); let chain_driver_a = chains.node_a.chain_driver(); let chain_driver_b = chains.node_b.chain_driver(); @@ -126,13 +126,12 @@ impl BinaryChannelTest for NoForwardRelayerTest { } impl BinaryChannelTest for InvalidForwardRelayerTest { - fn run( - &self, - _config: &TestConfig, - _relayer: RelayerDriver, - chains: ConnectedChains, - channel: ConnectedChannel, - ) -> Result<(), Error> { + fn run(&self, _relayer: RelayerDriver, context: &Context) -> Result<(), Error> + where + Context: HasTwoChains + HasTwoChannels, + { + let chains = context.chains(); + let channel = context.channel(); let chain_driver_a = chains.node_a.chain_driver(); let chain_driver_b = chains.node_b.chain_driver(); diff --git a/tools/integration-test/src/tests/fee/non_fee_channel.rs b/tools/integration-test/src/tests/fee/non_fee_channel.rs index b7b026ae3a..b04ba97762 100644 --- a/tools/integration-test/src/tests/fee/non_fee_channel.rs +++ b/tools/integration-test/src/tests/fee/non_fee_channel.rs @@ -8,6 +8,7 @@ //! ensures then that the transaction still follows through using //! `ibc_transfer_token` if the transfer without fees is used. +use ibc_test_framework::framework::next::chain::{HasTwoChains, HasTwoChannels}; use ibc_test_framework::prelude::*; use ibc_test_framework::util::random::random_u128_range; @@ -25,13 +26,12 @@ impl TestOverrides for NonFeeChannelTest { } impl BinaryChannelTest for NonFeeChannelTest { - fn run( - &self, - _config: &TestConfig, - _relayer: RelayerDriver, - chains: ConnectedChains, - channel: ConnectedChannel, - ) -> Result<(), Error> { + fn run(&self, _relayer: RelayerDriver, context: &Context) -> Result<(), Error> + where + Context: HasTwoChains + HasTwoChannels, + { + let chains = context.chains(); + let channel = context.channel(); let chain_driver_a = chains.node_a.chain_driver(); let chain_driver_b = chains.node_b.chain_driver(); diff --git a/tools/integration-test/src/tests/fee/pay_fee_async.rs b/tools/integration-test/src/tests/fee/pay_fee_async.rs index f62d089ff1..4480768cae 100644 --- a/tools/integration-test/src/tests/fee/pay_fee_async.rs +++ b/tools/integration-test/src/tests/fee/pay_fee_async.rs @@ -20,6 +20,7 @@ use ibc_relayer_types::core::ics04_channel::version::Version; use ibc_relayer_types::events::IbcEvent; +use ibc_test_framework::framework::next::chain::{HasTwoChains, HasTwoChannels}; use ibc_test_framework::prelude::*; use ibc_test_framework::util::random::random_u128_range; @@ -41,13 +42,12 @@ impl TestOverrides for PayPacketFeeAsyncTest { } impl BinaryChannelTest for PayPacketFeeAsyncTest { - fn run( - &self, - _config: &TestConfig, - relayer: RelayerDriver, - chains: ConnectedChains, - channel: ConnectedChannel, - ) -> Result<(), Error> { + fn run(&self, relayer: RelayerDriver, context: &Context) -> Result<(), Error> + where + Context: HasTwoChains + HasTwoChannels, + { + let chains = context.chains(); + let channel = context.channel(); let chain_driver_a = chains.node_a.chain_driver(); let chain_driver_b = chains.node_b.chain_driver(); diff --git a/tools/integration-test/src/tests/fee/register_payee.rs b/tools/integration-test/src/tests/fee/register_payee.rs index 0e92dfe4b3..e9e075a064 100644 --- a/tools/integration-test/src/tests/fee/register_payee.rs +++ b/tools/integration-test/src/tests/fee/register_payee.rs @@ -12,6 +12,7 @@ //! `ack_fee` in separate wallets. use ibc_relayer_types::core::ics04_channel::version::Version; +use ibc_test_framework::framework::next::chain::{HasTwoChains, HasTwoChannels}; use ibc_test_framework::prelude::*; use ibc_test_framework::util::random::random_u128_range; @@ -29,13 +30,12 @@ impl TestOverrides for ForwardRelayerTest { } impl BinaryChannelTest for ForwardRelayerTest { - fn run( - &self, - _config: &TestConfig, - _relayer: RelayerDriver, - chains: ConnectedChains, - channel: ConnectedChannel, - ) -> Result<(), Error> { + fn run(&self, _relayer: RelayerDriver, context: &Context) -> Result<(), Error> + where + Context: HasTwoChains + HasTwoChannels, + { + let chains = context.chains(); + let channel = context.channel(); let chain_driver_a = chains.node_a.chain_driver(); let chain_driver_b = chains.node_b.chain_driver(); diff --git a/tools/integration-test/src/tests/fee/timeout_fee.rs b/tools/integration-test/src/tests/fee/timeout_fee.rs index 1d6131ed89..733bb22503 100644 --- a/tools/integration-test/src/tests/fee/timeout_fee.rs +++ b/tools/integration-test/src/tests/fee/timeout_fee.rs @@ -3,6 +3,7 @@ //! operation times out. use ibc_relayer_types::core::ics04_channel::version::Version; +use ibc_test_framework::framework::next::chain::{HasTwoChains, HasTwoChannels}; use ibc_test_framework::prelude::*; use ibc_test_framework::util::random::random_u128_range; use std::thread; @@ -25,13 +26,12 @@ impl TestOverrides for TimeoutFeeTest { } impl BinaryChannelTest for TimeoutFeeTest { - fn run( - &self, - _config: &TestConfig, - relayer: RelayerDriver, - chains: ConnectedChains, - channel: ConnectedChannel, - ) -> Result<(), Error> { + fn run(&self, relayer: RelayerDriver, context: &Context) -> Result<(), Error> + where + Context: HasTwoChains + HasTwoChannels, + { + let chains = context.chains(); + let channel = context.channel(); let chain_driver_a = chains.node_a.chain_driver(); let denom_a = chains.node_a.denom(); diff --git a/tools/integration-test/src/tests/fee_grant.rs b/tools/integration-test/src/tests/fee_grant.rs index 39a3984a73..a384616c5b 100644 --- a/tools/integration-test/src/tests/fee_grant.rs +++ b/tools/integration-test/src/tests/fee_grant.rs @@ -15,6 +15,7 @@ use std::thread; use ibc_relayer::config::ChainConfig; use ibc_relayer_types::bigint::U256; use ibc_test_framework::chain::ext::fee_grant::FeeGrantMethodsExt; +use ibc_test_framework::framework::next::chain::{CanSpawnRelayer, HasTwoChains, HasTwoChannels}; use ibc_test_framework::prelude::*; #[test] @@ -32,13 +33,13 @@ struct FeeGrantTest; impl TestOverrides for FeeGrantTest {} impl BinaryChannelTest for FeeGrantTest { - fn run( - &self, - _config: &TestConfig, - relayer: RelayerDriver, - chains: ConnectedChains, - channels: ConnectedChannel, - ) -> Result<(), Error> { + fn run(&self, relayer: RelayerDriver, context: &Context) -> Result<(), Error> + where + Context: HasTwoChains + HasTwoChannels + CanSpawnRelayer, + { + let chains = context.chains(); + let channels = context.channel(); + let denom_a = chains.node_a.denom(); let wallet_a = chains.node_a.wallets().user1().cloned(); let wallet_b = chains.node_b.wallets().user1().cloned(); @@ -73,7 +74,7 @@ impl BinaryChannelTest for FeeGrantTest { &denom_a, )?; - let gas_denom: MonoTagged = MonoTagged::new(Denom::Base("stake".to_owned())); + let gas_denom = MonoTagged::new(Denom::Base("stake".to_owned())); let balance_user1_before = chains .node_a @@ -109,7 +110,7 @@ impl BinaryChannelTest for FeeGrantTest { modified_driver.tx_config = modified_tx_config; - let md: MonoTagged = MonoTagged::new(&modified_driver); + let md = MonoTagged::new(&modified_driver); md.ibc_transfer_token( &channels.port_a.as_ref(), @@ -163,13 +164,13 @@ struct NoFeeGrantTest; impl TestOverrides for NoFeeGrantTest {} impl BinaryChannelTest for NoFeeGrantTest { - fn run( - &self, - _config: &TestConfig, - _relayer: RelayerDriver, - chains: ConnectedChains, - channels: ConnectedChannel, - ) -> Result<(), Error> { + fn run(&self, _relayer: RelayerDriver, context: &Context) -> Result<(), Error> + where + Context: HasTwoChains + HasTwoChannels + CanSpawnRelayer, + { + let chains = context.chains(); + let channels = context.channel(); + let denom_a = chains.node_a.denom(); let wallet_a = chains.node_a.wallets().user1().cloned(); let wallet_a2 = chains.node_a.wallets().user2().cloned(); @@ -205,7 +206,7 @@ impl BinaryChannelTest for NoFeeGrantTest { &denom_a, )?; - let gas_denom: MonoTagged = MonoTagged::new(Denom::Base("stake".to_owned())); + let gas_denom = MonoTagged::new(Denom::Base("stake".to_owned())); let balance_user1_before = chains .node_a diff --git a/tools/integration-test/src/tests/ics31.rs b/tools/integration-test/src/tests/ics31.rs index 93454b0820..41697fc60f 100644 --- a/tools/integration-test/src/tests/ics31.rs +++ b/tools/integration-test/src/tests/ics31.rs @@ -19,6 +19,8 @@ use ibc_test_framework::chain::{ }, ext::crosschainquery::CrossChainQueryMethodsExt, }; +use ibc_test_framework::framework::next::chain::HasTwoChains; +use ibc_test_framework::framework::next::chain::HasTwoChannels; use ibc_test_framework::prelude::*; use ibc_test_framework::util::random::random_u128_range; @@ -72,13 +74,12 @@ impl TestOverrides for ICS31Test { } impl BinaryChannelTest for ICS31Test { - fn run( - &self, - _config: &TestConfig, - _relayer: RelayerDriver, - chains: ConnectedChains, - channel: ConnectedChannel, - ) -> Result<(), Error> { + fn run(&self, _relayer: RelayerDriver, context: &Context) -> Result<(), Error> + where + Context: HasTwoChains + HasTwoChannels, + { + let chains = context.chains(); + let channel = context.channel(); let denom_a = chains.node_a.denom(); let a_to_b_amount = random_u128_range(1000, 5000); let wallet_a = chains.node_a.wallets().user1().cloned(); 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..e3ce172f77 100644 --- a/tools/integration-test/src/tests/interchain_security/ica_transfer.rs +++ b/tools/integration-test/src/tests/interchain_security/ica_transfer.rs @@ -16,6 +16,7 @@ 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::framework::next::chain::{CanSpawnRelayer, HasTwoChains, HasTwoChannels}; use ibc_test_framework::prelude::*; use ibc_test_framework::relayer::channel::assert_eventually_channel_established; use ibc_test_framework::util::interchain_security::{ @@ -65,13 +66,13 @@ impl TestOverrides for InterchainSecurityIcaTransferTest { } impl BinaryChannelTest for InterchainSecurityIcaTransferTest { - fn run( - &self, - _config: &TestConfig, - _relayer: RelayerDriver, - chains: ConnectedChains, - channel: ConnectedChannel, - ) -> Result<(), Error> { + fn run(&self, _relayer: RelayerDriver, context: &Context) -> Result<(), Error> + where + Context: HasTwoChains + HasTwoChannels + CanSpawnRelayer, + { + let chains = context.chains(); + let channel = context.channel(); + 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)?; @@ -90,7 +91,7 @@ impl BinaryChannelTest for InterchainSecurityIcaTransferTest { &channel.connection.connection_id_b.as_ref(), )?; - let stake_denom: MonoTagged = MonoTagged::new(Denom::base("stake")); + let stake_denom = MonoTagged::new(Denom::base("stake")); chains.node_a.chain_driver().assert_eventual_wallet_amount( &ica_address.as_ref(), diff --git a/tools/integration-test/src/tests/interchain_security/icq.rs b/tools/integration-test/src/tests/interchain_security/icq.rs index 8b305f92e2..046a589718 100644 --- a/tools/integration-test/src/tests/interchain_security/icq.rs +++ b/tools/integration-test/src/tests/interchain_security/icq.rs @@ -11,6 +11,7 @@ use ibc_relayer::config::{self, ModeConfig}; +use ibc_test_framework::framework::next::chain::{CanSpawnRelayer, HasTwoChains, HasTwoChannels}; use ibc_test_framework::prelude::*; use ibc_test_framework::util::interchain_security::{ update_genesis_for_consumer_chain, update_relayer_config_for_consumer_chain, @@ -83,13 +84,13 @@ impl TestOverrides for InterchainSecurityIcqTest { } impl BinaryChannelTest for InterchainSecurityIcqTest { - fn run( - &self, - _config: &TestConfig, - _relayer: RelayerDriver, - chains: ConnectedChains, - channel: ConnectedChannel, - ) -> Result<(), Error> { + fn run(&self, _relayer: RelayerDriver, context: &Context) -> Result<(), Error> + where + Context: HasTwoChains + HasTwoChannels + CanSpawnRelayer, + { + let chains = context.chains(); + let channel = context.channel(); + let denom_a = chains.node_a.denom(); let a_to_b_amount = random_u128_range(1000, 5000); let wallet_a = chains.node_a.wallets().user1().cloned(); diff --git a/tools/integration-test/src/tests/interchain_security/simple_transfer.rs b/tools/integration-test/src/tests/interchain_security/simple_transfer.rs index 05317917ed..9780b03894 100644 --- a/tools/integration-test/src/tests/interchain_security/simple_transfer.rs +++ b/tools/integration-test/src/tests/interchain_security/simple_transfer.rs @@ -2,6 +2,7 @@ //! These tests require the first chain to be a Provider chain and //! the second chain a Consumer chain. use ibc_test_framework::framework::binary::channel::run_binary_interchain_security_channel_test; +use ibc_test_framework::framework::next::chain::{CanSpawnRelayer, HasTwoChains, HasTwoChannels}; use ibc_test_framework::prelude::*; use ibc_test_framework::util::interchain_security::{ update_genesis_for_consumer_chain, update_relayer_config_for_consumer_chain, @@ -30,13 +31,13 @@ impl TestOverrides for InterchainSecurityTransferTest { } impl BinaryChannelTest for InterchainSecurityTransferTest { - fn run( - &self, - _config: &TestConfig, - _relayer: RelayerDriver, - chains: ConnectedChains, - channel: ConnectedChannel, - ) -> Result<(), Error> { + fn run(&self, _relayer: RelayerDriver, context: &Context) -> Result<(), Error> + where + Context: HasTwoChains + HasTwoChannels + CanSpawnRelayer, + { + let chains = context.chains(); + let channel = context.channel(); + let denom_a = chains.node_a.denom(); let wallet_a = chains.node_a.wallets().user1().cloned(); diff --git a/tools/integration-test/src/tests/manual/simulation.rs b/tools/integration-test/src/tests/manual/simulation.rs index 76442747e9..a1b4cd9aa8 100644 --- a/tools/integration-test/src/tests/manual/simulation.rs +++ b/tools/integration-test/src/tests/manual/simulation.rs @@ -15,6 +15,7 @@ use core::time::Duration; use ibc_relayer::config::{types::MaxMsgNum, Config}; use ibc_relayer::transfer::{build_and_send_transfer_messages, TransferOptions}; use ibc_relayer_types::events::IbcEvent; +use ibc_test_framework::framework::next::chain::{HasTwoChains, HasTwoChannels}; use ibc_test_framework::prelude::*; #[test] @@ -35,17 +36,16 @@ impl TestOverrides for SimulationTest { } impl BinaryChannelTest for SimulationTest { - fn run( - &self, - _config: &TestConfig, - _relayer: RelayerDriver, - chains: ConnectedChains, - channel: ConnectedChannel, - ) -> Result<(), Error> { + fn run(&self, _relayer: RelayerDriver, context: &Context) -> Result<(), Error> + where + Context: HasTwoChains + HasTwoChannels, + { + let chains = context.chains(); + let channel = context.channel(); tx_raw_ft_transfer( chains.handle_a(), chains.handle_b(), - &channel, + channel, &chains.node_b.wallets().user1().address(), &chains.node_a.denom(), 9999, diff --git a/tools/integration-test/src/tests/memo.rs b/tools/integration-test/src/tests/memo.rs index 9ad439cf95..49ecb045e0 100644 --- a/tools/integration-test/src/tests/memo.rs +++ b/tools/integration-test/src/tests/memo.rs @@ -5,6 +5,7 @@ //! `tools/test-framework/src/docs/walkthroughs/memo.rs`. use ibc_relayer::config::{types::Memo, Config}; +use ibc_test_framework::framework::next::chain::{CanSpawnRelayer, HasTwoChains, HasTwoChannels}; use serde_json as json; use ibc_test_framework::ibc::denom::derive_ibc_denom; @@ -28,16 +29,20 @@ impl TestOverrides for MemoTest { chain.memo_prefix = self.memo.clone(); } } + + fn should_spawn_supervisor(&self) -> bool { + false + } } impl BinaryChannelTest for MemoTest { - fn run( - &self, - _config: &TestConfig, - _relayer: RelayerDriver, - chains: ConnectedChains, - channel: ConnectedChannel, - ) -> Result<(), Error> { + fn run(&self, _relayer: RelayerDriver, context: &Context) -> Result<(), Error> + where + Context: HasTwoChains + HasTwoChannels + CanSpawnRelayer, + { + let _handle = context.spawn_relayer()?; + let chains = context.chains(); + let channel = context.channel(); info!( "testing IBC transfer with memo configured: \"{}\"", self.memo diff --git a/tools/integration-test/src/tests/mod.rs b/tools/integration-test/src/tests/mod.rs index 57526ac08b..ed9b41ddaa 100644 --- a/tools/integration-test/src/tests/mod.rs +++ b/tools/integration-test/src/tests/mod.rs @@ -5,19 +5,23 @@ will pick up the definition by default. */ +#[cfg(not(feature = "next"))] pub mod clear_packet; pub mod client_expiration; pub mod client_filter; pub mod client_refresh; pub mod client_settings; pub mod client_upgrade; +#[cfg(not(feature = "next"))] pub mod connection_delay; pub mod consensus_states; pub mod denom_trace; pub mod error_events; pub mod execute_schedule; pub mod handshake_on_start; +#[cfg(not(feature = "next"))] pub mod memo; +pub mod next; pub mod python; pub mod query_packet; pub mod supervisor; diff --git a/tools/integration-test/src/tests/next/connection.rs b/tools/integration-test/src/tests/next/connection.rs new file mode 100644 index 0000000000..9aff0d4abd --- /dev/null +++ b/tools/integration-test/src/tests/next/connection.rs @@ -0,0 +1,93 @@ +use ibc_relayer::chain::client::ClientSettings; +use ibc_relayer::chain::cosmos::client::Settings; +use ibc_relayer::channel::version::Version; +use ibc_relayer::config::PacketFilter; +use ibc_relayer_components::build::impls::bootstrap::birelay::CanBootstrapBiRelay; +use ibc_relayer_components::relay::impls::channel::bootstrap::CanBootstrapChannel; +use ibc_relayer_components::relay::impls::connection::bootstrap::CanBootstrapConnection; +use ibc_relayer_components::relay::traits::two_way::HasTwoWayRelay; +use ibc_relayer_cosmos::types::channel::CosmosInitChannelOptions; +use ibc_relayer_cosmos::types::error::Error as CosmosError; +use ibc_test_framework::prelude::*; + +use crate::tests::next::context::new_cosmos_builder; + +#[test] +fn test_connection_and_channel_handshake_next() -> Result<(), Error> { + run_binary_chain_test(&ConnectionAndChannelHandshakeTest) +} + +pub struct ConnectionAndChannelHandshakeTest; + +impl TestOverrides for ConnectionAndChannelHandshakeTest { + fn should_spawn_supervisor(&self) -> bool { + false + } +} + +impl BinaryChainTest for ConnectionAndChannelHandshakeTest { + fn run( + &self, + _config: &TestConfig, + relayer: RelayerDriver, + chains: ConnectedChains, + ) -> Result<(), Error> { + let pf: PacketFilter = PacketFilter::default(); + + let runtime = chains.node_a.value().chain_driver.runtime.as_ref(); + + let builder = new_cosmos_builder(&relayer.config, &chains, pf)?; + + let chain_id_a = chains.chain_id_a().cloned_value(); + let chain_id_b = chains.chain_id_b().cloned_value(); + + // TODO: figure ways to build client settings easily without too much dependencies + let client_settings = ClientSettings::Tendermint(Settings { + max_clock_drift: Duration::from_secs(40), + trust_threshold: Default::default(), + trusting_period: None, + }); + + runtime + .block_on(async move { + let birelay = builder + .bootstrap_birelay(&chain_id_a, &chain_id_b, &client_settings, &client_settings) + .await?; + + let (connection_id_a, connection_id_b) = birelay + .relay_a_to_b() + .bootstrap_connection(&Default::default()) + .await?; + + info!( + "bootstrapped new IBC connections with ID {} and {}", + connection_id_a, connection_id_b + ); + + let channel_init_options = CosmosInitChannelOptions { + ordering: Ordering::Unordered, + connection_hops: vec![connection_id_a], + channel_version: Version::ics20(), + }; + + let (channel_id_a, channel_id_b) = birelay + .relay_a_to_b() + .bootstrap_channel( + &PortId::transfer(), + &PortId::transfer(), + &channel_init_options, + ) + .await?; + + info!( + "bootstrapped new IBC channel with ID {} and {}", + channel_id_a, channel_id_b + ); + + >::Ok(()) + }) + .unwrap(); + + Ok(()) + } +} diff --git a/tools/integration-test/src/tests/next/context.rs b/tools/integration-test/src/tests/next/context.rs new file mode 100644 index 0000000000..f07a4e4532 --- /dev/null +++ b/tools/integration-test/src/tests/next/context.rs @@ -0,0 +1,68 @@ +use std::collections::HashMap; + +use ibc_relayer::chain::handle::ChainHandle; +use ibc_relayer::config::filter::PacketFilter; +use ibc_relayer::config::Config; +use ibc_relayer_all_in_one::all_for_one::builder::CanBuildAfoBiRelay; +use ibc_relayer_all_in_one::one_for_all::types::builder::OfaBuilderWrapper; +use ibc_relayer_cosmos::all_for_one::birelay::AfoCosmosBiRelay; +use ibc_relayer_cosmos::contexts::builder::CosmosBuilder; +use ibc_test_framework::error::{handle_generic_error, Error}; +use ibc_test_framework::prelude::TaggedFullNodeExt; +use ibc_test_framework::types::binary::chains::ConnectedChains; + +pub fn new_cosmos_builder( + config: &Config, + chains: &ConnectedChains, + packet_filter: PacketFilter, +) -> Result, Error> +where + ChainA: ChainHandle, + ChainB: ChainHandle, +{ + let runtime = chains.node_a.value().chain_driver.runtime.clone(); + + let key_a = chains.node_a.wallets().value().relayer.key.clone(); + + let key_b = chains.node_b.wallets().value().relayer.key.clone(); + + let key_map = HashMap::from([ + (chains.chain_id_a().cloned_value(), key_a), + (chains.chain_id_b().cloned_value(), key_b), + ]); + + let builder = CosmosBuilder::new_wrapped( + config.clone(), + runtime, + Default::default(), + packet_filter, + Default::default(), + key_map, + ); + + Ok(builder) +} + +pub fn build_cosmos_relay_context( + config: &Config, + chains: &ConnectedChains, + packet_filter: PacketFilter, +) -> Result +where + ChainA: ChainHandle, + ChainB: ChainHandle, +{ + let runtime = chains.node_a.value().chain_driver.runtime.clone(); + let builder = new_cosmos_builder(config, chains, packet_filter)?; + + let birelay = runtime + .block_on(builder.build_afo_birelay( + chains.chain_id_a().value(), + chains.chain_id_b().value(), + chains.client_id_a().value(), + chains.client_id_b().value(), + )) + .map_err(handle_generic_error)?; + + Ok(birelay) +} diff --git a/tools/integration-test/src/tests/next/filter.rs b/tools/integration-test/src/tests/next/filter.rs new file mode 100644 index 0000000000..1251c0f6ee --- /dev/null +++ b/tools/integration-test/src/tests/next/filter.rs @@ -0,0 +1,112 @@ +use ibc_relayer::config::filter::PacketFilter; +use ibc_relayer_components::relay::traits::components::packet_relayer::CanRelayPacket; +use ibc_relayer_components::relay::traits::two_way::HasTwoWayRelay; +use ibc_test_framework::framework::next::chain::{HasTwoChains, HasTwoChannels}; +use ibc_test_framework::ibc::denom::derive_ibc_denom; +use ibc_test_framework::prelude::*; +use ibc_test_framework::util::random::random_u64_range; + +use crate::tests::next::context::build_cosmos_relay_context; + +#[test] +fn test_ibc_filter_next() -> Result<(), Error> { + run_binary_channel_test(&ChannelFilterTest) +} + +pub struct ChannelFilterTest; + +impl TestOverrides for ChannelFilterTest { + fn should_spawn_supervisor(&self) -> bool { + false + } +} + +impl BinaryChannelTest for ChannelFilterTest { + fn run(&self, relayer: RelayerDriver, context: &Context) -> Result<(), Error> + where + Context: HasTwoChains + HasTwoChannels, + { + let chains = context.chains(); + let channel = context.channel(); + let toml_content = r#" + policy = 'deny' + list = [ + ['transfer', 'channel-*'], + ] + "#; + let pf: PacketFilter = toml::from_str(toml_content).expect("could not parse filter policy"); + + let relay_context = build_cosmos_relay_context(&relayer.config, chains, pf)?; + + let runtime = chains.node_a.value().chain_driver.runtime.as_ref(); + + let denom_a = chains.node_a.denom(); + + let wallet_a = chains.node_a.wallets().user1().cloned(); + let wallet_b = chains.node_b.wallets().user1().cloned(); + + let balance_a = chains + .node_a + .chain_driver() + .query_balance(&wallet_a.address(), &denom_a)?; + + let a_to_b_amount = random_u64_range(1000, 5000); + + info!( + "Sending IBC transfer from chain {} to chain {} with amount of {} {}", + chains.chain_id_a(), + chains.chain_id_b(), + a_to_b_amount, + denom_a + ); + + let packet = chains.node_a.chain_driver().ibc_transfer_token( + &channel.port_a.as_ref(), + &channel.channel_id_a.as_ref(), + &wallet_a.as_ref(), + &wallet_b.address(), + &denom_a.with_amount(a_to_b_amount).as_ref(), + )?; + + info!("running relayer"); + + runtime.block_on(async { + relay_context + .relay_a_to_b() + .relay_packet(&packet) + .await + .unwrap() + }); + + info!("finished running relayer"); + + let denom_b = derive_ibc_denom( + &channel.port_b.as_ref(), + &channel.channel_id_b.as_ref(), + &denom_a, + )?; + + info!( + "User on chain B should not receive the transfer of {} {}", + a_to_b_amount, &denom_b + ); + + chains.node_a.chain_driver().assert_eventual_wallet_amount( + &wallet_a.address(), + &(balance_a - a_to_b_amount).as_ref(), + )?; + + chains.node_b.chain_driver().assert_eventual_wallet_amount( + &wallet_b.address(), + &denom_b.with_amount(0u64).as_ref(), + )?; + + info!( + "successfully filtered IBC transfer from chain {} to chain {}", + chains.chain_id_a(), + chains.chain_id_b(), + ); + + Ok(()) + } +} diff --git a/tools/integration-test/src/tests/next/mod.rs b/tools/integration-test/src/tests/next/mod.rs new file mode 100644 index 0000000000..5c1df86ad6 --- /dev/null +++ b/tools/integration-test/src/tests/next/mod.rs @@ -0,0 +1,6 @@ +pub mod connection; +pub mod context; +pub mod filter; +pub mod packet_clear; +pub mod timeout_transfer; +pub mod transfer; diff --git a/tools/integration-test/src/tests/next/packet_clear.rs b/tools/integration-test/src/tests/next/packet_clear.rs new file mode 100644 index 0000000000..832748a65a --- /dev/null +++ b/tools/integration-test/src/tests/next/packet_clear.rs @@ -0,0 +1,221 @@ +use ibc_relayer::config::PacketFilter; +use ibc_relayer_components::chain::traits::queries::packet_commitments::CanQueryPacketCommitments; +use ibc_relayer_components::chain::traits::queries::send_packet::CanQuerySendPacketsFromSequences; +use ibc_relayer_components::chain::traits::queries::unreceived_packets::CanQueryUnreceivedPacketSequences; +use ibc_relayer_components::relay::traits::chains::HasRelayChains; +use ibc_relayer_components::relay::traits::components::packet_clearer::CanClearPackets; +use ibc_relayer_components::relay::traits::two_way::HasTwoWayRelay; +use ibc_relayer_types::core::ics04_channel::packet::Sequence; +use ibc_relayer_types::Height; +use ibc_test_framework::framework::next::chain::{HasTwoChains, HasTwoChannels}; +use ibc_test_framework::prelude::*; +use ibc_test_framework::util::random::random_u64_range; + +use crate::tests::next::context::build_cosmos_relay_context; + +#[test] +fn test_ibc_clear_packet_next() -> Result<(), Error> { + run_binary_channel_test(&IbcClearPacketTest) +} + +pub struct IbcClearPacketTest; + +impl TestOverrides for IbcClearPacketTest { + fn should_spawn_supervisor(&self) -> bool { + false + } +} + +impl BinaryChannelTest for IbcClearPacketTest { + fn run(&self, relayer: RelayerDriver, context: &Context) -> Result<(), Error> + where + Context: HasTwoChains + HasTwoChannels, + { + let chains = context.chains(); + let cloned_channel = context.channel().clone(); + let channel = context.channel().clone(); + let pf: PacketFilter = PacketFilter::default(); + + let relay_context = build_cosmos_relay_context(&relayer.config, chains, pf)?; + + let relay_a_to_b = relay_context.relay_a_to_b(); + let relay_b_to_a = relay_context.relay_b_to_a(); + let chain_a = relay_a_to_b.src_chain(); + let chain_b = relay_a_to_b.dst_chain(); + + let runtime = chains.node_a.value().chain_driver.runtime.as_ref(); + + let denom_a = chains.node_a.denom(); + + let wallet_a = chains.node_a.wallets().user1().cloned(); + let wallet_b = chains.node_b.wallets().user1().cloned(); + + let balance_a = chains + .node_a + .chain_driver() + .query_balance(&wallet_a.address(), &denom_a)?; + + let a_to_b_amount = random_u64_range(1000, 5000); + + info!( + "Sending IBC transfer from chain {} to chain {} with amount of {} {}", + chains.chain_id_a(), + chains.chain_id_b(), + a_to_b_amount, + denom_a + ); + + chains.node_a.chain_driver().ibc_transfer_token( + &channel.port_a.as_ref(), + &channel.channel_id_a.as_ref(), + &wallet_a.as_ref(), + &wallet_b.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, + )?; + + runtime.block_on(async { + info!("Assert query packet commitments works as expected"); + + let (src_commitments, src_height): (Vec, Height) = chain_a + .query_packet_commitments(channel.channel_id_a.value(), channel.port_a.value()) + .await + .unwrap(); + + assert_eq!(src_commitments, vec!(Sequence::from(1))); + + let (dst_commitments, dst_height): (Vec, Height) = chain_b + .query_packet_commitments(channel.channel_id_b.value(), channel.port_b.value()) + .await + .unwrap(); + + assert_eq!(dst_commitments, vec!()); + + info!("Assert query unreceived packet sequences works as expected"); + + let unreceived_packet_sequences: Vec = chain_a + .query_unreceived_packet_sequences( + channel.channel_id_a.value(), + channel.port_a.value(), + &src_commitments, + ) + .await + .unwrap(); + + assert_eq!(unreceived_packet_sequences, vec!(Sequence::from(1))); + + let unreceived_packet_sequences: Vec = chain_b + .query_unreceived_packet_sequences( + channel.channel_id_b.value(), + channel.port_b.value(), + &src_commitments, + ) + .await + .unwrap(); + + assert_eq!(unreceived_packet_sequences, vec!(Sequence::from(1))); + + info!("Assert query unreceived packets works as expected"); + + let send_packets = chain_a + .query_send_packets_from_sequences( + channel.channel_id_a.value(), + channel.port_a.value(), + channel.channel_id_b.value(), + channel.port_b.value(), + &unreceived_packet_sequences, + &src_height, + ) + .await + .unwrap(); + + assert_eq!(send_packets.len(), 1); + + let send_packets = chain_b + .query_send_packets_from_sequences( + channel.channel_id_b.value(), + channel.port_b.value(), + channel.channel_id_a.value(), + channel.port_a.value(), + &unreceived_packet_sequences, + &dst_height, + ) + .await; + + assert!( + send_packets.is_err(), + "There should be no send packets from Chain B" + ); + + let _ = relay_b_to_a + .clear_packets( + channel.channel_id_a.value(), + channel.port_a.value(), + channel.channel_id_b.value(), + channel.port_b.value(), + ) + .await; + + info!("Clear packets from B to A should not clear the pending packet from A to B"); + + let amount = chains + .node_a + .chain_driver() + .query_balance(&wallet_a.address(), &balance_a.denom()) + .unwrap(); + + assert_eq!( + amount.value().amount, + (balance_a.clone() - a_to_b_amount).amount() + ); + + let amount = chains + .node_b + .chain_driver() + .query_balance(&wallet_b.address(), &denom_b.as_ref()) + .unwrap(); + + assert_eq!(amount.value().amount, denom_b.with_amount(0u64).amount()); + + let _ = relay_a_to_b + .clear_packets( + cloned_channel.channel_id_a.value(), + cloned_channel.port_a.value(), + cloned_channel.channel_id_b.value(), + cloned_channel.port_b.value(), + ) + .await; + + info!("Clear packet from A to B should clear the pending packet"); + + let amount = chains + .node_a + .chain_driver() + .query_balance(&wallet_a.address(), &balance_a.denom()) + .unwrap(); + + assert_eq!( + amount.value().amount, + (balance_a.clone() - a_to_b_amount).amount() + ); + + let amount = chains + .node_b + .chain_driver() + .query_balance(&wallet_b.address(), &denom_b.as_ref()) + .unwrap(); + + assert_eq!( + amount.value().amount, + denom_b.with_amount(a_to_b_amount).amount() + ); + }); + + Ok(()) + } +} diff --git a/tools/integration-test/src/tests/next/timeout_transfer.rs b/tools/integration-test/src/tests/next/timeout_transfer.rs new file mode 100644 index 0000000000..1304fe67ac --- /dev/null +++ b/tools/integration-test/src/tests/next/timeout_transfer.rs @@ -0,0 +1,102 @@ +//! Tests the capability of a full relayer instance to relay a timeout packet. +//! +//! This test ensures that a source chain that initiates an IBC transfer is +//! refunded the tokens that it sent in response to receiving a timeout packet +//! relayed by a full relayer. + +use ibc_relayer::config::PacketFilter; +use ibc_relayer_components::relay::traits::components::packet_relayer::CanRelayPacket; +use ibc_relayer_components::relay::traits::two_way::HasTwoWayRelay; +use ibc_test_framework::framework::next::chain::{HasTwoChains, HasTwoChannels}; +use ibc_test_framework::prelude::*; +use ibc_test_framework::util::random::random_u64_range; + +use crate::tests::next::context::build_cosmos_relay_context; + +#[test] +fn test_ibc_transfer_timeout_next() -> Result<(), Error> { + run_binary_channel_test(&IbcTransferTest) +} + +pub struct IbcTransferTest; + +impl TestOverrides for IbcTransferTest { + fn should_spawn_supervisor(&self) -> bool { + false + } +} + +impl BinaryChannelTest for IbcTransferTest { + fn run(&self, relayer: RelayerDriver, context: &Context) -> Result<(), Error> + where + Context: HasTwoChains + HasTwoChannels, + { + let chains = context.chains(); + let channel = context.channel(); + let pf: PacketFilter = PacketFilter::default(); + + let relay_context = build_cosmos_relay_context(&relayer.config, chains, pf)?; + + let runtime = chains.node_a.value().chain_driver.runtime.as_ref(); + + let denom_a = chains.node_a.denom(); + + let wallet_a = chains.node_a.wallets().user1().cloned(); + let wallet_b = chains.node_b.wallets().user1().cloned(); + + let balance_a = chains + .node_a + .chain_driver() + .query_balance(&wallet_a.address(), &denom_a)?; + + let a_to_b_amount = random_u64_range(1000, 5000); + + info!( + "Sending IBC timeout from chain {} to chain {} with amount of {} {}", + chains.chain_id_a(), + chains.chain_id_b(), + a_to_b_amount, + denom_a + ); + + let packet = chains + .node_a + .chain_driver() + .ibc_transfer_token_with_memo_and_timeout( + &channel.port_a.as_ref(), + &channel.channel_id_a.as_ref(), + &wallet_a.as_ref(), + &wallet_b.address(), + &denom_a.with_amount(a_to_b_amount).as_ref(), + None, + Some(Duration::from_secs(1)), + )?; + + info!("running relayer"); + + sleep(Duration::from_secs(5)); + + runtime.block_on(async { + relay_context + .relay_a_to_b() + .relay_packet(&packet) + .await + .unwrap() + }); + + info!("finished running relayer"); + + chains + .node_a + .chain_driver() + .assert_eventual_wallet_amount(&wallet_a.address(), &balance_a.as_ref())?; + + info!( + "successfully refunded IBC transfer back to chain {} from chain {}", + chains.chain_id_a(), + chains.chain_id_b(), + ); + + Ok(()) + } +} diff --git a/tools/integration-test/src/tests/next/transfer.rs b/tools/integration-test/src/tests/next/transfer.rs new file mode 100644 index 0000000000..a613f031db --- /dev/null +++ b/tools/integration-test/src/tests/next/transfer.rs @@ -0,0 +1,144 @@ +use ibc_relayer::config::PacketFilter; +use ibc_relayer_components::relay::traits::components::auto_relayer::CanAutoRelay; +use ibc_test_framework::framework::next::chain::{HasTwoChains, HasTwoChannels}; +use ibc_test_framework::ibc::denom::derive_ibc_denom; +use ibc_test_framework::prelude::*; +use ibc_test_framework::util::random::random_u64_range; +use std::thread::sleep; + +use crate::tests::next::context::build_cosmos_relay_context; + +#[test] +fn test_ibc_transfer_next() -> Result<(), Error> { + run_binary_channel_test(&IbcTransferTest) +} + +pub struct IbcTransferTest; + +impl TestOverrides for IbcTransferTest { + fn should_spawn_supervisor(&self) -> bool { + false + } +} + +impl BinaryChannelTest for IbcTransferTest { + fn run(&self, relayer: RelayerDriver, context: &Context) -> Result<(), Error> + where + Context: HasTwoChains + HasTwoChannels, + { + let chains = context.chains(); + let channel = context.channel(); + let pf: PacketFilter = PacketFilter::default(); + + let relay_context = build_cosmos_relay_context(&relayer.config, chains, pf)?; + + let runtime = chains.node_a.value().chain_driver.runtime.as_ref(); + + runtime.spawn(async move { + let _ = relay_context.auto_relay().await; + }); + + let denom_a = chains.node_a.denom(); + + let wallet_a = chains.node_a.wallets().user1().cloned(); + let wallet_b = chains.node_b.wallets().user1().cloned(); + let wallet_c = chains.node_a.wallets().user2().cloned(); + + let balance_a = chains + .node_a + .chain_driver() + .query_balance(&wallet_a.address(), &denom_a)?; + + let a_to_b_amount = random_u64_range(1000, 5000); + + info!( + "Sending IBC transfer from chain {} to chain {} with amount of {} {}", + chains.chain_id_a(), + chains.chain_id_b(), + a_to_b_amount, + denom_a + ); + + chains.node_a.chain_driver().ibc_transfer_token( + &channel.port_a.as_ref(), + &channel.channel_id_a.as_ref(), + &wallet_a.as_ref(), + &wallet_b.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, + )?; + + info!( + "Waiting for user on chain B to receive IBC transferred amount of {} {}", + a_to_b_amount, denom_b + ); + + chains.node_a.chain_driver().assert_eventual_wallet_amount( + &wallet_a.address(), + &(balance_a - a_to_b_amount).as_ref(), + )?; + + chains.node_b.chain_driver().assert_eventual_wallet_amount( + &wallet_b.address(), + &denom_b.with_amount(a_to_b_amount).as_ref(), + )?; + + info!( + "successfully performed IBC transfer from chain {} to chain {}", + chains.chain_id_a(), + chains.chain_id_b(), + ); + + let balance_c = chains + .node_a + .chain_driver() + .query_balance(&wallet_c.address(), &denom_a)?; + + let b_to_a_amount = random_u64_range(500, a_to_b_amount); + + info!( + "Sending IBC transfer from chain {} to chain {} with amount of {}", + chains.chain_id_b(), + chains.chain_id_a(), + b_to_a_amount, + ); + + chains.node_b.chain_driver().ibc_transfer_token( + &channel.port_b.as_ref(), + &channel.channel_id_b.as_ref(), + &wallet_b.as_ref(), + &wallet_c.address(), + &denom_b.with_amount(b_to_a_amount).as_ref(), + )?; + + info!( + "Waiting for user on chain A to receive IBC transferred amount of {} {}", + b_to_a_amount, denom_a + ); + + chains.node_b.chain_driver().assert_eventual_wallet_amount( + &wallet_b.address(), + &denom_b.with_amount(a_to_b_amount - b_to_a_amount).as_ref(), + )?; + + chains.node_a.chain_driver().assert_eventual_wallet_amount( + &wallet_c.address(), + &(balance_c + b_to_a_amount).as_ref(), + )?; + + info!( + "successfully performed reverse IBC transfer from chain {} back to chain {}", + chains.chain_id_b(), + chains.chain_id_a(), + ); + + sleep(Duration::from_secs(2)); + + Ok(()) + } +} diff --git a/tools/integration-test/src/tests/ordered_channel.rs b/tools/integration-test/src/tests/ordered_channel.rs index 25177b4ee8..8a44c2083e 100644 --- a/tools/integration-test/src/tests/ordered_channel.rs +++ b/tools/integration-test/src/tests/ordered_channel.rs @@ -13,6 +13,7 @@ //! A more thorough walkthrough of this test can be found at //! `tools/test-framework/src/docs/walkthroughs/ordered_channel.rs`. +use ibc_test_framework::framework::next::chain::{HasTwoChains, HasTwoChannels}; use ibc_test_framework::ibc::denom::derive_ibc_denom; use ibc_test_framework::prelude::*; use ibc_test_framework::util::random::random_u128_range; @@ -42,13 +43,12 @@ impl TestOverrides for OrderedChannelTest { } impl BinaryChannelTest for OrderedChannelTest { - fn run( - &self, - _config: &TestConfig, - relayer: RelayerDriver, - chains: ConnectedChains, - channel: ConnectedChannel, - ) -> Result<(), Error> { + fn run(&self, relayer: RelayerDriver, context: &Context) -> Result<(), Error> + where + Context: HasTwoChains + HasTwoChannels, + { + let chains = context.chains(); + let channel = context.channel(); let denom_a = chains.node_a.denom(); let wallet_a = chains.node_a.wallets().user1().cloned(); diff --git a/tools/integration-test/src/tests/ordered_channel_clear.rs b/tools/integration-test/src/tests/ordered_channel_clear.rs index 5590812164..0779f8719b 100644 --- a/tools/integration-test/src/tests/ordered_channel_clear.rs +++ b/tools/integration-test/src/tests/ordered_channel_clear.rs @@ -2,6 +2,7 @@ use ibc_relayer::config::types::MaxMsgNum; use ibc_relayer::link::{Link, LinkParameters}; use ibc_relayer::transfer::{build_and_send_transfer_messages, TransferOptions}; use ibc_relayer_types::events::IbcEvent; +use ibc_test_framework::framework::next::chain::{HasTwoChains, HasTwoChannels}; use ibc_test_framework::ibc::denom::derive_ibc_denom; use ibc_test_framework::prelude::*; use ibc_test_framework::util::random::random_u64_range; @@ -62,13 +63,12 @@ impl TestOverrides for OrderedChannelClearTest { } impl BinaryChannelTest for OrderedChannelClearTest { - fn run( - &self, - _config: &TestConfig, - _relayer: RelayerDriver, - chains: ConnectedChains, - channel: ConnectedChannel, - ) -> Result<(), Error> { + fn run(&self, _relayer: RelayerDriver, context: &Context) -> Result<(), Error> + where + Context: HasTwoChains + HasTwoChannels, + { + let chains = context.chains(); + let channel = context.channel(); let denom_a = chains.node_a.denom(); let wallet_a = chains.node_a.wallets().user1().cloned(); @@ -197,13 +197,12 @@ impl TestOverrides for OrderedChannelClearEqualCLITest { // Sends to B all packets found on chain A in the block at `clear_height`, prepending an update_client message // The test expects to get 4 IBC events from chain B, one for the update_client and 3 for the write_acknowledgment events impl BinaryChannelTest for OrderedChannelClearEqualCLITest { - fn run( - &self, - _config: &TestConfig, - _relayer: RelayerDriver, - chains: ConnectedChains, - channel: ConnectedChannel, - ) -> Result<(), Error> { + fn run(&self, _relayer: RelayerDriver, context: &Context) -> Result<(), Error> + where + Context: HasTwoChains + HasTwoChannels, + { + let chains = context.chains().clone(); + let channel = context.channel().clone(); let num_msgs = 5_usize; info!( diff --git a/tools/integration-test/src/tests/query_packet.rs b/tools/integration-test/src/tests/query_packet.rs index 6c97c871d0..54c01211c9 100644 --- a/tools/integration-test/src/tests/query_packet.rs +++ b/tools/integration-test/src/tests/query_packet.rs @@ -1,6 +1,7 @@ use ibc_relayer::chain::counterparty::{channel_on_destination, pending_packet_summary}; use ibc_relayer::link::{Link, LinkParameters}; +use ibc_test_framework::framework::next::chain::{HasTwoChains, HasTwoChannels}; use ibc_test_framework::prelude::*; use ibc_test_framework::relayer::channel::query_identified_channel_end; use ibc_test_framework::relayer::connection::query_identified_connection_end; @@ -27,13 +28,12 @@ impl TestOverrides for QueryPacketPendingTest { } impl BinaryChannelTest for QueryPacketPendingTest { - fn run( - &self, - _config: &TestConfig, - _relayer: RelayerDriver, - chains: ConnectedChains, - channel: ConnectedChannel, - ) -> Result<(), Error> { + fn run(&self, _relayer: RelayerDriver, context: &Context) -> Result<(), Error> + where + Context: HasTwoChains + HasTwoChannels, + { + let chains = context.chains(); + let channel = context.channel(); let denom_a = chains.node_a.denom(); let wallet_a = chains.node_a.wallets().user1().cloned(); diff --git a/tools/integration-test/src/tests/supervisor.rs b/tools/integration-test/src/tests/supervisor.rs index c934d7a659..c9db62a2ac 100644 --- a/tools/integration-test/src/tests/supervisor.rs +++ b/tools/integration-test/src/tests/supervisor.rs @@ -1,6 +1,7 @@ use ibc_relayer::config::{self, Config, ModeConfig}; use ibc_test_framework::ibc::denom::derive_ibc_denom; +use ibc_test_framework::framework::next::chain::{CanSpawnRelayer, HasTwoChains, HasTwoChannels}; use ibc_test_framework::prelude::*; use ibc_test_framework::relayer::channel::{assert_eventually_channel_established, init_channel}; use ibc_test_framework::relayer::connection::{ @@ -186,13 +187,12 @@ impl TestOverrides for SupervisorScanTest { } impl BinaryChannelTest for SupervisorScanTest { - fn run( - &self, - _config: &TestConfig, - _relayer: RelayerDriver, - chains: ConnectedChains, - channels: ConnectedChannel, - ) -> Result<(), Error> { + fn run(&self, _relayer: RelayerDriver, context: &Context) -> Result<(), Error> + where + Context: HasTwoChains + HasTwoChannels + CanSpawnRelayer, + { + let chains = context.chains(); + let channels = context.channel(); let denom_a = chains.node_a.denom(); let denom_b = derive_ibc_denom( diff --git a/tools/integration-test/src/tests/tendermint/sequential.rs b/tools/integration-test/src/tests/tendermint/sequential.rs index b74d2ee366..a5f41c690c 100644 --- a/tools/integration-test/src/tests/tendermint/sequential.rs +++ b/tools/integration-test/src/tests/tendermint/sequential.rs @@ -3,6 +3,7 @@ use std::time::Instant; use ibc_relayer::chain::tracking::TrackedMsgs; use ibc_relayer::config::types::max_msg_num::MaxMsgNum; use ibc_test_framework::chain::config; +use ibc_test_framework::framework::next::chain::{HasTwoChains, HasTwoChannels}; use ibc_test_framework::prelude::*; use ibc_test_framework::relayer::transfer::build_transfer_message; @@ -48,13 +49,12 @@ impl TestOverrides for SequentialCommitTest { } impl BinaryChannelTest for SequentialCommitTest { - fn run( - &self, - _config: &TestConfig, - _relayer: RelayerDriver, - chains: ConnectedChains, - channel: ConnectedChannel, - ) -> Result<(), Error> { + fn run(&self, _relayer: RelayerDriver, context: &Context) -> Result<(), Error> + where + Context: HasTwoChains + HasTwoChannels, + { + let chains = context.chains(); + let channel = context.channel(); let wallet_a = chains.node_a.wallets().relayer().cloned(); let wallet_b = chains.node_b.wallets().relayer().cloned(); diff --git a/tools/integration-test/src/tests/transfer.rs b/tools/integration-test/src/tests/transfer.rs index d51e3be616..ad8219abdc 100644 --- a/tools/integration-test/src/tests/transfer.rs +++ b/tools/integration-test/src/tests/transfer.rs @@ -1,3 +1,7 @@ +use ibc_test_framework::framework::next::chain::{ + CanShutdown, CanSpawnRelayer, CanWaitForAck, HasContextId, HasTwoChains, HasTwoChannels, + HasTwoNodes, +}; use ibc_test_framework::prelude::*; use ibc_test_framework::util::random::random_u128_range; @@ -36,26 +40,38 @@ fn test_self_connected_nary_ibc_transfer() -> Result<(), Error> { ))) } -pub struct IbcTransferTest; +impl TestOverrides for IbcTransferTest { + fn should_spawn_supervisor(&self) -> bool { + false + } +} -impl TestOverrides for IbcTransferTest {} +pub struct IbcTransferTest; impl BinaryChannelTest for IbcTransferTest { - fn run( - &self, - _config: &TestConfig, - _relayer: RelayerDriver, - chains: ConnectedChains, - channel: ConnectedChannel, - ) -> Result<(), Error> { - let denom_a = chains.node_a.denom(); - - let wallet_a = chains.node_a.wallets().user1().cloned(); - let wallet_b = chains.node_b.wallets().user1().cloned(); - let wallet_c = chains.node_a.wallets().user2().cloned(); - - let balance_a = chains - .node_a + fn run(&self, _relayer: RelayerDriver, context: &Context) -> Result<(), Error> + where + Context: HasTwoChains + + HasTwoChannels + + HasTwoNodes + + CanSpawnRelayer + + HasContextId + + CanWaitForAck + + CanShutdown, + { + info!("Will run test with {}", context.context_id()); + let handle = context.spawn_relayer()?; + + let node_a = context.node_a(); + let node_b = context.node_b(); + + let denom_a = node_a.denom(); + + let wallet_a = node_a.wallets().user1().cloned(); + let wallet_b = node_b.wallets().user1().cloned(); + let wallet_c = node_a.wallets().user2().cloned(); + + let balance_a = node_a .chain_driver() .query_balance(&wallet_a.address(), &denom_a)?; @@ -63,13 +79,15 @@ impl BinaryChannelTest for IbcTransferTest { info!( "Sending IBC transfer from chain {} to chain {} with amount of {} {}", - chains.chain_id_a(), - chains.chain_id_b(), + context.chain_a().id(), + context.chain_b().id(), a_to_b_amount, denom_a ); - chains.node_a.chain_driver().ibc_transfer_token( + let channel = context.channel(); + + node_a.chain_driver().ibc_transfer_token( &channel.port_a.as_ref(), &channel.channel_id_a.as_ref(), &wallet_a.as_ref(), @@ -88,24 +106,25 @@ impl BinaryChannelTest for IbcTransferTest { a_to_b_amount ); - chains.node_a.chain_driver().assert_eventual_wallet_amount( + node_a.chain_driver().assert_eventual_wallet_amount( &wallet_a.address(), &(balance_a - a_to_b_amount).as_ref(), )?; - chains.node_b.chain_driver().assert_eventual_wallet_amount( + node_b.chain_driver().assert_eventual_wallet_amount( &wallet_b.address(), &denom_b.with_amount(a_to_b_amount).as_ref(), )?; + context.wait_for_src_acks()?; + info!( "successfully performed IBC transfer from chain {} to chain {}", - chains.chain_id_a(), - chains.chain_id_b(), + context.chain_a().id(), + context.chain_b().id(), ); - let balance_c = chains - .node_a + let balance_c = node_a .chain_driver() .query_balance(&wallet_c.address(), &denom_a)?; @@ -113,12 +132,12 @@ impl BinaryChannelTest for IbcTransferTest { info!( "Sending IBC transfer from chain {} to chain {} with amount of {}", - chains.chain_id_b(), - chains.chain_id_a(), + context.chain_b().id(), + context.chain_a().id(), b_to_a_amount, ); - chains.node_b.chain_driver().ibc_transfer_token( + node_b.chain_driver().ibc_transfer_token( &channel.port_b.as_ref(), &channel.channel_id_b.as_ref(), &wallet_b.as_ref(), @@ -126,22 +145,26 @@ impl BinaryChannelTest for IbcTransferTest { &denom_b.with_amount(b_to_a_amount).as_ref(), )?; - chains.node_b.chain_driver().assert_eventual_wallet_amount( + node_b.chain_driver().assert_eventual_wallet_amount( &wallet_b.address(), &denom_b.with_amount(a_to_b_amount - b_to_a_amount).as_ref(), )?; - chains.node_a.chain_driver().assert_eventual_wallet_amount( + node_a.chain_driver().assert_eventual_wallet_amount( &wallet_c.address(), &(balance_c + b_to_a_amount).as_ref(), )?; info!( "successfully performed reverse IBC transfer from chain {} back to chain {}", - chains.chain_id_b(), - chains.chain_id_a(), + context.chain_b().id(), + context.chain_a().id(), ); + context.wait_for_dst_acks()?; + + context.shutdown(handle); + Ok(()) } } diff --git a/tools/test-framework/Cargo.toml b/tools/test-framework/Cargo.toml index e8cdf503dc..8b5a91648e 100644 --- a/tools/test-framework/Cargo.toml +++ b/tools/test-framework/Cargo.toml @@ -14,9 +14,12 @@ description = """ """ [dependencies] -ibc-relayer-types = { version = "=0.25.0", path = "../../crates/relayer-types" } -ibc-relayer = { version = "=0.25.0", path = "../../crates/relayer" } +ibc-relayer-types = { version = "=0.25.0", path = "../../crates/relayer-types" } +ibc-relayer = { version = "=0.25.0", path = "../../crates/relayer" } +ibc-relayer-cosmos = { version = "=0.25.0", path = "../../crates/relayer-cosmos" } ibc-relayer-cli = { version = "=1.6.0", path = "../../crates/relayer-cli" } +ibc-relayer-all-in-one = { version = "=0.1.0", path = "../../crates/relayer-all-in-one" } +ibc-relayer-components = { version = "=0.1.0", path = "../../crates/relayer-components" } ibc-proto = { version = "0.34.0", features = ["serde"] } tendermint-rpc = { version = "0.33.0", features = ["http-client", "websocket-client"] } @@ -26,6 +29,7 @@ tracing = "0.1.36" tracing-subscriber = "0.3.14" eyre = "0.6.8" color-eyre = "0.6" +cfg-if = "1.0.0" rand = "0.8.5" hex = "0.4.3" serde = "1.0" diff --git a/tools/test-framework/src/docs/walkthroughs/memo.rs b/tools/test-framework/src/docs/walkthroughs/memo.rs index c13a667df1..7afbc5f7e9 100644 --- a/tools/test-framework/src/docs/walkthroughs/memo.rs +++ b/tools/test-framework/src/docs/walkthroughs/memo.rs @@ -11,6 +11,7 @@ //! ```no_run //! # use serde_json as json; //! # use ibc_relayer::config::{types::Memo, Config}; +//! # use ibc_test_framework::framework::next::chain::{CanSpawnRelayer, HasTwoChains, HasTwoChannels}; //! # 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}; @@ -35,13 +36,14 @@ //! } //! //! impl BinaryChannelTest for MemoTest { -//! fn run( -//! &self, -//! _config: &TestConfig, -//! _relayer: RelayerDriver, -//! chains: ConnectedChains, -//! channel: ConnectedChannel, -//! ) -> Result<(), Error> { +//! fn run(&self, _relayer: RelayerDriver, context: &Context) -> Result<(), Error> +//! where +//! Context: HasTwoChains + HasTwoChannels + CanSpawnRelayer, +//! { +//! let _handle = context.spawn_relayer()?; +//! let chains = context.chains(); +//! let channel = context.channel(); +//! //! let denom_a = chains.node_a.denom(); //! //! let a_to_b_amount = random_u128_range(1000, 5000); diff --git a/tools/test-framework/src/docs/walkthroughs/ordered_channel.rs b/tools/test-framework/src/docs/walkthroughs/ordered_channel.rs index c4ef2c4616..58377996e1 100644 --- a/tools/test-framework/src/docs/walkthroughs/ordered_channel.rs +++ b/tools/test-framework/src/docs/walkthroughs/ordered_channel.rs @@ -13,6 +13,7 @@ //! The test in most of its entirety (some parts omitted for brevity) looks like this: //! //! ```no_run +//! # use ibc_test_framework::framework::next::chain::{HasTwoChains, HasTwoChannels}; //! # use ibc_test_framework::ibc::denom::derive_ibc_denom; //! # use ibc_test_framework::prelude::*; //! # use ibc_test_framework::util::random::random_u128_range; @@ -40,13 +41,12 @@ //! } //! //! impl BinaryChannelTest for OrderedChannelTest { -//! fn run( -//! &self, -//! _config: &TestConfig, -//! relayer: RelayerDriver, -//! chains: ConnectedChains, -//! channel: ConnectedChannel, -//! ) -> Result<(), Error> { +//! fn run(&self, relayer: RelayerDriver, context: &Context) -> Result<(), Error> +//! where +//! Context: HasTwoChains + HasTwoChannels, +//! { +//! let chains = context.chains(); +//! let channel = context.channel(); //! let denom_a = chains.node_a.denom(); //! //! let wallet_a = chains.node_a.wallets().user1().cloned(); diff --git a/tools/test-framework/src/docs/walkthroughs/simple.rs b/tools/test-framework/src/docs/walkthroughs/simple.rs index bd7b8d0bf2..b426630c5b 100644 --- a/tools/test-framework/src/docs/walkthroughs/simple.rs +++ b/tools/test-framework/src/docs/walkthroughs/simple.rs @@ -16,12 +16,10 @@ //! impl TestOverrides for ExampleTest {} //! //! impl BinaryChannelTest for ExampleTest { -//! fn run( +//! fn run( //! &self, -//! _config: &TestConfig, //! _relayer: RelayerDriver, -//! _chains: ConnectedChains, -//! _channel: ConnectedChannel, +//! _context: &Context //! ) -> Result<(), Error> { //! suspend() //! } diff --git a/tools/test-framework/src/framework/binary/channel.rs b/tools/test-framework/src/framework/binary/channel.rs index 00ae9efd89..b11fe8525a 100644 --- a/tools/test-framework/src/framework/binary/channel.rs +++ b/tools/test-framework/src/framework/binary/channel.rs @@ -3,7 +3,6 @@ relayer setup with chain handles and foreign clients, as well as connected IBC channels with completed handshakes. */ - use tracing::info; use ibc_relayer::chain::handle::ChainHandle; @@ -26,10 +25,14 @@ use crate::framework::binary::ics::run_binary_interchain_security_node_test; use crate::framework::binary::node::{ run_binary_node_test, NodeConfigOverride, NodeGenesisOverride, }; +use crate::framework::next::chain::{ + CanShutdown, CanSpawnRelayer, CanWaitForAck, HasContextId, HasTestConfig, HasTwoChains, + HasTwoChannels, HasTwoNodes, +}; +use crate::framework::next::context::build_test_context; use crate::framework::supervisor::{RunWithSupervisor, SupervisorOverride}; use crate::relayer::driver::RelayerDriver; use crate::types::binary::chains::ConnectedChains; -use crate::types::binary::channel::ConnectedChannel; use crate::types::binary::connection::ConnectedConnection; use crate::types::config::TestConfig; use crate::types::env::write_env; @@ -112,13 +115,16 @@ where */ pub trait BinaryChannelTest { /// Test runner - fn run( - &self, - config: &TestConfig, - relayer: RelayerDriver, - chains: ConnectedChains, - channels: ConnectedChannel, - ) -> Result<(), Error>; + fn run(&self, relayer: RelayerDriver, context: &Context) -> Result<(), Error> + where + Context: HasTwoChains + + HasTwoChannels + + HasTwoNodes + + HasTestConfig + + CanSpawnRelayer + + HasContextId + + CanWaitForAck + + CanShutdown; } /** @@ -243,20 +249,23 @@ where info!("written channel environment to {}", env_path.display()); - self.test.run(config, relayer, chains, channels)?; + let test_context = build_test_context(config, relayer.clone(), chains, channels)?; + + self.test.run(relayer, &test_context)?; Ok(()) } } impl<'a, Test: BinaryChannelTest> BinaryChannelTest for RunTwoWayBinaryChannelTest<'a, Test> { - fn run( - &self, - config: &TestConfig, - relayer: RelayerDriver, - chains: ConnectedChains, - channels: ConnectedChannel, - ) -> Result<(), Error> { + fn run(&self, relayer: RelayerDriver, context: &Context) -> Result<(), Error> + where + Context: HasTwoChains + HasTwoChannels + HasTestConfig, + { + let config = context.config(); + let chains = context.chains().clone(); + let channels = context.channel().clone(); + info!( "running two-way channel test, from {}/{} to {}/{}", chains.chain_id_a(), @@ -265,8 +274,10 @@ impl<'a, Test: BinaryChannelTest> BinaryChannelTest for RunTwoWayBinaryChannelTe channels.channel_id_b, ); - self.test - .run(config, relayer.clone(), chains.clone(), channels.clone())?; + let test_context = + build_test_context(config, relayer.clone(), chains.clone(), channels.clone())?; + + self.test.run(relayer.clone(), &test_context)?; info!( "running two-way channel test in the opposite direction, from {}/{} to {}/{}", @@ -279,7 +290,9 @@ impl<'a, Test: BinaryChannelTest> BinaryChannelTest for RunTwoWayBinaryChannelTe let chains = chains.flip(); let channels = channels.flip(); - self.test.run(config, relayer, chains, channels)?; + let test_context = build_test_context(config, relayer.clone(), chains, channels)?; + + self.test.run(relayer, &test_context)?; Ok(()) } @@ -291,21 +304,24 @@ where Test: HasOverrides, Overrides: SupervisorOverride, { - fn run( - &self, - config: &TestConfig, - relayer: RelayerDriver, - chains: ConnectedChains, - channels: ConnectedChannel, - ) -> Result<(), Error> { + fn run(&self, relayer: RelayerDriver, context: &Context) -> Result<(), Error> + where + Context: HasTwoChains + + HasTwoChannels + + HasTwoNodes + + HasTestConfig + + CanSpawnRelayer + + HasContextId + + CanWaitForAck + + CanShutdown, + { + let config = context.config(); if self.get_overrides().should_spawn_supervisor() { relayer .clone() - .with_supervisor(|| self.test.run(config, relayer, chains, channels)) + .with_supervisor(|| self.test.run(relayer, context)) } else { - hang_on_error(config.hang_on_fail, || { - self.test.run(config, relayer, chains, channels) - }) + hang_on_error(config.hang_on_fail, || self.test.run(relayer, context)) } } } diff --git a/tools/test-framework/src/framework/binary/mod.rs b/tools/test-framework/src/framework/binary/mod.rs index 38e9dd4550..02045138a8 100644 --- a/tools/test-framework/src/framework/binary/mod.rs +++ b/tools/test-framework/src/framework/binary/mod.rs @@ -6,4 +6,5 @@ pub mod chain; pub mod channel; pub mod connection; pub mod ics; +pub mod next; pub mod node; diff --git a/tools/test-framework/src/framework/binary/next.rs b/tools/test-framework/src/framework/binary/next.rs new file mode 100644 index 0000000000..b0c4ae7ab6 --- /dev/null +++ b/tools/test-framework/src/framework/binary/next.rs @@ -0,0 +1,388 @@ +use eyre::eyre; +use std::thread; +use std::time::Duration; + +use ibc_relayer::chain::counterparty::unreceived_acknowledgements; +use ibc_relayer::chain::handle::{BaseChainHandle, ChainHandle}; +use ibc_relayer::foreign_client::ForeignClient; +use ibc_relayer::path::PathIdentifiers; +use ibc_relayer_all_in_one::one_for_all::traits::birelay::OfaBiRelay; +use ibc_relayer_all_in_one::one_for_all::types::birelay::OfaBiRelayWrapper; +use ibc_relayer_components::relay::traits::components::auto_relayer::CanAutoRelay; +use ibc_relayer_cosmos::contexts::birelay::CosmosBiRelay; +use ibc_relayer_types::core::ics04_channel::channel::IdentifiedChannelEnd; +use tokio::task::JoinHandle; + +use crate::error::Error; +use crate::framework::next::chain::{ + CanShutdown, CanSpawnRelayer, CanWaitForAck, HasContextId, HasTestConfig, HasTwoChains, + HasTwoChannels, HasTwoNodes, +}; +use crate::prelude::{ + assert_eventually_succeed, ConnectedChains, ConnectedChannel, FullNode, RelayerDriver, + TestConfig, +}; +use crate::types::tagged::*; +use crate::util::suspend::hang_on_error; + +const WAIT_PENDING_ACKS_ATTEMPTS: u16 = 90; + +/// Test context for the current relayer. +/// Uses a RelayerDriver. +pub struct TestContextV1 { + pub context_id: String, + pub config: TestConfig, + pub relayer: RelayerDriver, + pub chains: ConnectedChains, + pub channel: ConnectedChannel, +} + +impl HasTwoChains for TestContextV1 { + type ChainA = ChainA; + + type ChainB = ChainB; + + fn chain_a(&self) -> &Self::ChainA { + self.chains.handle_a() + } + + fn chain_b(&self) -> &Self::ChainB { + self.chains.handle_b() + } + + fn foreign_client_a_to_b(&self) -> &ForeignClient { + &self.chains.foreign_clients.client_a_to_b + } + + fn foreign_client_b_to_a(&self) -> &ForeignClient { + &self.chains.foreign_clients.client_b_to_a + } + + fn chains(&self) -> &ConnectedChains { + &self.chains + } +} + +impl HasTwoNodes for TestContextV1 { + fn node_a(&self) -> &MonoTagged { + &self.chains.node_a + } + + fn node_b(&self) -> &MonoTagged { + &self.chains.node_b + } +} + +impl HasTestConfig for TestContextV1 { + fn config(&self) -> &TestConfig { + &self.config + } +} + +impl HasTwoChannels for TestContextV1 { + fn channel(&self) -> &ConnectedChannel { + &self.channel + } +} + +impl CanSpawnRelayer for TestContextV1 { + fn spawn_relayer(&self) -> Result>, Error> { + let relayer = self.relayer.clone(); + thread::spawn(move || { + if let Ok(handler) = relayer.spawn_supervisor() { + handler.wait(); + } + }); + + Ok(None) + } + + fn with_supervisor(&self, cont: impl FnOnce() -> Result) -> Result { + self.relayer.with_supervisor(cont) + } +} + +pub fn wait_for_acks( + chain: &Chain, + counterparty: &Counterparty, + path_identifiers: &PathIdentifiers, +) -> Result<(), Error> +where + Chain: ChainHandle, + Counterparty: ChainHandle, +{ + assert_eventually_succeed( + "waiting on pending acks on chain", + WAIT_PENDING_ACKS_ATTEMPTS, + Duration::from_secs(1), + || { + let unreceived_acks = + unreceived_acknowledgements(chain, counterparty, path_identifiers); + + match unreceived_acks { + Ok(Some((acks, _))) => { + if acks.is_empty() { + Ok(()) + } else { + Err(Error::generic(eyre!( + "there are still {} pending acks", + acks.len() + ))) + } + } + Ok(None) => Ok(()), + Err(e) => Err(Error::generic(eyre!( + "error retrieving number of pending acks {}", + e + ))), + } + }, + ) +} + +impl CanWaitForAck for TestContextV1 { + fn wait_for_src_acks(&self) -> Result<(), Error> { + let src_chain = self.chain_a(); + let dst_chain = self.chain_b(); + let channel = self.channel(); + + let channel_end_a = match channel.channel.a_channel(Some(&channel.channel_id_a.0)) { + Ok(channel_end) => channel_end, + Err(e) => { + return Err(e.into()); + } + }; + let identified_channel_end_a = IdentifiedChannelEnd::new( + channel.port_a.0.clone(), + channel.channel_id_a.0.clone(), + channel_end_a, + ); + let path_identifiers_a = + match PathIdentifiers::from_channel_end(identified_channel_end_a.clone()) { + Some(path_identifiers) => path_identifiers, + None => { + return Err(Error::generic(eyre!( + "No path identifier found for {:?}", + identified_channel_end_a + ))); + } + }; + + wait_for_acks(src_chain, dst_chain, &path_identifiers_a)?; + + Ok(()) + } + + fn wait_for_dst_acks(&self) -> Result<(), Error> { + let src_chain = self.chain_a(); + let dst_chain = self.chain_b(); + let channel = self.channel(); + let channel_end_b = match channel.channel.b_channel(Some(&channel.channel_id_b.0)) { + Ok(channel_end) => channel_end, + Err(e) => { + return Err(e.into()); + } + }; + let identified_channel_end_b = IdentifiedChannelEnd::new( + channel.port_b.0.clone(), + channel.channel_id_b.0.clone(), + channel_end_b, + ); + let path_identifiers_b = + match PathIdentifiers::from_channel_end(identified_channel_end_b.clone()) { + Some(path_identifiers) => path_identifiers, + None => { + tracing::error!( + "{}", + Error::generic(eyre!("error getting path_identifiers b")) + ); + return Err(Error::generic(eyre!( + "No path identifier found for {:?}", + identified_channel_end_b + ))); + } + }; + + wait_for_acks(dst_chain, src_chain, &path_identifiers_b)?; + + Ok(()) + } +} + +impl CanShutdown for TestContextV1 { + fn shutdown(&self, _auto_relay_handle: Option>) {} +} + +impl HasContextId for TestContextV1 { + fn context_id(&self) -> String { + self.context_id.clone() + } +} + +/// Test context for the relayer-next. +/// Uses a OfaBiRelayWrapper. +pub struct TestContextV2 { + pub context_id: String, + pub config: TestConfig, + pub relayer: OfaBiRelayWrapper>, + pub chains: ConnectedChains, + pub channel: ConnectedChannel, +} + +impl HasTwoChains for TestContextV2 { + type ChainA = ChainA; + + type ChainB = ChainB; + + fn chain_a(&self) -> &Self::ChainA { + self.chains.handle_a() + } + + fn chain_b(&self) -> &Self::ChainB { + self.chains.handle_b() + } + + fn foreign_client_a_to_b(&self) -> &ForeignClient { + &self.chains.foreign_clients.client_a_to_b + } + + fn foreign_client_b_to_a(&self) -> &ForeignClient { + &self.chains.foreign_clients.client_b_to_a + } + + fn chains(&self) -> &ConnectedChains { + &self.chains + } +} + +impl HasTwoNodes for TestContextV2 { + fn node_a(&self) -> &MonoTagged { + &self.chains.node_a + } + + fn node_b(&self) -> &MonoTagged { + &self.chains.node_b + } +} + +impl HasTestConfig for TestContextV2 { + fn config(&self) -> &TestConfig { + &self.config + } +} + +impl HasTwoChannels for TestContextV2 { + fn channel(&self) -> &ConnectedChannel { + &self.channel + } +} + +impl CanSpawnRelayer for TestContextV2 { + fn spawn_relayer(&self) -> Result>, Error> { + let runtime = self.relayer.birelay.runtime(); + let birelay = self.relayer.clone(); + + let handle = runtime.runtime.spawn(async move { + let _ = birelay.auto_relay().await; + }); + + Ok(Some(handle)) + } + + fn with_supervisor(&self, cont: impl FnOnce() -> Result) -> Result { + self.spawn_relayer()?; + + hang_on_error(false, cont) + } +} + +impl CanWaitForAck for TestContextV2 { + fn wait_for_src_acks(&self) -> Result<(), Error> { + let src_chain = self.chain_a(); + let dst_chain = self.chain_b(); + let channel = self.channel(); + + let channel_end_a = match channel.channel.a_channel(Some(&channel.channel_id_a.0)) { + Ok(channel_end) => channel_end, + Err(e) => { + return Err(e.into()); + } + }; + let identified_channel_end_a = IdentifiedChannelEnd::new( + channel.port_a.0.clone(), + channel.channel_id_a.0.clone(), + channel_end_a, + ); + let path_identifiers_a = + match PathIdentifiers::from_channel_end(identified_channel_end_a.clone()) { + Some(path_identifiers) => path_identifiers, + None => { + return Err(Error::generic(eyre!( + "No path identifier found for {:?}", + identified_channel_end_a + ))); + } + }; + + wait_for_acks(src_chain, dst_chain, &path_identifiers_a)?; + + Ok(()) + } + + fn wait_for_dst_acks(&self) -> Result<(), Error> { + let src_chain = self.chain_a(); + let dst_chain = self.chain_b(); + let channel = self.channel(); + let channel_end_b = match channel.channel.b_channel(Some(&channel.channel_id_b.0)) { + Ok(channel_end) => channel_end, + Err(e) => { + return Err(e.into()); + } + }; + let identified_channel_end_b = IdentifiedChannelEnd::new( + channel.port_b.0.clone(), + channel.channel_id_b.0.clone(), + channel_end_b, + ); + let path_identifiers_b = + match PathIdentifiers::from_channel_end(identified_channel_end_b.clone()) { + Some(path_identifiers) => path_identifiers, + None => { + tracing::error!( + "{}", + Error::generic(eyre!("error getting path_identifiers b")) + ); + return Err(Error::generic(eyre!( + "No path identifier found for {:?}", + identified_channel_end_b + ))); + } + }; + + wait_for_acks(dst_chain, src_chain, &path_identifiers_b)?; + + Ok(()) + } +} + +/// This is a temporary solution. When the clean shutdown is implemented in the runtime +/// context, this should be replaced, see . +impl CanShutdown for TestContextV2 { + fn shutdown(&self, auto_relay_handle: Option>) { + if let Some(handle) = auto_relay_handle { + JoinHandle::abort(&handle); + loop { + if handle.is_finished() { + break; + } + thread::sleep(Duration::from_secs(1)); + } + } + } +} + +impl HasContextId for TestContextV2 { + fn context_id(&self) -> String { + self.context_id.clone() + } +} diff --git a/tools/test-framework/src/framework/mod.rs b/tools/test-framework/src/framework/mod.rs index ecc6d402e2..b5e46780ef 100644 --- a/tools/test-framework/src/framework/mod.rs +++ b/tools/test-framework/src/framework/mod.rs @@ -35,5 +35,6 @@ pub mod base; pub mod binary; pub mod nary; +pub mod next; pub mod overrides; pub mod supervisor; diff --git a/tools/test-framework/src/framework/nary/channel.rs b/tools/test-framework/src/framework/nary/channel.rs index cd7ccdfe78..5146cf26b4 100644 --- a/tools/test-framework/src/framework/nary/channel.rs +++ b/tools/test-framework/src/framework/nary/channel.rs @@ -3,10 +3,10 @@ together with the relayer setup with chain handles and foreign clients, as well as connected IBC channels with completed handshakes. */ +use tracing::info; use ibc_relayer::chain::handle::ChainHandle; use ibc_relayer_types::core::ics24_host::identifier::PortId; -use tracing::info; use crate::bootstrap::nary::channel::bootstrap_channels_with_connections; use crate::error::Error; @@ -18,6 +18,7 @@ use crate::framework::binary::node::{NodeConfigOverride, NodeGenesisOverride}; use crate::framework::nary::chain::RunNaryChainTest; use crate::framework::nary::connection::{NaryConnectionTest, RunNaryConnectionTest}; use crate::framework::nary::node::run_nary_node_test; +use crate::framework::next::context::build_test_context; use crate::framework::supervisor::{RunWithSupervisor, SupervisorOverride}; use crate::relayer::driver::RelayerDriver; use crate::types::config::TestConfig; @@ -204,8 +205,13 @@ where chains: NaryConnectedChains, channels: ConnectedChannels, ) -> Result<(), Error> { - self.test - .run(config, relayer, chains.into(), channels.into()) + let connected_chains = chains.connected_chains_at::<0, 1>()?; + let connected_channel = channels.channel_at::<0, 1>()?; + + let test_context = + build_test_context(config, relayer.clone(), connected_chains, connected_channel)?; + + self.test.run(relayer, &test_context) } } diff --git a/tools/test-framework/src/framework/next/chain.rs b/tools/test-framework/src/framework/next/chain.rs new file mode 100644 index 0000000000..2abe9dc38e --- /dev/null +++ b/tools/test-framework/src/framework/next/chain.rs @@ -0,0 +1,56 @@ +use ibc_relayer::chain::handle::ChainHandle; +use ibc_relayer::foreign_client::ForeignClient; +use tokio::task::JoinHandle; + +use crate::error::Error; +use crate::prelude::{ConnectedChains, ConnectedChannel, FullNode, TestConfig}; +use crate::types::tagged::*; + +pub trait HasTwoChains { + type ChainA: ChainHandle; + type ChainB: ChainHandle; + + fn chain_a(&self) -> &Self::ChainA; + + fn chain_b(&self) -> &Self::ChainB; + + fn foreign_client_a_to_b(&self) -> &ForeignClient; + + fn foreign_client_b_to_a(&self) -> &ForeignClient; + + fn chains(&self) -> &ConnectedChains; +} + +pub trait HasTwoNodes: HasTwoChains { + fn node_a(&self) -> &MonoTagged; + + fn node_b(&self) -> &MonoTagged; +} + +pub trait HasTestConfig { + fn config(&self) -> &TestConfig; +} + +pub trait HasTwoChannels: HasTwoChains { + fn channel(&self) -> &ConnectedChannel; +} + +pub trait CanSpawnRelayer { + fn spawn_relayer(&self) -> Result>, Error>; + + fn with_supervisor(&self, cont: impl FnOnce() -> Result) -> Result; +} + +pub trait CanWaitForAck: HasTwoChains { + fn wait_for_src_acks(&self) -> Result<(), Error>; + + fn wait_for_dst_acks(&self) -> Result<(), Error>; +} + +pub trait CanShutdown { + fn shutdown(&self, auto_relay_handle: Option>); +} + +pub trait HasContextId { + fn context_id(&self) -> String; +} diff --git a/tools/test-framework/src/framework/next/context.rs b/tools/test-framework/src/framework/next/context.rs new file mode 100644 index 0000000000..9e64e0ac12 --- /dev/null +++ b/tools/test-framework/src/framework/next/context.rs @@ -0,0 +1,95 @@ +#[cfg(feature = "next")] +use { + crate::framework::binary::next::TestContextV2, crate::prelude::handle_generic_error, + ibc_relayer::keyring::Secp256k1KeyPair, + ibc_relayer_all_in_one::extra::all_for_one::builder::CanBuildAfoFullBiRelay, + ibc_relayer_cosmos::contexts::full::builder::CosmosRelayBuilder, + ibc_relayer_types::core::ics24_host::identifier::ChainId, std::collections::HashMap, + std::sync::Arc, tokio::runtime::Runtime as TokioRuntime, +}; + +#[cfg(not(feature = "next"))] +use crate::framework::binary::next::TestContextV1; + +use crate::framework::next::chain::{ + CanShutdown, CanSpawnRelayer, CanWaitForAck, HasContextId, HasTestConfig, HasTwoChains, + HasTwoChannels, HasTwoNodes, +}; +use crate::prelude::*; + +pub fn build_test_context( + config: &TestConfig, + relayer: RelayerDriver, + chains: ConnectedChains, + channels: ConnectedChannel, +) -> Result< + impl HasTwoChains + + HasTwoChannels + + HasTwoNodes + + HasTestConfig + + CanSpawnRelayer + + HasContextId + + CanWaitForAck + + CanShutdown, + Error, +> { + cfg_if::cfg_if! { + if #[cfg(feature = "next")] { + + let runtime = Arc::new(TokioRuntime::new().unwrap()); + + // Build key map from existing keys in ChainHandle + let mut key_map: HashMap = HashMap::new(); + let key_a = chains.handle_a().get_key().unwrap(); + if let ibc_relayer::keyring::AnySigningKeyPair::Secp256k1(secp256k1_a) = key_a { + let chain_a_id = chains.handle_a().id(); + key_map.insert(chain_a_id, secp256k1_a); + } + let key_b = chains.handle_b().get_key().unwrap(); + if let ibc_relayer::keyring::AnySigningKeyPair::Secp256k1(secp256k1_b) = key_b { + let chain_b_id = chains.handle_b().id(); + key_map.insert(chain_b_id, secp256k1_b); + } + + let builder = CosmosRelayBuilder::new_wrapped( + relayer.config, + runtime.clone(), + Default::default(), + Default::default(), + Default::default(), + key_map, + ); + + let birelay = runtime.block_on(async {builder + .build_afo_full_birelay( + chains.chain_id_a().0, + chains.chain_id_b().0, + chains.client_id_a().0, + chains.client_id_b().0, + ) + .await + .map_err(handle_generic_error) + })?; + + let context_next = TestContextV2 { + context_id: "relayer_next".to_owned(), + config: config.clone(), + relayer: birelay, + chains, + channel: channels, + }; + + Ok(context_next) + } else { + let context_current = TestContextV1 { + context_id: "current_relayer".to_owned(), + config: config.clone(), + relayer, + chains, + channel: channels, + }; + + Ok(context_current) + } + } +} diff --git a/tools/test-framework/src/framework/next/mod.rs b/tools/test-framework/src/framework/next/mod.rs new file mode 100644 index 0000000000..d04fb322c1 --- /dev/null +++ b/tools/test-framework/src/framework/next/mod.rs @@ -0,0 +1,2 @@ +pub mod chain; +pub mod context; diff --git a/tools/test-framework/src/types/config.rs b/tools/test-framework/src/types/config.rs index ec11607bee..1cbfb95ff0 100644 --- a/tools/test-framework/src/types/config.rs +++ b/tools/test-framework/src/types/config.rs @@ -10,7 +10,7 @@ use std::path::PathBuf; from the [`init_test`](crate::bootstrap::init::init_test) function based on the test environment variables. */ -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct TestConfig { /** The command that the [`ChainDriver`](crate::chain::driver::ChainDriver)