├── .github ├── markdown-link-check.json └── workflows │ ├── markdown-link-check.yml │ ├── no-std.yaml │ ├── release.yml │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── build_crates.sh ├── check_crates.sh ├── ci └── no-std-check │ ├── .gitignore │ ├── Cargo.toml │ ├── Makefile │ ├── README.md │ └── src │ └── lib.rs ├── crates ├── ibc-types-core-channel │ ├── Cargo.toml │ └── src │ │ ├── channel.rs │ │ ├── commitment.rs │ │ ├── error.rs │ │ ├── events.rs │ │ ├── events │ │ ├── channel.rs │ │ ├── channel_attributes.rs │ │ ├── error.rs │ │ ├── packet.rs │ │ ├── packet_attributes.rs │ │ └── tests.rs │ │ ├── identifier.rs │ │ ├── lib.rs │ │ ├── mocks.rs │ │ ├── mod.rs │ │ ├── msgs.rs │ │ ├── msgs │ │ ├── acknowledgement.rs │ │ ├── chan_close_confirm.rs │ │ ├── chan_close_init.rs │ │ ├── chan_open_ack.rs │ │ ├── chan_open_confirm.rs │ │ ├── chan_open_init.rs │ │ ├── chan_open_try.rs │ │ ├── recv_packet.rs │ │ ├── timeout.rs │ │ └── timeout_on_close.rs │ │ ├── packet.rs │ │ ├── prelude.rs │ │ ├── timeout.rs │ │ └── version.rs ├── ibc-types-core-client │ ├── Cargo.toml │ └── src │ │ ├── client_id.rs │ │ ├── client_type.rs │ │ ├── error.rs │ │ ├── events.rs │ │ ├── height.rs │ │ ├── lib.rs │ │ ├── mock │ │ ├── client_state.rs │ │ ├── consensus_state.rs │ │ ├── header.rs │ │ ├── misbehaviour.rs │ │ └── mod.rs │ │ ├── msgs.rs │ │ ├── msgs │ │ ├── create_client.rs │ │ ├── misbehaviour.rs │ │ ├── update_client.rs │ │ └── upgrade_client.rs │ │ └── prelude.rs ├── ibc-types-core-commitment │ ├── Cargo.toml │ └── src │ │ ├── error.rs │ │ ├── lib.rs │ │ ├── mock.rs │ │ ├── path.rs │ │ ├── prefix.rs │ │ ├── prelude.rs │ │ ├── proof.rs │ │ └── root.rs ├── ibc-types-core-connection │ ├── Cargo.toml │ └── src │ │ ├── connection.rs │ │ ├── error.rs │ │ ├── events.rs │ │ ├── identifier.rs │ │ ├── lib.rs │ │ ├── mocks.rs │ │ ├── msgs.rs │ │ ├── msgs │ │ ├── conn_open_ack.rs │ │ ├── conn_open_confirm.rs │ │ ├── conn_open_init.rs │ │ └── conn_open_try.rs │ │ ├── prelude.rs │ │ └── version.rs ├── ibc-types-domain-type │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ └── prelude.rs ├── ibc-types-identifier │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ └── prelude.rs ├── ibc-types-lightclients-tendermint │ ├── Cargo.toml │ └── src │ │ ├── client_state.rs │ │ ├── consensus_state.rs │ │ ├── error.rs │ │ ├── header.rs │ │ ├── lib.rs │ │ ├── misbehaviour.rs │ │ ├── prelude.rs │ │ └── trust_threshold.rs ├── ibc-types-path │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── ibc-types-timestamp │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ └── prelude.rs ├── ibc-types-transfer │ ├── Cargo.toml │ └── src │ │ ├── acknowledgement.rs │ │ ├── lib.rs │ │ └── prelude.rs └── ibc-types │ ├── Cargo.toml │ └── src │ └── lib.rs ├── docs └── architecture │ ├── README.md │ ├── adr-001-handler-implementation.md │ ├── adr-002-error.md │ ├── adr-003-ics20-implementation.md │ ├── adr-004-light-client-crates-extraction.md │ ├── adr-005-handlers-redesign.md │ ├── adr-006-upgrade-client-implementation.md │ ├── adr-template.md │ └── assets │ └── adr05.jpg ├── flake.lock ├── flake.nix └── release.sh /.github/markdown-link-check.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignorePatterns": [ 3 | { 4 | "pattern": "^https://crates.io" 5 | }, 6 | { 7 | "pattern": "^https?://localhost" 8 | } 9 | ], 10 | "aliveStatusCodes": [429, 200] 11 | } 12 | -------------------------------------------------------------------------------- /.github/workflows/markdown-link-check.yml: -------------------------------------------------------------------------------- 1 | name: Check Markdown links 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | 7 | workflow_dispatch: 8 | 9 | jobs: 10 | md-link-check: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: markdown-link-check 15 | uses: gaurav-nelson/github-action-markdown-link-check@1.0.13 16 | with: 17 | config-file: '.github/markdown-link-check.json' 18 | use-quiet-mode: 'yes' 19 | use-verbose-mode: 'yes' 20 | -------------------------------------------------------------------------------- /.github/workflows/no-std.yaml: -------------------------------------------------------------------------------- 1 | # name: no_std check 2 | # on: 3 | # pull_request: 4 | # paths: 5 | # - .github/workflows/no-std.yml 6 | # - Cargo.toml 7 | # - Cargo.lock 8 | # - ci/** 9 | # - crates/** 10 | # push: 11 | # branches: main 12 | # paths: 13 | # - .github/workflows/no-std.yml 14 | # - Cargo.toml 15 | # - Cargo.lock 16 | # - ci/** 17 | # - crates/** 18 | # 19 | # jobs: 20 | # check-no-std-panic-conflict: 21 | # name: Check no_std panic conflict 22 | # runs-on: buildjet-16vcpu-ubuntu-2004 23 | # steps: 24 | # - uses: actions/checkout@v2 25 | # - uses: actions-rs/toolchain@v1 26 | # with: 27 | # toolchain: stable 28 | # override: true 29 | # - run: | 30 | # cd ci/no-std-check 31 | # make check-panic-conflict 32 | # 33 | # check-substrate: 34 | # name: Check no_std substrate support 35 | # runs-on: buildjet-16vcpu-ubuntu-2004 36 | # steps: 37 | # - uses: actions/checkout@v2 38 | # - uses: actions-rs/toolchain@v1 39 | # with: 40 | # toolchain: nightly 41 | # target: wasm32-unknown-unknown 42 | # override: true 43 | # - run: | 44 | # cd ci/no-std-check 45 | # make check-substrate 46 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # Attempts to perform a release when a particular tag is pushed. This uses the 2 | # release.sh script in the root of the repository, and assumes that the 3 | # CRATES_TOKEN secret has been set and contains an API token with which we can 4 | # publish our crates to crates.io. 5 | # 6 | # If release operation fails partway through due to a temporary error (e.g. the 7 | # crate being published depends on the crate published just prior, but the 8 | # prior crate isn't yet available via crates.io), one can simply rerun this 9 | # workflow. The release.sh script aims to be an idempotent operation, skipping 10 | # the publishing of crates that have already been published. 11 | name: Release 12 | 13 | on: 14 | push: 15 | tags: 16 | - "v[0-9]+.[0-9]+.[0-9]+" # Push events to matching v*, i.e. v0.26.0, v1.0.0 17 | - "v[0-9]+.[0-9]+.[0-9]+-pre.[0-9]+" # e.g. v0.26.0-pre.1 18 | 19 | jobs: 20 | release: 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@v3 24 | - uses: dtolnay/rust-toolchain@stable 25 | - name: Publish crates 26 | run: ./release.sh 27 | env: 28 | CRATES_TOKEN: ${{ secrets.CRATES_TOKEN }} 29 | 30 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | on: 3 | pull_request: 4 | paths: 5 | - .github/workflows/rust.yml 6 | - Cargo.toml 7 | - ci/** 8 | - crates/** 9 | push: 10 | branches: main 11 | paths: 12 | - .github/workflows/rust.yml 13 | - Cargo.toml 14 | - ci/** 15 | - crates/** 16 | 17 | env: 18 | CARGO_INCREMENTAL: 0 19 | CARGO_PROFILE_DEV_DEBUG: 1 20 | CARGO_PROFILE_RELEASE_DEBUG: 1 21 | RUST_BACKTRACE: short 22 | CARGO_NET_RETRY: 10 23 | RUSTUP_MAX_RETRIES: 10 24 | 25 | jobs: 26 | cleanup-runs: 27 | runs-on: buildjet-16vcpu-ubuntu-2004 28 | steps: 29 | - uses: rokroskar/workflow-run-cleanup-action@master 30 | env: 31 | GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 32 | if: "!startsWith(github.ref, 'refs/tags/') && github.ref != 'refs/heads/main'" 33 | 34 | fmt: 35 | runs-on: buildjet-16vcpu-ubuntu-2004 36 | steps: 37 | - uses: actions/checkout@v2 38 | - uses: actions-rs/toolchain@v1 39 | with: 40 | toolchain: stable 41 | override: true 42 | - uses: actions-rs/cargo@v1 43 | with: 44 | command: fmt 45 | args: --all -- --check 46 | 47 | doc: 48 | runs-on: buildjet-16vcpu-ubuntu-2004 49 | steps: 50 | - uses: actions/checkout@v2 51 | - uses: actions-rs/toolchain@v1 52 | with: 53 | toolchain: stable 54 | override: true 55 | - uses: actions-rs/cargo@v1 56 | with: 57 | command: doc 58 | args: --all-features --release 59 | 60 | test-stable: 61 | runs-on: buildjet-16vcpu-ubuntu-2004 62 | timeout-minutes: 30 63 | steps: 64 | - uses: actions/checkout@v2 65 | - uses: actions-rs/toolchain@v1 66 | with: 67 | toolchain: stable 68 | override: true 69 | - uses: Swatinem/rust-cache@v1 70 | - uses: actions-rs/cargo@v1 71 | with: 72 | command: test 73 | args: --all-features --no-fail-fast --no-run 74 | - uses: actions-rs/cargo@v1 75 | with: 76 | command: test 77 | args: --all-features --no-fail-fast --workspace -- --nocapture 78 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | target/ 4 | 5 | # These are backup files generated by rustfmt 6 | **/*.rs.bk 7 | 8 | # Ignore database folders 9 | **/*.db/ 10 | 11 | # Ignore Jetbrains 12 | .idea 13 | 14 | # Ignore VisualStudio 15 | .vscode 16 | 17 | # Ignore chain's data 18 | data 19 | 20 | # Ignore Python artifacts 21 | .mypy_cache/ 22 | __pycache__/ 23 | 24 | # Ignore modelator aritfacts 25 | .modelator 26 | mc.log 27 | 28 | # Ignore OSX .DS_Store file 29 | .DS_Store 30 | 31 | Cargo.lock 32 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | resolver = "2" 4 | 5 | members = [ 6 | "crates/ibc-types", 7 | "crates/ibc-types-timestamp", 8 | "crates/ibc-types-identifier", 9 | "crates/ibc-types-domain-type", 10 | "crates/ibc-types-core-client", 11 | "crates/ibc-types-core-connection", 12 | "crates/ibc-types-core-channel", 13 | "crates/ibc-types-core-commitment", 14 | "crates/ibc-types-lightclients-tendermint", 15 | "crates/ibc-types-path", 16 | "crates/ibc-types-transfer", 17 | ] 18 | 19 | exclude = [ 20 | "ci/no-std-check", 21 | ] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ibc-types 2 | 3 | This crate defines common data structures for Inter-Blockchain Communication 4 | (IBC) messages that can be reused by different IBC implementations or IBC 5 | ecosystem tooling. 6 | 7 | Unlike [ibc-rs], which provides a specific and opinionated implementation of 8 | IBC, `ibc-types` just defines Rust types that allow working with IBC messages, 9 | allowing an IBC implementation or IBC ecosystem tooling to be built on top using 10 | a common language. 11 | 12 | In addition to defining Rust types for IBC messages, `ibc-types` also defines 13 | Rust types for IBC events, and provides code for parsing IBC events to and from 14 | ABCI messages. IBC events are de facto a critical part of IBC, in that they're 15 | needed to interoperate with relayers, but are not really specified anywhere. 16 | Providing event parsing code in `ibc-types` allows IBC implementations and 17 | relayer implementations to share common code for producing and consuming events. 18 | 19 | The `ibc-types` crate is a top-level wrapper crate re-exporting the contents of 20 | subcrates scoped by IBC module. This structure means that external users of the 21 | library can use one catch-all crate, but allows dependency relationships between 22 | different IBC modules. For example, the `ibc-types` crate re-exports the client 23 | types defined in the `ibc-types-core-client` crate, as well as the types for the 24 | Tendermint light client defined in the `ibc-types-lightclients-tendermint` 25 | crate. But because these are separate crates, the Tendermint light client 26 | types can depend on the core IBC client types, preventing cyclic dependency 27 | issues. 28 | 29 | ## Contributing 30 | 31 | IBC is specified in English in the [cosmos/ibc repo][ibc]. Any 32 | protocol changes or clarifications should be contributed there. 33 | 34 | This repo contains Rust datatypes modeling IBC messages. 35 | 36 | ## Versioning 37 | 38 | We follow [Semantic Versioning][semver], though APIs are still 39 | under active development. 40 | 41 | ## Resources 42 | 43 | - [IBC Website][ibc-homepage] 44 | - [IBC Specification][ibc] 45 | - [IBC Go implementation][ibc-go] 46 | 47 | ## License 48 | 49 | Copyright © 2023 ibc-types authors. 50 | 51 | This crate was originally forked from ibc-rs: 52 | 53 | Copyright © 2022 Informal Systems Inc. and ibc-rs authors. 54 | 55 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use the files in this repository except in compliance with the License. You may 56 | obtain a copy of the License at 57 | 58 | https://www.apache.org/licenses/LICENSE-2.0 59 | 60 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 61 | CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 62 | 63 | [//]: # (badges) 64 | [docs-image]: https://docs.rs/ibc/badge.svg 65 | [docs-link]: https://docs.rs/ibc/ 66 | [build-image]: https://github.com/cosmos/ibc-rs/workflows/Rust/badge.svg 67 | [build-link]: https://github.com/cosmos/ibc-rs/actions?query=workflow%3ARust 68 | [codecov-image]: https://codecov.io/gh/cosmos/ibc-rs/branch/main/graph/badge.svg?token=wUm2aLCOu 69 | [codecov-link]: https://codecov.io/gh/cosmos/ibc-rs 70 | [license-image]: https://img.shields.io/badge/license-Apache2.0-blue.svg 71 | [license-link]: https://github.com/cosmos/ibc-rs/blob/main/LICENSE 72 | [rustc-image]: https://img.shields.io/badge/rustc-stable-blue.svg 73 | [rustc-version]: https://img.shields.io/badge/rustc-1.60+-blue.svg 74 | 75 | [//]: # (general links) 76 | [ibc-rs]: https://github.com/cosmos/ibc-rs 77 | [ibc]: https://github.com/cosmos/ibc 78 | [ibc-go]: https://github.com/cosmos/ibc-go 79 | [ibc-homepage]: https://cosmos.network/ibc 80 | [cosmos-link]: https://cosmos.network 81 | [semver]: https://semver.org/ 82 | -------------------------------------------------------------------------------- /build_crates.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | members=( 4 | "crates/ibc-types-domain-type" 5 | "crates/ibc-types-core-client" 6 | "crates/ibc-types-core-channel" 7 | "crates/ibc-types-timestamp" 8 | "crates/ibc-types-identifier" 9 | "crates/ibc-types-core-connection" 10 | "crates/ibc-types-core-commitment" 11 | "crates/ibc-types-lightclients-tendermint" 12 | "crates/ibc-types-path" 13 | "crates/ibc-types-transfer" 14 | "crates/ibc-types" 15 | ) 16 | 17 | for crate_path in "${members[@]}"; do 18 | echo "processing $crate_path" 19 | pushd $crate_path 20 | cargo build || exit 1 21 | popd 22 | done 23 | 24 | -------------------------------------------------------------------------------- /check_crates.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | members=( 4 | "crates/ibc-types-domain-type" 5 | "crates/ibc-types-core-client" 6 | "crates/ibc-types-core-channel" 7 | "crates/ibc-types-timestamp" 8 | "crates/ibc-types-identifier" 9 | "crates/ibc-types-core-connection" 10 | "crates/ibc-types-core-commitment" 11 | "crates/ibc-types-lightclients-tendermint" 12 | "crates/ibc-types-path" 13 | "crates/ibc-types-transfer" 14 | "crates/ibc-types" 15 | ) 16 | 17 | for crate_path in "${members[@]}"; do 18 | echo "processing $crate_path" 19 | pushd $crate_path 20 | cargo check || exit 1 21 | popd 22 | done 23 | 24 | -------------------------------------------------------------------------------- /ci/no-std-check/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /ci/no-std-check/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "no-std-check" 3 | version = "0.2.0" 4 | edition = "2021" 5 | resolver = "2" 6 | 7 | [dependencies] 8 | ibc-types = { path = "../../crates/ibc-types", default-features = false, features = [ 9 | "serde", 10 | "mocks-no-std", 11 | ] } 12 | ibc-proto = { version = "0.42.2", default-features = false } 13 | tendermint = { version = "0.34.0", default-features = false } 14 | tendermint-proto = { version = "0.34.0", default-features = false } 15 | tendermint-light-client-verifier = { version = "0.34.0", default-features = false, features = ["rust-crypto"] } 16 | 17 | sp-core = { version = "17.0.0", default-features = false, optional = true } 18 | sp-io = { version = "18.0.0", default-features = false, optional = true } 19 | sp-runtime = { version = "19.0.0", default-features = false, optional = true } 20 | sp-std = { version = "6.0.0", default-features = false, optional = true } 21 | 22 | [features] 23 | panic-handler = [] 24 | use-substrate = ["sp-core", "sp-io", "sp-runtime", "sp-std"] 25 | substrate-std = ["sp-core/std", "sp-io/std", "sp-runtime/std", "sp-std/std"] 26 | -------------------------------------------------------------------------------- /ci/no-std-check/Makefile: -------------------------------------------------------------------------------- 1 | NIGHTLY_VERSION=nightly 2 | 3 | .DEFAULT_GOAL := help 4 | 5 | .PHONY: all setup build-substrate check-panic-conflict check-cargo-build-std check-wasm check-substrate help 6 | 7 | all: ## Run the setup and all checks 8 | $(MAKE) build-substrate 9 | $(MAKE) check-panic-conflict 10 | $(MAKE) check-cargo-build-std 11 | $(MAKE) check-wasm 12 | $(MAKE) check-substrate 13 | 14 | setup: ## Setup the required nightly toolchain and the wasm32 target 15 | rustup install $(NIGHTLY_VERSION) 16 | rustup target add wasm32-unknown-unknown --toolchain $(NIGHTLY_VERSION) 17 | rustup component add rust-src --toolchain $(NIGHTLY_VERSION) 18 | 19 | build-substrate: ## Build with Substrate support 20 | cargo build \ 21 | --no-default-features \ 22 | --features use-substrate,substrate-std 23 | 24 | check-panic-conflict: ## Check for `no_std` compliance by installing a panic handler, and any other crate importing `std` will cause a conflict. Runs on default target. 25 | cargo build \ 26 | --no-default-features \ 27 | --features panic-handler 28 | 29 | check-cargo-build-std: ## Check for `no_std` compliance using Cargo nightly's `build-std` feature. Runs on the target `x86_64-unknown-linux-gnu`. 30 | rustup run $(NIGHTLY_VERSION) -- \ 31 | cargo build -Z build-std=core,alloc \ 32 | --no-default-features \ 33 | --target x86_64-unknown-linux-gnu 34 | 35 | check-wasm: ## Check for WebAssembly and `no_std` compliance by building on the target `wasm32-unknown-unknown` and installing a panic handler. 36 | rustup run $(NIGHTLY_VERSION) -- \ 37 | cargo build \ 38 | --features panic-handler \ 39 | --target wasm32-unknown-unknown 40 | 41 | check-substrate: ## Check for Substrate, WebAssembly, and `no_std` compliance by importing Substrate crates and building on `wasm32-unknown-unknown`. 42 | rustup run $(NIGHTLY_VERSION) -- \ 43 | cargo build \ 44 | --no-default-features \ 45 | --features use-substrate \ 46 | --target wasm32-unknown-unknown 47 | 48 | help: ## Show this help message 49 | @grep -E '^[a-z.A-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' 50 | -------------------------------------------------------------------------------- /ci/no-std-check/src/lib.rs: -------------------------------------------------------------------------------- 1 | // ensure_no_std/src/main.rs 2 | #![no_std] 3 | #![allow(unused_imports)] 4 | 5 | extern crate alloc; 6 | 7 | // Import the crates that we want to check if they are fully no-std compliance 8 | 9 | use ibc_proto; 10 | use ibc_types; 11 | use tendermint; 12 | use tendermint_light_client_verifier; 13 | use tendermint_proto; 14 | 15 | #[cfg(feature = "sp-core")] 16 | use sp_core; 17 | 18 | #[cfg(feature = "sp-io")] 19 | use sp_io; 20 | 21 | #[cfg(feature = "sp-runtime")] 22 | use sp_runtime; 23 | 24 | #[cfg(feature = "sp-std")] 25 | use sp_std; 26 | 27 | use core::panic::PanicInfo; 28 | 29 | /* 30 | 31 | This function definition checks for the compliance of no-std in 32 | dependencies by causing a compile error if this crate is 33 | linked with `std`. When that happens, you should see error messages 34 | such as follows: 35 | 36 | ``` 37 | error[E0152]: found duplicate lang item `panic_impl` 38 | --> no-std-check/src/lib.rs 39 | | 40 | 12 | fn panic(_info: &PanicInfo) -> ! { 41 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 42 | | 43 | = note: the lang item is first defined in crate `std` (which `offending-crate` depends on) 44 | ``` 45 | 46 | */ 47 | #[cfg(feature = "panic-handler")] 48 | #[panic_handler] 49 | #[no_mangle] 50 | fn panic(_info: &PanicInfo) -> ! { 51 | loop {} 52 | } 53 | -------------------------------------------------------------------------------- /crates/ibc-types-core-channel/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ibc-types-core-channel" 3 | version = "0.16.0" 4 | edition = "2021" 5 | license = "Apache-2.0" 6 | readme = "../../README.md" 7 | keywords = ["blockchain", "consensus", "cosmos", "ibc", "tendermint"] 8 | repository = "https://github.com/penumbra-zone/ibc-types" 9 | authors = ["Penumbra Labs ); 6 | 7 | impl PacketCommitment { 8 | pub fn into_vec(self) -> Vec { 9 | self.0 10 | } 11 | } 12 | 13 | impl AsRef<[u8]> for PacketCommitment { 14 | fn as_ref(&self) -> &[u8] { 15 | &self.0 16 | } 17 | } 18 | 19 | impl From> for PacketCommitment { 20 | fn from(bytes: Vec) -> Self { 21 | Self(bytes) 22 | } 23 | } 24 | 25 | /// Acknowledgement commitment to be stored 26 | #[derive(Clone, Debug, PartialEq, Eq)] 27 | pub struct AcknowledgementCommitment(pub Vec); 28 | 29 | impl AcknowledgementCommitment { 30 | pub fn into_vec(self) -> Vec { 31 | self.0 32 | } 33 | } 34 | 35 | impl AsRef<[u8]> for AcknowledgementCommitment { 36 | fn as_ref(&self) -> &[u8] { 37 | &self.0 38 | } 39 | } 40 | 41 | impl From> for AcknowledgementCommitment { 42 | fn from(bytes: Vec) -> Self { 43 | Self(bytes) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /crates/ibc-types-core-channel/src/events.rs: -------------------------------------------------------------------------------- 1 | //! Types for the IBC events emitted from Tendermint Websocket by the channels module. 2 | 3 | pub mod channel; 4 | pub mod packet; 5 | 6 | mod error; 7 | pub use error::Error; 8 | 9 | #[cfg(test)] 10 | mod tests; 11 | -------------------------------------------------------------------------------- /crates/ibc-types-core-channel/src/events/channel_attributes.rs: -------------------------------------------------------------------------------- 1 | ///! This module holds all the abci event attributes for IBC events emitted 2 | ///! during the channel handshake. 3 | use derive_more::From; 4 | use tendermint::abci; 5 | 6 | use crate::core::{ 7 | ics04_channel::Version, 8 | ics24_host::identifier::{ChannelId, ConnectionId, PortId}, 9 | }; 10 | 11 | const CONNECTION_ID_ATTRIBUTE_KEY: &str = "connection_id"; 12 | const CHANNEL_ID_ATTRIBUTE_KEY: &str = "channel_id"; 13 | const PORT_ID_ATTRIBUTE_KEY: &str = "port_id"; 14 | /// This attribute key is public so that OpenInit can use it to convert itself 15 | /// to an `AbciEvent` 16 | pub(super) const COUNTERPARTY_CHANNEL_ID_ATTRIBUTE_KEY: &str = "counterparty_channel_id"; 17 | const COUNTERPARTY_PORT_ID_ATTRIBUTE_KEY: &str = "counterparty_port_id"; 18 | const VERSION_ATTRIBUTE_KEY: &str = "version"; 19 | 20 | #[derive(Clone, Debug, From, PartialEq, Eq)] 21 | pub struct PortIdAttribute { 22 | pub port_id: PortId, 23 | } 24 | 25 | impl From for abci::EventAttribute { 26 | fn from(attr: PortIdAttribute) -> Self { 27 | (PORT_ID_ATTRIBUTE_KEY, attr.port_id.as_str()).into() 28 | } 29 | } 30 | 31 | #[derive(Clone, Debug, From, PartialEq, Eq)] 32 | pub struct ChannelIdAttribute { 33 | pub channel_id: ChannelId, 34 | } 35 | 36 | impl From for abci::EventAttribute { 37 | fn from(attr: ChannelIdAttribute) -> Self { 38 | (CHANNEL_ID_ATTRIBUTE_KEY, attr.channel_id.as_str()).into() 39 | } 40 | } 41 | #[derive(Clone, Debug, From, PartialEq, Eq)] 42 | pub struct CounterpartyPortIdAttribute { 43 | pub counterparty_port_id: PortId, 44 | } 45 | 46 | impl From for abci::EventAttribute { 47 | fn from(attr: CounterpartyPortIdAttribute) -> Self { 48 | ( 49 | COUNTERPARTY_PORT_ID_ATTRIBUTE_KEY, 50 | attr.counterparty_port_id.as_str(), 51 | ) 52 | .into() 53 | } 54 | } 55 | #[derive(Clone, Debug, From, PartialEq, Eq)] 56 | pub struct CounterpartyChannelIdAttribute { 57 | pub counterparty_channel_id: ChannelId, 58 | } 59 | 60 | impl From for abci::EventAttribute { 61 | fn from(attr: CounterpartyChannelIdAttribute) -> Self { 62 | ( 63 | COUNTERPARTY_CHANNEL_ID_ATTRIBUTE_KEY, 64 | attr.counterparty_channel_id.as_str(), 65 | ) 66 | .into() 67 | } 68 | } 69 | 70 | impl AsRef for CounterpartyChannelIdAttribute { 71 | fn as_ref(&self) -> &ChannelId { 72 | &self.counterparty_channel_id 73 | } 74 | } 75 | 76 | #[derive(Clone, Debug, From, PartialEq, Eq)] 77 | pub struct ConnectionIdAttribute { 78 | pub connection_id: ConnectionId, 79 | } 80 | 81 | impl From for abci::EventAttribute { 82 | fn from(attr: ConnectionIdAttribute) -> Self { 83 | (CONNECTION_ID_ATTRIBUTE_KEY, attr.connection_id.as_str()).into() 84 | } 85 | } 86 | 87 | #[derive(Clone, Debug, From, PartialEq, Eq)] 88 | pub struct VersionAttribute { 89 | pub version: Version, 90 | } 91 | 92 | impl From for abci::EventAttribute { 93 | fn from(attr: VersionAttribute) -> Self { 94 | (VERSION_ATTRIBUTE_KEY, attr.version.as_str()).into() 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /crates/ibc-types-core-channel/src/events/error.rs: -------------------------------------------------------------------------------- 1 | use crate::{prelude::*, ChannelError}; 2 | 3 | use core::num::ParseIntError; 4 | 5 | use displaydoc::Display; 6 | use ibc_types_core_client::HeightParseError; 7 | use ibc_types_identifier::IdentifierError; 8 | use ibc_types_timestamp::ParseTimestampError; 9 | 10 | /// An error while parsing an event. 11 | #[derive(Debug, Display)] 12 | pub enum Error { 13 | /// Wrong event type: expected {expected} 14 | WrongType { 15 | // The actual event type is intentionally not included in the error, so 16 | // that Error::WrongType doesn't allocate and is cheap to use for trial 17 | // deserialization (attempt parsing of each event type in turn, which is 18 | // then just as fast as matching over the event type) 19 | // 20 | // TODO: is this good? 21 | expected: &'static str, 22 | }, 23 | /// Missing expected event attribute "{0}" 24 | MissingAttribute(&'static str), 25 | /// Unexpected event attribute "{0}" 26 | UnexpectedAttribute(String), 27 | /// Error parsing channel order in "{key}": {e} 28 | ParseChannelOrder { key: &'static str, e: ChannelError }, 29 | /// Error parsing hex bytes in "{key}": {e} 30 | ParseHex { 31 | key: &'static str, 32 | e: subtle_encoding::Error, 33 | }, 34 | /// Error parsing timeout timestamp value in "{key}": {e} 35 | ParseTimeoutTimestampValue { key: &'static str, e: ParseIntError }, 36 | /// Error parsing timeout timestamp in "{key}": {e} 37 | ParseTimeoutTimestamp { 38 | key: &'static str, 39 | e: ParseTimestampError, 40 | }, 41 | /// Error parsing timeout height in "{key}": {e} 42 | ParseTimeoutHeight { 43 | key: &'static str, 44 | e: HeightParseError, 45 | }, 46 | /// Error parsing channel ID in "{key}": {e} 47 | ParseChannelId { 48 | key: &'static str, 49 | e: IdentifierError, 50 | }, 51 | /// Error parsing port ID in "{key}": {e} 52 | ParsePortId { 53 | key: &'static str, 54 | e: IdentifierError, 55 | }, 56 | /// Error parsing connection ID in "{key}": {e} 57 | ParseConnectionId { 58 | key: &'static str, 59 | e: IdentifierError, 60 | }, 61 | /// Error parsing packet sequence in "{key}": {e} 62 | ParseSequence { key: &'static str, e: ChannelError }, 63 | /// Two different encodings of the same packet data were supplied, but they don't match. 64 | MismatchedPacketData, 65 | /// Two different encodings of the same acknowledgements were supplied, but they don't match. 66 | MismatchedAcks, 67 | } 68 | 69 | #[cfg(feature = "std")] 70 | impl std::error::Error for Error { 71 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 72 | // Note: fill in if errors have causes 73 | match &self { 74 | Self::ParseChannelOrder { e, .. } => Some(e), 75 | Self::ParseHex { e, .. } => Some(e), 76 | _ => None, 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /crates/ibc-types-core-channel/src/events/tests.rs: -------------------------------------------------------------------------------- 1 | use crate::{prelude::*, ChannelId, PortId, Version}; 2 | 3 | use ibc_types_core_connection::ConnectionId; 4 | use tendermint::abci::Event as AbciEvent; 5 | 6 | use super::channel::*; 7 | 8 | #[test] 9 | fn ibc_to_abci_channel_events() { 10 | struct Test { 11 | kind: &'static str, 12 | event: AbciEvent, 13 | expected_keys: Vec<&'static str>, 14 | expected_values: Vec<&'static str>, 15 | } 16 | 17 | let port_id = PortId::transfer(); 18 | let channel_id = ChannelId::new(0); 19 | let connection_id = ConnectionId::new(0); 20 | let counterparty_port_id = PortId::transfer(); 21 | let counterparty_channel_id = ChannelId::new(1); 22 | let version = Version::new("ics20-1".to_string()); 23 | let expected_keys = vec![ 24 | "port_id", 25 | "channel_id", 26 | "counterparty_port_id", 27 | "counterparty_channel_id", 28 | "connection_id", 29 | "version", 30 | ]; 31 | let expected_values = vec![ 32 | "transfer", 33 | "channel-0", 34 | "transfer", 35 | "channel-1", 36 | "connection-0", 37 | "ics20-1", 38 | ]; 39 | 40 | let tests: Vec = vec![ 41 | Test { 42 | kind: "channel_open_init", 43 | event: OpenInit { 44 | port_id: port_id.clone(), 45 | channel_id: channel_id.clone(), 46 | counterparty_port_id: counterparty_port_id.clone(), 47 | connection_id: connection_id.clone(), 48 | version: version.clone(), 49 | } 50 | .into(), 51 | expected_keys: vec![ 52 | "port_id", 53 | "channel_id", 54 | "counterparty_port_id", 55 | "connection_id", 56 | "version", 57 | ], 58 | expected_values: vec![ 59 | "transfer", 60 | "channel-0", 61 | "transfer", 62 | "connection-0", 63 | "ics20-1", 64 | ], 65 | }, 66 | Test { 67 | kind: "channel_open_try", 68 | event: OpenTry { 69 | port_id: port_id.clone(), 70 | channel_id: channel_id.clone(), 71 | counterparty_port_id: counterparty_port_id.clone(), 72 | counterparty_channel_id: counterparty_channel_id.clone(), 73 | connection_id: connection_id.clone(), 74 | version: version.clone(), 75 | } 76 | .into(), 77 | expected_keys: expected_keys.clone(), 78 | expected_values: expected_values.clone(), 79 | }, 80 | Test { 81 | kind: "channel_open_ack", 82 | event: OpenAck { 83 | port_id: port_id.clone(), 84 | channel_id: channel_id.clone(), 85 | counterparty_port_id: counterparty_port_id.clone(), 86 | counterparty_channel_id: counterparty_channel_id.clone(), 87 | connection_id: connection_id.clone(), 88 | } 89 | .into(), 90 | expected_keys: expected_keys[0..5].to_vec(), 91 | expected_values: expected_values[0..5].to_vec(), 92 | }, 93 | Test { 94 | kind: "channel_open_confirm", 95 | event: OpenConfirm { 96 | port_id: port_id.clone(), 97 | channel_id: channel_id.clone(), 98 | counterparty_port_id: counterparty_port_id.clone(), 99 | counterparty_channel_id: counterparty_channel_id.clone(), 100 | connection_id: connection_id.clone(), 101 | } 102 | .into(), 103 | expected_keys: expected_keys[0..5].to_vec(), 104 | expected_values: expected_values[0..5].to_vec(), 105 | }, 106 | Test { 107 | kind: "channel_close_init", 108 | event: CloseInit { 109 | port_id: port_id.clone(), 110 | channel_id: channel_id.clone(), 111 | counterparty_port_id: counterparty_port_id.clone(), 112 | counterparty_channel_id: counterparty_channel_id.clone(), 113 | connection_id: connection_id.clone(), 114 | } 115 | .into(), 116 | expected_keys: expected_keys[0..5].to_vec(), 117 | expected_values: expected_values[0..5].to_vec(), 118 | }, 119 | Test { 120 | kind: "channel_close_confirm", 121 | event: CloseConfirm { 122 | port_id: port_id.clone(), 123 | channel_id: channel_id.clone(), 124 | counterparty_port_id: counterparty_port_id.clone(), 125 | counterparty_channel_id: counterparty_channel_id.clone(), 126 | connection_id: connection_id.clone(), 127 | } 128 | .into(), 129 | expected_keys: expected_keys[0..5].to_vec(), 130 | expected_values: expected_values[0..5].to_vec(), 131 | }, 132 | ]; 133 | 134 | for t in tests { 135 | assert_eq!(t.kind, t.event.kind); 136 | assert_eq!(t.expected_keys.len(), t.event.attributes.len()); 137 | for (i, e) in t.event.attributes.iter().enumerate() { 138 | assert_eq!( 139 | e.key_bytes(), 140 | t.expected_keys[i].as_bytes(), 141 | "key mismatch for {:?}", 142 | t.kind, 143 | ); 144 | } 145 | assert_eq!(t.expected_values.len(), t.event.attributes.len()); 146 | for (i, e) in t.event.attributes.iter().enumerate() { 147 | assert_eq!( 148 | e.value_bytes(), 149 | t.expected_values[i].as_bytes(), 150 | "value mismatch for {:?}", 151 | t.kind, 152 | ); 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /crates/ibc-types-core-channel/src/identifier.rs: -------------------------------------------------------------------------------- 1 | use core::{ 2 | fmt::{Debug, Display, Error as FmtError, Formatter}, 3 | str::FromStr, 4 | }; 5 | 6 | use ibc_types_identifier::{ 7 | validate_channel_identifier, validate_port_identifier, IdentifierError, 8 | }; 9 | 10 | use crate::prelude::*; 11 | 12 | #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 13 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 14 | pub struct PortId(pub String); 15 | 16 | impl PortId { 17 | /// Infallible creation of the well-known transfer port 18 | pub fn transfer() -> Self { 19 | Self("transfer".to_string()) 20 | } 21 | 22 | /// Get this identifier as a borrowed `&str` 23 | pub fn as_str(&self) -> &str { 24 | &self.0 25 | } 26 | 27 | /// Get this identifier as a borrowed byte slice 28 | pub fn as_bytes(&self) -> &[u8] { 29 | self.0.as_bytes() 30 | } 31 | } 32 | 33 | /// This implementation provides a `to_string` method. 34 | impl Display for PortId { 35 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { 36 | write!(f, "{}", self.0) 37 | } 38 | } 39 | 40 | impl FromStr for PortId { 41 | type Err = IdentifierError; 42 | 43 | fn from_str(s: &str) -> Result { 44 | validate_port_identifier(s).map(|_| Self(s.to_string())) 45 | } 46 | } 47 | 48 | impl AsRef for PortId { 49 | fn as_ref(&self) -> &str { 50 | self.0.as_str() 51 | } 52 | } 53 | 54 | impl Default for PortId { 55 | fn default() -> Self { 56 | "defaultPort".to_string().parse().unwrap() 57 | } 58 | } 59 | 60 | #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 61 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 62 | pub struct ChannelId(pub String); 63 | 64 | impl ChannelId { 65 | const PREFIX: &'static str = "channel-"; 66 | 67 | /// Builds a new channel identifier. Like client and connection identifiers, channel ids are 68 | /// deterministically formed from two elements: a prefix `prefix`, and a monotonically 69 | /// increasing `counter`, separated by a dash "-". 70 | /// The prefix is currently determined statically (see `ChannelId::prefix()`) so this method 71 | /// accepts a single argument, the `counter`. 72 | /// 73 | /// ``` 74 | /// use ibc_types_core_channel::ChannelId; 75 | /// let chan_id = ChannelId::new(27); 76 | /// assert_eq!(chan_id.to_string(), "channel-27"); 77 | /// ``` 78 | pub fn new(identifier: u64) -> Self { 79 | let id = format!("{}{}", Self::PREFIX, identifier); 80 | Self(id) 81 | } 82 | 83 | /// Get this identifier as a borrowed `&str` 84 | pub fn as_str(&self) -> &str { 85 | &self.0 86 | } 87 | 88 | /// Get this identifier as a borrowed byte slice 89 | pub fn as_bytes(&self) -> &[u8] { 90 | self.0.as_bytes() 91 | } 92 | } 93 | 94 | /// This implementation provides a `to_string` method. 95 | impl Display for ChannelId { 96 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { 97 | write!(f, "{}", self.0) 98 | } 99 | } 100 | 101 | impl FromStr for ChannelId { 102 | type Err = IdentifierError; 103 | 104 | fn from_str(s: &str) -> Result { 105 | validate_channel_identifier(s).map(|_| Self(s.to_string())) 106 | } 107 | } 108 | 109 | impl AsRef for ChannelId { 110 | fn as_ref(&self) -> &str { 111 | &self.0 112 | } 113 | } 114 | 115 | impl Default for ChannelId { 116 | fn default() -> Self { 117 | Self::new(0) 118 | } 119 | } 120 | 121 | /// Equality check against string literal (satisfies &ChannelId == &str). 122 | /// ``` 123 | /// use core::str::FromStr; 124 | /// use ibc_types_core_channel::ChannelId; 125 | /// let channel_id = ChannelId::from_str("channelId-0"); 126 | /// assert!(channel_id.is_ok()); 127 | /// channel_id.map(|id| {assert_eq!(&id, "channelId-0")}); 128 | /// ``` 129 | impl PartialEq for ChannelId { 130 | fn eq(&self, other: &str) -> bool { 131 | self.as_str().eq(other) 132 | } 133 | } 134 | 135 | /// A pair of [`PortId`] and [`ChannelId`] are used together for sending IBC packets. 136 | #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 137 | pub struct PortChannelId { 138 | pub channel_id: ChannelId, 139 | pub port_id: PortId, 140 | } 141 | 142 | impl Display for PortChannelId { 143 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { 144 | write!(f, "{}/{}", self.port_id, self.channel_id) 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /crates/ibc-types-core-channel/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! IBC channel-related types. 2 | #![no_std] 3 | // Requires nightly. 4 | #![cfg_attr(docsrs, feature(doc_auto_cfg))] 5 | 6 | extern crate alloc; 7 | #[cfg(any(test, feature = "std"))] 8 | extern crate std; 9 | 10 | pub mod channel; 11 | pub mod packet; 12 | 13 | mod commitment; 14 | mod error; 15 | mod identifier; 16 | mod prelude; 17 | mod timeout; 18 | mod version; 19 | 20 | pub use channel::{ChannelEnd, Counterparty, IdentifiedChannelEnd}; 21 | pub use commitment::{AcknowledgementCommitment, PacketCommitment}; 22 | pub use error::{ChannelError, PacketError}; 23 | pub use identifier::{ChannelId, PortId}; 24 | pub use packet::Packet; 25 | pub use timeout::TimeoutHeight; 26 | pub use version::Version; 27 | 28 | pub mod events; 29 | pub mod msgs; 30 | 31 | #[cfg(any(test, feature = "mocks", feature = "mocks-no-std"))] 32 | pub mod mocks; 33 | -------------------------------------------------------------------------------- /crates/ibc-types-core-channel/src/mocks.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec; 2 | use ibc_types_core_commitment::MerkleProof; 3 | use ibc_types_domain_type::DomainType; 4 | use ics23::CommitmentProof; 5 | 6 | use crate::prelude::*; 7 | 8 | pub fn get_dummy_proof() -> vec::Vec { 9 | let m = MerkleProof { 10 | proofs: vec![CommitmentProof::default()], 11 | }; 12 | m.encode_to_vec() 13 | } 14 | 15 | pub fn get_dummy_account_id() -> String { 16 | "0CDA3F47EF3C4906693B170EF650EB968C5F4B2C".parse().unwrap() 17 | } 18 | 19 | pub fn get_dummy_bech32_account() -> String { 20 | "cosmos1wxeyh7zgn4tctjzs0vtqpc6p5cxq5t2muzl7ng".to_string() 21 | } 22 | -------------------------------------------------------------------------------- /crates/ibc-types-core-channel/src/mod.rs: -------------------------------------------------------------------------------- 1 | //! ICS 04: Channel implementation that facilitates communication between 2 | //! applications and the chains those applications are built upon. 3 | 4 | pub mod channel; 5 | pub mod context; 6 | pub mod error; 7 | pub mod events; 8 | 9 | pub mod handler; 10 | pub mod msgs; 11 | pub mod packet; 12 | pub mod timeout; 13 | 14 | pub mod commitment; 15 | mod version; 16 | pub use version::Version; 17 | -------------------------------------------------------------------------------- /crates/ibc-types-core-channel/src/msgs.rs: -------------------------------------------------------------------------------- 1 | //! Message definitions for all ICS4 domain types: channel open & close handshake datagrams, as well 2 | //! as packets. 3 | 4 | // Opening handshake messages. 5 | mod chan_open_ack; 6 | mod chan_open_confirm; 7 | mod chan_open_init; 8 | mod chan_open_try; 9 | 10 | pub use chan_open_ack::MsgChannelOpenAck; 11 | pub use chan_open_confirm::MsgChannelOpenConfirm; 12 | pub use chan_open_init::MsgChannelOpenInit; 13 | pub use chan_open_try::MsgChannelOpenTry; 14 | 15 | // Closing handshake messages. 16 | mod chan_close_confirm; 17 | mod chan_close_init; 18 | 19 | pub use chan_close_confirm::MsgChannelCloseConfirm; 20 | pub use chan_close_init::MsgChannelCloseInit; 21 | 22 | // Packet specific messages. 23 | mod acknowledgement; 24 | mod recv_packet; 25 | mod timeout; 26 | mod timeout_on_close; 27 | 28 | pub use acknowledgement::MsgAcknowledgement; 29 | pub use recv_packet::MsgRecvPacket; 30 | pub use timeout::MsgTimeout; 31 | pub use timeout_on_close::MsgTimeoutOnClose; 32 | 33 | /// Enumeration of all possible messages that the ICS4 protocol processes. 34 | #[derive(Clone, Debug, PartialEq)] 35 | pub enum ChannelMsg { 36 | OpenInit(MsgChannelOpenInit), 37 | OpenTry(MsgChannelOpenTry), 38 | OpenAck(MsgChannelOpenAck), 39 | OpenConfirm(MsgChannelOpenConfirm), 40 | CloseInit(MsgChannelCloseInit), 41 | CloseConfirm(MsgChannelCloseConfirm), 42 | } 43 | 44 | #[derive(Clone, Debug, PartialEq)] 45 | pub enum PacketMsg { 46 | Recv(MsgRecvPacket), 47 | Ack(MsgAcknowledgement), 48 | Timeout(MsgTimeout), 49 | TimeoutOnClose(MsgTimeoutOnClose), 50 | } 51 | -------------------------------------------------------------------------------- /crates/ibc-types-core-channel/src/msgs/chan_close_init.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | 3 | use ibc_proto::ibc::core::channel::v1::MsgChannelCloseInit as RawMsgChannelCloseInit; 4 | 5 | use ibc_types_domain_type::DomainType; 6 | 7 | use crate::{ChannelError, ChannelId, PortId}; 8 | 9 | /// 10 | /// Message definition for the first step in the channel close handshake (`ChanCloseInit` datagram). 11 | /// Per our convention, this message is sent to chain A. 12 | /// 13 | #[derive(Clone, Debug, PartialEq, Eq)] 14 | pub struct MsgChannelCloseInit { 15 | pub port_id_on_a: PortId, 16 | pub chan_id_on_a: ChannelId, 17 | pub signer: String, 18 | } 19 | 20 | impl DomainType for MsgChannelCloseInit { 21 | type Proto = RawMsgChannelCloseInit; 22 | } 23 | 24 | impl TryFrom for MsgChannelCloseInit { 25 | type Error = ChannelError; 26 | 27 | fn try_from(raw_msg: RawMsgChannelCloseInit) -> Result { 28 | Ok(MsgChannelCloseInit { 29 | port_id_on_a: raw_msg.port_id.parse().map_err(ChannelError::Identifier)?, 30 | chan_id_on_a: raw_msg 31 | .channel_id 32 | .parse() 33 | .map_err(ChannelError::Identifier)?, 34 | signer: raw_msg.signer, 35 | }) 36 | } 37 | } 38 | 39 | impl From for RawMsgChannelCloseInit { 40 | fn from(domain_msg: MsgChannelCloseInit) -> Self { 41 | RawMsgChannelCloseInit { 42 | port_id: domain_msg.port_id_on_a.to_string(), 43 | channel_id: domain_msg.chan_id_on_a.to_string(), 44 | signer: domain_msg.signer, 45 | } 46 | } 47 | } 48 | 49 | #[cfg(test)] 50 | pub mod test_util { 51 | use crate::prelude::*; 52 | use ibc_proto::ibc::core::channel::v1::MsgChannelCloseInit as RawMsgChannelCloseInit; 53 | 54 | use crate::mocks::get_dummy_bech32_account; 55 | use crate::{ChannelId, PortId}; 56 | 57 | /// Returns a dummy `RawMsgChannelCloseInit`, for testing only! 58 | pub fn get_dummy_raw_msg_chan_close_init() -> RawMsgChannelCloseInit { 59 | RawMsgChannelCloseInit { 60 | port_id: PortId::default().to_string(), 61 | channel_id: ChannelId::default().to_string(), 62 | signer: get_dummy_bech32_account(), 63 | } 64 | } 65 | } 66 | 67 | #[cfg(test)] 68 | mod tests { 69 | use crate::prelude::*; 70 | 71 | use test_log::test; 72 | 73 | use ibc_proto::ibc::core::channel::v1::MsgChannelCloseInit as RawMsgChannelCloseInit; 74 | 75 | use super::test_util::get_dummy_raw_msg_chan_close_init; 76 | use super::*; 77 | 78 | #[test] 79 | fn parse_channel_close_init_msg() { 80 | struct Test { 81 | name: String, 82 | raw: RawMsgChannelCloseInit, 83 | want_pass: bool, 84 | } 85 | 86 | let default_raw_msg = get_dummy_raw_msg_chan_close_init(); 87 | 88 | let tests: Vec = vec![ 89 | Test { 90 | name: "Good parameters".to_string(), 91 | raw: default_raw_msg.clone(), 92 | want_pass: true, 93 | }, 94 | Test { 95 | name: "Correct port".to_string(), 96 | raw: RawMsgChannelCloseInit { 97 | port_id: "p34".to_string(), 98 | ..default_raw_msg.clone() 99 | }, 100 | want_pass: true, 101 | }, 102 | Test { 103 | name: "Bad port, name too short".to_string(), 104 | raw: RawMsgChannelCloseInit { 105 | port_id: "p".to_string(), 106 | ..default_raw_msg.clone() 107 | }, 108 | want_pass: false, 109 | }, 110 | Test { 111 | name: "Bad port, name too long".to_string(), 112 | raw: RawMsgChannelCloseInit { 113 | port_id: "abcdefsdfasdfasdfasdfasdfasdfadsfasdgafsgadfasdfasdfasdfsdfasdfaghijklmnopqrstuabcdefsdfasdfasdfasdfasdfasdfadsfasdgafsgadfasdfasdfasdfsdfasdfaghijklmnopqrstu".to_string(), 114 | ..default_raw_msg.clone() 115 | }, 116 | want_pass: false, 117 | }, 118 | Test { 119 | name: "Correct channel identifier".to_string(), 120 | raw: RawMsgChannelCloseInit { 121 | channel_id: "channel-34".to_string(), 122 | ..default_raw_msg.clone() 123 | }, 124 | want_pass: true, 125 | }, 126 | Test { 127 | name: "Bad channel, name too short".to_string(), 128 | raw: RawMsgChannelCloseInit { 129 | channel_id: "chshort".to_string(), 130 | ..default_raw_msg.clone() 131 | }, 132 | want_pass: false, 133 | }, 134 | Test { 135 | name: "Bad channel, name too long".to_string(), 136 | raw: RawMsgChannelCloseInit { 137 | channel_id: "channel-128391283791827398127398791283912837918273981273987912839".to_string(), 138 | ..default_raw_msg 139 | }, 140 | want_pass: false, 141 | }, 142 | ] 143 | .into_iter() 144 | .collect(); 145 | 146 | for test in tests { 147 | let msg = MsgChannelCloseInit::try_from(test.raw.clone()); 148 | 149 | assert_eq!( 150 | test.want_pass, 151 | msg.is_ok(), 152 | "MsgChanCloseInit::try_from failed for test {}, \nmsg {:?} with error {:?}", 153 | test.name, 154 | test.raw, 155 | msg.err(), 156 | ); 157 | } 158 | } 159 | 160 | #[test] 161 | fn to_and_from() { 162 | let raw = get_dummy_raw_msg_chan_close_init(); 163 | let msg = MsgChannelCloseInit::try_from(raw.clone()).unwrap(); 164 | let raw_back = RawMsgChannelCloseInit::from(msg.clone()); 165 | let msg_back = MsgChannelCloseInit::try_from(raw_back.clone()).unwrap(); 166 | assert_eq!(raw, raw_back); 167 | assert_eq!(msg, msg_back); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /crates/ibc-types-core-channel/src/msgs/chan_open_init.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | 3 | use ibc_types_core_connection::ConnectionId; 4 | use ibc_types_domain_type::DomainType; 5 | 6 | use crate::{ 7 | channel::{ChannelEnd, Counterparty, Order, State}, 8 | ChannelError, PortId, Version, 9 | }; 10 | 11 | use ibc_proto::ibc::core::channel::v1::MsgChannelOpenInit as RawMsgChannelOpenInit; 12 | 13 | /// 14 | /// Message definition for the first step in the channel open handshake (`ChanOpenInit` datagram). 15 | /// Per our convention, this message is sent to chain A. 16 | /// 17 | #[derive(Clone, Debug, PartialEq, Eq)] 18 | pub struct MsgChannelOpenInit { 19 | pub port_id_on_a: PortId, 20 | pub connection_hops_on_a: Vec, 21 | pub port_id_on_b: PortId, 22 | pub ordering: Order, 23 | pub signer: String, 24 | /// Allow a relayer to specify a particular version by providing a non-empty version string 25 | pub version_proposal: Version, 26 | } 27 | 28 | impl DomainType for MsgChannelOpenInit { 29 | type Proto = RawMsgChannelOpenInit; 30 | } 31 | 32 | impl TryFrom for MsgChannelOpenInit { 33 | type Error = ChannelError; 34 | 35 | fn try_from(raw_msg: RawMsgChannelOpenInit) -> Result { 36 | let chan_end_on_a: ChannelEnd = raw_msg 37 | .channel 38 | .ok_or(ChannelError::MissingChannel)? 39 | .try_into()?; 40 | Ok(MsgChannelOpenInit { 41 | port_id_on_a: raw_msg.port_id.parse().map_err(ChannelError::Identifier)?, 42 | connection_hops_on_a: chan_end_on_a.connection_hops, 43 | port_id_on_b: chan_end_on_a.remote.port_id, 44 | ordering: chan_end_on_a.ordering, 45 | signer: raw_msg.signer, 46 | version_proposal: chan_end_on_a.version, 47 | }) 48 | } 49 | } 50 | impl From for RawMsgChannelOpenInit { 51 | fn from(domain_msg: MsgChannelOpenInit) -> Self { 52 | let chan_end_on_a = ChannelEnd::new( 53 | State::Init, 54 | domain_msg.ordering, 55 | Counterparty::new(domain_msg.port_id_on_b, None), 56 | domain_msg.connection_hops_on_a, 57 | domain_msg.version_proposal, 58 | // Initial upgrade sequence for a newly initialized channel is 0 59 | 0, 60 | ); 61 | RawMsgChannelOpenInit { 62 | port_id: domain_msg.port_id_on_a.to_string(), 63 | channel: Some(chan_end_on_a.into()), 64 | signer: domain_msg.signer, 65 | } 66 | } 67 | } 68 | 69 | #[cfg(test)] 70 | pub mod test_util { 71 | use crate::prelude::*; 72 | use ibc_proto::ibc::core::channel::v1::MsgChannelOpenInit as RawMsgChannelOpenInit; 73 | 74 | use crate::channel::test_util::get_dummy_raw_channel_end; 75 | use crate::mocks::get_dummy_bech32_account; 76 | use crate::PortId; 77 | 78 | /// Returns a dummy `RawMsgChannelOpenInit`, for testing only! 79 | pub fn get_dummy_raw_msg_chan_open_init( 80 | counterparty_channel_id: Option, 81 | ) -> RawMsgChannelOpenInit { 82 | RawMsgChannelOpenInit { 83 | port_id: PortId::default().to_string(), 84 | channel: Some(get_dummy_raw_channel_end(counterparty_channel_id)), 85 | signer: get_dummy_bech32_account(), 86 | } 87 | } 88 | } 89 | 90 | #[cfg(test)] 91 | mod tests { 92 | use crate::prelude::*; 93 | 94 | use super::test_util::*; 95 | use super::*; 96 | 97 | use ibc_proto::ibc::core::channel::v1::MsgChannelOpenInit as RawMsgChannelOpenInit; 98 | use test_log::test; 99 | 100 | #[test] 101 | fn channel_open_init_from_raw() { 102 | struct Test { 103 | name: String, 104 | raw: RawMsgChannelOpenInit, 105 | want_pass: bool, 106 | } 107 | 108 | let default_raw_init_msg = get_dummy_raw_msg_chan_open_init(None); 109 | 110 | let tests: Vec = vec![ 111 | Test { 112 | name: "Good parameters".to_string(), 113 | raw: default_raw_init_msg.clone(), 114 | want_pass: true, 115 | }, 116 | Test { 117 | name: "Incorrect port identifier, slash (separator) prohibited".to_string(), 118 | raw: RawMsgChannelOpenInit { 119 | port_id: "p34/".to_string(), 120 | ..default_raw_init_msg.clone() 121 | }, 122 | want_pass: false, 123 | }, 124 | Test { 125 | name: "Missing channel".to_string(), 126 | raw: RawMsgChannelOpenInit { 127 | channel: None, 128 | ..default_raw_init_msg 129 | }, 130 | want_pass: false, 131 | }, 132 | ] 133 | .into_iter() 134 | .collect(); 135 | 136 | for test in tests { 137 | let res_msg = MsgChannelOpenInit::try_from(test.raw.clone()); 138 | 139 | assert_eq!( 140 | test.want_pass, 141 | res_msg.is_ok(), 142 | "MsgChanOpenInit::try_from failed for test {}, \nraw msg {:?} with error {:?}", 143 | test.name, 144 | test.raw, 145 | res_msg.err(), 146 | ); 147 | } 148 | } 149 | 150 | #[test] 151 | fn to_and_from() { 152 | // Check if raw and domain types are equal after conversions 153 | let raw = get_dummy_raw_msg_chan_open_init(None); 154 | let msg = MsgChannelOpenInit::try_from(raw.clone()).unwrap(); 155 | let raw_back = RawMsgChannelOpenInit::from(msg.clone()); 156 | let msg_back = MsgChannelOpenInit::try_from(raw_back.clone()).unwrap(); 157 | assert_eq!(raw, raw_back); 158 | assert_eq!(msg, msg_back); 159 | 160 | // Check if handler sets counterparty channel id to `None` 161 | // in case relayer passes `MsgChannelOpenInit` message with it set to `Some(_)` 162 | let raw_with_counterpary_chan_id_some = get_dummy_raw_msg_chan_open_init(None); 163 | let msg_with_counterpary_chan_id_some = 164 | MsgChannelOpenInit::try_from(raw_with_counterpary_chan_id_some).unwrap(); 165 | let raw_with_counterpary_chan_id_some_back = 166 | RawMsgChannelOpenInit::from(msg_with_counterpary_chan_id_some.clone()); 167 | let msg_with_counterpary_chan_id_some_back = 168 | MsgChannelOpenInit::try_from(raw_with_counterpary_chan_id_some_back.clone()).unwrap(); 169 | assert_eq!( 170 | raw_with_counterpary_chan_id_some_back 171 | .channel 172 | .unwrap() 173 | .counterparty 174 | .unwrap() 175 | .channel_id, 176 | "".to_string() 177 | ); 178 | assert_eq!( 179 | msg_with_counterpary_chan_id_some, 180 | msg_with_counterpary_chan_id_some_back 181 | ); 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /crates/ibc-types-core-channel/src/msgs/recv_packet.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | 3 | use ibc_types_core_client::Height; 4 | use ibc_types_core_commitment::MerkleProof; 5 | use ibc_types_domain_type::DomainType; 6 | 7 | use crate::{Packet, PacketError}; 8 | 9 | use ibc_proto::ibc::core::channel::v1::MsgRecvPacket as RawMsgRecvPacket; 10 | 11 | /// 12 | /// Message definition for the "packet receiving" datagram. 13 | /// 14 | #[derive(Clone, Debug, PartialEq)] 15 | pub struct MsgRecvPacket { 16 | /// The packet to be received 17 | pub packet: Packet, 18 | /// Proof of packet commitment on the sending chain 19 | pub proof_commitment_on_a: MerkleProof, 20 | /// Height at which the commitment proof in this message were taken 21 | pub proof_height_on_a: Height, 22 | /// The signer of the message 23 | pub signer: String, 24 | } 25 | 26 | impl DomainType for MsgRecvPacket { 27 | type Proto = RawMsgRecvPacket; 28 | } 29 | 30 | impl TryFrom for MsgRecvPacket { 31 | type Error = PacketError; 32 | 33 | fn try_from(raw_msg: RawMsgRecvPacket) -> Result { 34 | if raw_msg.proof_commitment.is_empty() { 35 | return Err(PacketError::InvalidProof); 36 | } 37 | Ok(MsgRecvPacket { 38 | packet: raw_msg 39 | .packet 40 | .ok_or(PacketError::MissingPacket)? 41 | .try_into()?, 42 | proof_commitment_on_a: MerkleProof::decode(raw_msg.proof_commitment.as_ref()) 43 | .map_err(|_| PacketError::InvalidProof)?, 44 | proof_height_on_a: raw_msg 45 | .proof_height 46 | .and_then(|raw_height| raw_height.try_into().ok()) 47 | .ok_or(PacketError::MissingHeight)?, 48 | signer: raw_msg.signer, 49 | }) 50 | } 51 | } 52 | 53 | impl From for RawMsgRecvPacket { 54 | fn from(domain_msg: MsgRecvPacket) -> Self { 55 | RawMsgRecvPacket { 56 | packet: Some(domain_msg.packet.into()), 57 | proof_commitment: domain_msg.proof_commitment_on_a.encode_to_vec(), 58 | proof_height: Some(domain_msg.proof_height_on_a.into()), 59 | signer: domain_msg.signer, 60 | } 61 | } 62 | } 63 | 64 | #[cfg(test)] 65 | pub mod test_util { 66 | use ibc_proto::ibc::core::channel::v1::MsgRecvPacket as RawMsgRecvPacket; 67 | use ibc_proto::ibc::core::client::v1::Height as RawHeight; 68 | 69 | use crate::mocks::{get_dummy_bech32_account, get_dummy_proof}; 70 | use crate::packet::test_utils::get_dummy_raw_packet; 71 | use core::ops::Add; 72 | use core::time::Duration; 73 | use ibc_types_timestamp::Timestamp; 74 | 75 | /// Returns a dummy `RawMsgRecvPacket`, for testing only! The `height` parametrizes both the 76 | /// proof height as well as the timeout height. 77 | pub fn get_dummy_raw_msg_recv_packet(height: u64) -> RawMsgRecvPacket { 78 | let timestamp = Timestamp::now().add(Duration::from_secs(9)); 79 | RawMsgRecvPacket { 80 | packet: Some(get_dummy_raw_packet( 81 | height, 82 | timestamp.unwrap().nanoseconds(), 83 | )), 84 | proof_commitment: get_dummy_proof(), 85 | proof_height: Some(RawHeight { 86 | revision_number: 0, 87 | revision_height: height, 88 | }), 89 | signer: get_dummy_bech32_account(), 90 | } 91 | } 92 | } 93 | 94 | #[cfg(test)] 95 | mod test { 96 | use crate::prelude::*; 97 | 98 | use test_log::test; 99 | 100 | use ibc_proto::ibc::core::channel::v1::MsgRecvPacket as RawMsgRecvPacket; 101 | 102 | use super::test_util::*; 103 | use super::*; 104 | 105 | use crate::mocks::get_dummy_bech32_account; 106 | 107 | #[test] 108 | fn msg_recv_packet_try_from_raw() { 109 | struct Test { 110 | name: String, 111 | raw: RawMsgRecvPacket, 112 | want_pass: bool, 113 | } 114 | 115 | let height = 20; 116 | let default_raw_msg = get_dummy_raw_msg_recv_packet(height); 117 | let tests: Vec = vec![ 118 | Test { 119 | name: "Good parameters".to_string(), 120 | raw: default_raw_msg.clone(), 121 | want_pass: true, 122 | }, 123 | Test { 124 | name: "Missing proof".to_string(), 125 | raw: RawMsgRecvPacket { 126 | proof_commitment: Vec::new(), 127 | ..default_raw_msg.clone() 128 | }, 129 | want_pass: false, 130 | }, 131 | Test { 132 | name: "Missing proof height".to_string(), 133 | raw: RawMsgRecvPacket { 134 | proof_height: None, 135 | ..default_raw_msg.clone() 136 | }, 137 | want_pass: false, 138 | }, 139 | Test { 140 | name: "Empty signer".to_string(), 141 | raw: RawMsgRecvPacket { 142 | signer: get_dummy_bech32_account(), 143 | ..default_raw_msg 144 | }, 145 | want_pass: true, 146 | }, 147 | ]; 148 | 149 | for test in tests { 150 | let res_msg: Result = test.raw.clone().try_into(); 151 | 152 | assert_eq!( 153 | res_msg.is_ok(), 154 | test.want_pass, 155 | "MsgRecvPacket::try_from failed for test {} \nraw message: {:?} with error: {:?}", 156 | test.name, 157 | test.raw, 158 | res_msg.err() 159 | ); 160 | } 161 | } 162 | 163 | #[test] 164 | fn to_and_from() { 165 | let raw = get_dummy_raw_msg_recv_packet(15); 166 | let msg = MsgRecvPacket::try_from(raw.clone()).unwrap(); 167 | let raw_back = RawMsgRecvPacket::from(msg.clone()); 168 | let msg_back = MsgRecvPacket::try_from(raw_back.clone()).unwrap(); 169 | assert_eq!(raw, raw_back); 170 | assert_eq!(msg, msg_back); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /crates/ibc-types-core-channel/src/msgs/timeout.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | 3 | use ibc_proto::ibc::core::channel::v1::MsgTimeout as RawMsgTimeout; 4 | 5 | use ibc_types_core_client::Height; 6 | use ibc_types_core_commitment::MerkleProof; 7 | use ibc_types_domain_type::DomainType; 8 | 9 | use crate::{packet::Sequence, Packet, PacketError}; 10 | 11 | /// 12 | /// Message definition for packet timeout domain type, 13 | /// which is sent on chain A and needs to prove that a previously sent packet was not received on chain B 14 | /// 15 | #[derive(Clone, Debug, PartialEq)] 16 | pub struct MsgTimeout { 17 | pub packet: Packet, 18 | pub next_seq_recv_on_b: Sequence, 19 | pub proof_unreceived_on_b: MerkleProof, 20 | pub proof_height_on_b: Height, 21 | pub signer: String, 22 | } 23 | 24 | impl DomainType for MsgTimeout { 25 | type Proto = RawMsgTimeout; 26 | } 27 | 28 | impl TryFrom for MsgTimeout { 29 | type Error = PacketError; 30 | 31 | fn try_from(raw_msg: RawMsgTimeout) -> Result { 32 | // TODO: Domain type verification for the next sequence: this should probably be > 0. 33 | if raw_msg.proof_unreceived.is_empty() { 34 | return Err(PacketError::InvalidProof); 35 | } 36 | Ok(MsgTimeout { 37 | packet: raw_msg 38 | .packet 39 | .ok_or(PacketError::MissingPacket)? 40 | .try_into()?, 41 | next_seq_recv_on_b: Sequence::from(raw_msg.next_sequence_recv), 42 | proof_unreceived_on_b: MerkleProof::decode(raw_msg.proof_unreceived.as_ref()) 43 | .map_err(|_| PacketError::InvalidProof)?, 44 | proof_height_on_b: raw_msg 45 | .proof_height 46 | .and_then(|raw_height| raw_height.try_into().ok()) 47 | .ok_or(PacketError::MissingHeight)?, 48 | signer: raw_msg.signer, 49 | }) 50 | } 51 | } 52 | 53 | impl From for RawMsgTimeout { 54 | fn from(domain_msg: MsgTimeout) -> Self { 55 | RawMsgTimeout { 56 | packet: Some(domain_msg.packet.into()), 57 | proof_unreceived: domain_msg.proof_unreceived_on_b.encode_to_vec(), 58 | proof_height: Some(domain_msg.proof_height_on_b.into()), 59 | next_sequence_recv: domain_msg.next_seq_recv_on_b.into(), 60 | signer: domain_msg.signer, 61 | } 62 | } 63 | } 64 | 65 | #[cfg(test)] 66 | pub mod test_util { 67 | use ibc_proto::ibc::core::channel::v1::MsgTimeout as RawMsgTimeout; 68 | use ibc_proto::ibc::core::client::v1::Height as RawHeight; 69 | 70 | use crate::mocks::{get_dummy_bech32_account, get_dummy_proof}; 71 | use crate::packet::test_utils::get_dummy_raw_packet; 72 | 73 | /// Returns a dummy `RawMsgTimeout`, for testing only! 74 | /// The `height` parametrizes both the proof height as well as the timeout height. 75 | pub fn get_dummy_raw_msg_timeout( 76 | proof_height: u64, 77 | timeout_height: u64, 78 | timeout_timestamp: u64, 79 | ) -> RawMsgTimeout { 80 | RawMsgTimeout { 81 | packet: Some(get_dummy_raw_packet(timeout_height, timeout_timestamp)), 82 | proof_unreceived: get_dummy_proof(), 83 | proof_height: Some(RawHeight { 84 | revision_number: 0, 85 | revision_height: proof_height, 86 | }), 87 | next_sequence_recv: 1, 88 | signer: get_dummy_bech32_account(), 89 | } 90 | } 91 | } 92 | 93 | #[cfg(test)] 94 | mod test { 95 | 96 | use test_log::test; 97 | 98 | use ibc_proto::ibc::core::channel::v1::MsgTimeout as RawMsgTimeout; 99 | 100 | use super::test_util::*; 101 | use super::*; 102 | use crate::mocks::get_dummy_bech32_account; 103 | 104 | #[test] 105 | fn msg_timeout_try_from_raw() { 106 | struct Test { 107 | name: String, 108 | raw: RawMsgTimeout, 109 | want_pass: bool, 110 | } 111 | 112 | let proof_height = 50; 113 | let timeout_height = proof_height; 114 | let timeout_timestamp = 0; 115 | let default_raw_msg = 116 | get_dummy_raw_msg_timeout(proof_height, timeout_height, timeout_timestamp); 117 | 118 | let tests: Vec = vec![ 119 | Test { 120 | name: "Good parameters".to_string(), 121 | raw: default_raw_msg.clone(), 122 | want_pass: true, 123 | }, 124 | Test { 125 | name: "Missing packet".to_string(), 126 | raw: RawMsgTimeout { 127 | packet: None, 128 | ..default_raw_msg.clone() 129 | }, 130 | want_pass: false, 131 | }, 132 | Test { 133 | name: "Missing proof".to_string(), 134 | raw: RawMsgTimeout { 135 | proof_unreceived: Vec::new(), 136 | ..default_raw_msg.clone() 137 | }, 138 | want_pass: false, 139 | }, 140 | Test { 141 | name: "Missing proof height".to_string(), 142 | raw: RawMsgTimeout { 143 | proof_height: None, 144 | ..default_raw_msg.clone() 145 | }, 146 | want_pass: false, 147 | }, 148 | Test { 149 | name: "Empty signer".to_string(), 150 | raw: RawMsgTimeout { 151 | signer: get_dummy_bech32_account(), 152 | ..default_raw_msg 153 | }, 154 | want_pass: true, 155 | }, 156 | ]; 157 | 158 | for test in tests { 159 | let res_msg: Result = test.raw.clone().try_into(); 160 | 161 | assert_eq!( 162 | res_msg.is_ok(), 163 | test.want_pass, 164 | "MsgTimeout::try_from failed for test {} \nraw message: {:?} with error: {:?}", 165 | test.name, 166 | test.raw, 167 | res_msg.err() 168 | ); 169 | } 170 | } 171 | 172 | #[test] 173 | fn to_and_from() { 174 | let raw = get_dummy_raw_msg_timeout(15, 20, 0); 175 | let msg = MsgTimeout::try_from(raw.clone()).unwrap(); 176 | let raw_back = RawMsgTimeout::from(msg.clone()); 177 | let msg_back = MsgTimeout::try_from(raw_back.clone()).unwrap(); 178 | assert_eq!(raw, raw_back); 179 | assert_eq!(msg, msg_back); 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /crates/ibc-types-core-channel/src/prelude.rs: -------------------------------------------------------------------------------- 1 | pub use core::prelude::v1::*; 2 | 3 | // allow `unused_imports`, rustc errantly claims this `vec!` is not used. 4 | #[allow(unused_imports)] 5 | // Re-export according to alloc::prelude::v1 because it is not yet stabilized 6 | // https://doc.rust-lang.org/src/alloc/prelude/v1.rs.html 7 | pub use alloc::{ 8 | string::{String, ToString}, 9 | vec, 10 | vec::Vec, 11 | }; 12 | 13 | pub use alloc::format; 14 | -------------------------------------------------------------------------------- /crates/ibc-types-core-channel/src/timeout.rs: -------------------------------------------------------------------------------- 1 | use core::{ 2 | fmt::{Display, Error as FmtError, Formatter}, 3 | str::FromStr, 4 | }; 5 | 6 | use ibc_proto::ibc::core::client::v1::Height as RawHeight; 7 | use ibc_types_core_client::{Error as ClientError, Height}; 8 | 9 | use crate::prelude::*; 10 | 11 | /// Indicates a consensus height on the destination chain after which the packet 12 | /// will no longer be processed, and will instead count as having timed-out. 13 | /// 14 | /// `TimeoutHeight` is treated differently from other heights because 15 | /// 16 | /// `RawHeight.timeout_height == {revision_number: 0, revision_height = 0}` 17 | /// 18 | /// is legal and meaningful, even though the Tendermint spec rejects this height 19 | /// as invalid. Thus, it must be parsed specially, where this special case means 20 | /// "no timeout". 21 | #[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)] 22 | pub enum TimeoutHeight { 23 | Never, 24 | At(Height), 25 | } 26 | 27 | impl TimeoutHeight { 28 | pub fn no_timeout() -> Self { 29 | Self::Never 30 | } 31 | 32 | /// Revision number to be used in packet commitment computation 33 | pub fn commitment_revision_number(&self) -> u64 { 34 | match self { 35 | Self::At(height) => height.revision_number(), 36 | Self::Never => 0, 37 | } 38 | } 39 | 40 | /// Revision height to be used in packet commitment computation 41 | pub fn commitment_revision_height(&self) -> u64 { 42 | match self { 43 | Self::At(height) => height.revision_height(), 44 | Self::Never => 0, 45 | } 46 | } 47 | 48 | /// Check if a height is *stricly past* the timeout height, and thus is 49 | /// deemed expired. 50 | pub fn has_expired(&self, height: Height) -> bool { 51 | match self { 52 | Self::At(timeout_height) => height > *timeout_height, 53 | // When there's no timeout, heights are never expired 54 | Self::Never => false, 55 | } 56 | } 57 | 58 | /// Returns a string formatted for an ABCI event attribute value. 59 | pub fn to_event_attribute_value(self) -> String { 60 | match self { 61 | TimeoutHeight::At(height) => height.to_string(), 62 | TimeoutHeight::Never => "0-0".into(), 63 | } 64 | } 65 | } 66 | 67 | impl Display for TimeoutHeight { 68 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { 69 | f.write_str(&self.to_event_attribute_value()) 70 | } 71 | } 72 | 73 | impl FromStr for TimeoutHeight { 74 | type Err = ::Err; 75 | fn from_str(s: &str) -> Result { 76 | if s == "0-0" { 77 | Ok(TimeoutHeight::Never) 78 | } else { 79 | Ok(TimeoutHeight::At(s.parse()?)) 80 | } 81 | } 82 | } 83 | 84 | impl Default for TimeoutHeight { 85 | fn default() -> Self { 86 | Self::Never 87 | } 88 | } 89 | 90 | impl TryFrom for TimeoutHeight { 91 | type Error = ClientError; 92 | 93 | // Note: it is important for `revision_number` to also be `0`, otherwise 94 | // packet commitment proofs will be incorrect (proof construction in 95 | // `ChannelReader::packet_commitment()` uses both `revision_number` and 96 | // `revision_height`). Note also that ibc-go conforms to this convention. 97 | fn try_from(raw_height: RawHeight) -> Result { 98 | if raw_height.revision_number == 0 && raw_height.revision_height == 0 { 99 | Ok(TimeoutHeight::Never) 100 | } else { 101 | let height: Height = raw_height.try_into()?; 102 | Ok(TimeoutHeight::At(height)) 103 | } 104 | } 105 | } 106 | 107 | impl TryFrom> for TimeoutHeight { 108 | type Error = ClientError; 109 | 110 | fn try_from(maybe_raw_height: Option) -> Result { 111 | match maybe_raw_height { 112 | Some(raw_height) => Self::try_from(raw_height), 113 | None => Ok(TimeoutHeight::Never), 114 | } 115 | } 116 | } 117 | /// We map "no timeout height" to `Some(RawHeight::zero)` due to a quirk 118 | /// in ICS-4. See . 119 | impl From for Option { 120 | fn from(timeout_height: TimeoutHeight) -> Self { 121 | let raw_height = match timeout_height { 122 | TimeoutHeight::At(height) => height.into(), 123 | TimeoutHeight::Never => RawHeight { 124 | revision_number: 0, 125 | revision_height: 0, 126 | }, 127 | }; 128 | 129 | Some(raw_height) 130 | } 131 | } 132 | 133 | impl From for TimeoutHeight { 134 | fn from(height: Height) -> Self { 135 | Self::At(height) 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /crates/ibc-types-core-channel/src/version.rs: -------------------------------------------------------------------------------- 1 | //! Data type definition and utilities for the 2 | //! version field of a channel end. 3 | //! 4 | 5 | use core::convert::Infallible; 6 | use core::fmt::{Display, Error as FmtError, Formatter}; 7 | use core::str::FromStr; 8 | 9 | use crate::prelude::*; 10 | 11 | /// The version field for a `ChannelEnd`. 12 | /// 13 | /// This field is opaque to the core IBC protocol. 14 | /// No explicit validation is necessary, and the 15 | /// spec (v1) currently allows empty strings. 16 | #[derive(Clone, Debug, PartialEq, Eq)] 17 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 18 | pub struct Version(pub String); 19 | 20 | impl Version { 21 | pub fn new(v: String) -> Self { 22 | Self(v) 23 | } 24 | 25 | pub fn empty() -> Self { 26 | Self::new("".to_string()) 27 | } 28 | 29 | pub fn is_empty(&self) -> bool { 30 | self.0.is_empty() 31 | } 32 | 33 | pub fn as_str(&self) -> &str { 34 | &self.0 35 | } 36 | } 37 | 38 | impl From for Version { 39 | fn from(s: String) -> Self { 40 | Self::new(s) 41 | } 42 | } 43 | 44 | impl FromStr for Version { 45 | type Err = Infallible; 46 | 47 | fn from_str(s: &str) -> Result { 48 | Ok(Self::new(s.to_string())) 49 | } 50 | } 51 | 52 | /// The default version is empty (unspecified). 53 | impl Default for Version { 54 | fn default() -> Self { 55 | Version::empty() 56 | } 57 | } 58 | 59 | impl Display for Version { 60 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { 61 | write!(f, "{}", self.0) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /crates/ibc-types-core-client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ibc-types-core-client" 3 | version = "0.16.0" 4 | edition = "2021" 5 | license = "Apache-2.0" 6 | readme = "../../README.md" 7 | keywords = ["blockchain", "consensus", "cosmos", "ibc", "tendermint"] 8 | repository = "https://github.com/penumbra-zone/ibc-types" 9 | authors = ["Penumbra Labs Result { 30 | let prefix = client_type.as_str(); 31 | let id = format!("{prefix}-{counter}"); 32 | Self::from_str(id.as_str()) 33 | } 34 | 35 | /// Get this identifier as a borrowed `&str` 36 | pub fn as_str(&self) -> &str { 37 | &self.0 38 | } 39 | 40 | /// Get this identifier as a borrowed byte slice 41 | pub fn as_bytes(&self) -> &[u8] { 42 | self.0.as_bytes() 43 | } 44 | 45 | // TODO: add accessors for counter, client type 46 | } 47 | 48 | /// This implementation provides a `to_string` method. 49 | impl Display for ClientId { 50 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { 51 | write!(f, "{}", self.0) 52 | } 53 | } 54 | 55 | impl FromStr for ClientId { 56 | type Err = IdentifierError; 57 | 58 | fn from_str(s: &str) -> Result { 59 | validate_client_identifier(s).map(|_| Self(s.to_string())) 60 | } 61 | } 62 | 63 | impl TryFrom for ClientId { 64 | type Error = IdentifierError; 65 | 66 | fn try_from(value: String) -> Result { 67 | validate_client_identifier(&value).map(|_| Self(value)) 68 | } 69 | } 70 | 71 | impl Default for ClientId { 72 | fn default() -> Self { 73 | Self("07-tendermint-0".to_string()) 74 | } 75 | } 76 | 77 | /// Equality check against string literal (satisfies &ClientId == &str). 78 | /// ``` 79 | /// # use core::str::FromStr; 80 | /// # use ibc_types_core_client::ClientId; 81 | /// let client_id = ClientId::from_str("clientidtwo"); 82 | /// assert!(client_id.is_ok()); 83 | /// client_id.map(|id| {assert_eq!(&id, "clientidtwo")}); 84 | /// ``` 85 | impl PartialEq for ClientId { 86 | fn eq(&self, other: &str) -> bool { 87 | self.as_str().eq(other) 88 | } 89 | } 90 | 91 | impl PartialEq for str { 92 | fn eq(&self, other: &ClientId) -> bool { 93 | other.as_str().eq(self) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /crates/ibc-types-core-client/src/client_type.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | use core::fmt::{Display, Error as FmtError, Formatter}; 3 | 4 | /// Type of the client, depending on the specific consensus algorithm. 5 | #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 6 | pub struct ClientType(pub String); 7 | 8 | impl ClientType { 9 | pub fn new(s: String) -> Self { 10 | Self(s) 11 | } 12 | 13 | /// Yields this identifier as a borrowed `&str` 14 | pub fn as_str(&self) -> &str { 15 | &self.0 16 | } 17 | } 18 | 19 | impl Display for ClientType { 20 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { 21 | write!(f, "{}", self.0) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /crates/ibc-types-core-client/src/error.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | 3 | use displaydoc::Display; 4 | use ibc_types_identifier::IdentifierError; 5 | use ibc_types_timestamp::Timestamp; 6 | use tendermint_proto::Error as TendermintProtoError; 7 | 8 | use crate::{client_id::ClientId, client_type::ClientType}; 9 | 10 | use crate::height::Height; 11 | 12 | // TODO: after code cleanup, go through and remove never-constructed errors 13 | 14 | /// A catch-all error type. 15 | #[derive(Debug, Display)] 16 | pub enum Error { 17 | /// Client identifier constructor failed for type `{client_type}` with counter `{counter}`, validation error: `{validation_error}` 18 | ClientIdentifierConstructor { 19 | client_type: ClientType, 20 | counter: u64, 21 | validation_error: IdentifierError, 22 | }, 23 | /// client not found: `{client_id}` 24 | ClientNotFound { client_id: ClientId }, 25 | /// client is frozen: `{client_id}` 26 | ClientFrozen { client_id: ClientId }, 27 | /// consensus state not found at: `{client_id}` at height `{height}` 28 | ConsensusStateNotFound { client_id: ClientId, height: Height }, 29 | /// implementation specific error 30 | ImplementationSpecific, 31 | /// header verification failed with reason: `{reason}` 32 | HeaderVerificationFailure { reason: String }, 33 | /// failed to build trust threshold from fraction: `{numerator}`/`{denominator}` 34 | InvalidTrustThreshold { numerator: u64, denominator: u64 }, 35 | /// failed to build Tendermint domain type trust threshold from fraction: `{numerator}`/`{denominator}` 36 | FailedTrustThresholdConversion { numerator: u64, denominator: u64 }, 37 | /// unknown client state type: `{client_state_type}` 38 | UnknownClientStateType { client_state_type: String }, 39 | /// empty prefix 40 | EmptyPrefix, 41 | /// unknown client consensus state type: `{consensus_state_type}` 42 | UnknownConsensusStateType { consensus_state_type: String }, 43 | /// unknown header type: `{header_type}` 44 | UnknownHeaderType { header_type: String }, 45 | /// unknown misbehaviour type: `{misbehaviour_type}` 46 | UnknownMisbehaviourType { misbehaviour_type: String }, 47 | /// missing raw client state 48 | MissingRawClientState, 49 | /// missing raw client consensus state 50 | MissingRawConsensusState, 51 | /// invalid client id in the update client message: `{0}` 52 | InvalidMsgUpdateClientId(IdentifierError), 53 | /// Encode error: `{0}` 54 | Encode(TendermintProtoError), 55 | /// decode error: `{0}` 56 | Decode(prost::DecodeError), 57 | /// invalid client identifier error: `{0}` 58 | InvalidClientIdentifier(IdentifierError), 59 | /// invalid raw header error: `{0}` 60 | InvalidRawHeader(TendermintProtoError), 61 | /// missing raw header 62 | MissingRawHeader, 63 | /// missing raw client message 64 | MissingRawClientMessage, 65 | /// invalid raw misbehaviour error: `{0}` 66 | InvalidRawMisbehaviour(IdentifierError), 67 | /// missing raw misbehaviour 68 | MissingRawMisbehaviour, 69 | /// revision height cannot be zero 70 | InvalidHeight, 71 | /// height cannot end up zero or negative 72 | InvalidHeightResult, 73 | /// invalid proof for the upgraded client state error: `{0}` 74 | InvalidUpgradeClientProof(prost::DecodeError), 75 | /// invalid proof for the upgraded consensus state error: `{0}` 76 | InvalidUpgradeConsensusStateProof(prost::DecodeError), 77 | /// invalid packet timeout timestamp value error: `{0}` 78 | InvalidPacketTimestamp(ibc_types_timestamp::ParseTimestampError), 79 | /// mismatch between client and arguments types 80 | ClientArgsTypeMismatch { client_type: ClientType }, 81 | /// received header height (`{header_height}`) is lower than (or equal to) client latest height (`{latest_height}`) 82 | LowHeaderHeight { 83 | header_height: Height, 84 | latest_height: Height, 85 | }, 86 | /// upgraded client height `{upgraded_height}` must be at greater than current client height `{client_height}` 87 | LowUpgradeHeight { 88 | upgraded_height: Height, 89 | client_height: Height, 90 | }, 91 | /// timestamp is invalid or missing, timestamp=`{time1}`, now=`{time2}` 92 | InvalidConsensusStateTimestamp { time1: Timestamp, time2: Timestamp }, 93 | /// header not within trusting period: expires_at=`{latest_time}` now=`{update_time}` 94 | HeaderNotWithinTrustPeriod { 95 | latest_time: Timestamp, 96 | update_time: Timestamp, 97 | }, 98 | /// the local consensus state could not be retrieved for height `{height}` 99 | MissingLocalConsensusState { height: Height }, 100 | /// invalid connection end error: `{0}` 101 | InvalidConnectionEnd(TendermintProtoError), 102 | /// invalid channel end error: `{0}` 103 | InvalidChannelEnd(TendermintProtoError), 104 | /// invalid any client consensus state error: `{0}` 105 | InvalidAnyConsensusState(TendermintProtoError), 106 | /// misbehaviour handling failed with reason: `{reason}` 107 | MisbehaviourHandlingFailure { reason: String }, 108 | /// client specific error: `{description}` 109 | ClientSpecific { description: String }, 110 | /// other error: `{description}` 111 | Other { description: String }, 112 | } 113 | 114 | #[cfg(feature = "std")] 115 | impl std::error::Error for Error { 116 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 117 | match &self { 118 | Self::ClientIdentifierConstructor { 119 | validation_error: e, 120 | .. 121 | } => Some(e), 122 | Self::InvalidMsgUpdateClientId(e) => Some(e), 123 | Self::InvalidClientIdentifier(e) => Some(e), 124 | Self::InvalidRawHeader(e) => Some(e), 125 | Self::InvalidRawMisbehaviour(e) => Some(e), 126 | Self::InvalidUpgradeClientProof(e) => Some(e), 127 | Self::InvalidUpgradeConsensusStateProof(e) => Some(e), 128 | Self::InvalidPacketTimestamp(e) => Some(e), 129 | Self::InvalidConnectionEnd(e) => Some(e), 130 | Self::InvalidChannelEnd(e) => Some(e), 131 | Self::InvalidAnyConsensusState(e) => Some(e), 132 | _ => None, 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /crates/ibc-types-core-client/src/height.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | use core::cmp::Ordering; 3 | 4 | use core::{num::ParseIntError, str::FromStr}; 5 | 6 | use displaydoc::Display; 7 | use ibc_proto::Protobuf; 8 | 9 | use ibc_proto::ibc::core::client::v1::Height as RawHeight; 10 | 11 | use crate::error::Error; 12 | 13 | /// An IBC height, containing a revision number (epoch) and a revision height (block height). 14 | #[derive(Copy, Clone, PartialEq, Eq, Hash)] 15 | #[cfg_attr( 16 | feature = "with_serde", 17 | derive(serde::Serialize, serde::Deserialize), 18 | serde(try_from = "RawHeight", into = "RawHeight") 19 | )] 20 | pub struct Height { 21 | /// Previously known as "epoch" 22 | pub revision_number: u64, 23 | 24 | /// The height of a block 25 | pub revision_height: u64, 26 | } 27 | 28 | impl Height { 29 | pub fn new(revision_number: u64, revision_height: u64) -> Result { 30 | if revision_height == 0 { 31 | return Err(Error::InvalidHeight); 32 | } 33 | 34 | Ok(Self { 35 | revision_number, 36 | revision_height, 37 | }) 38 | } 39 | 40 | pub fn revision_number(&self) -> u64 { 41 | self.revision_number 42 | } 43 | 44 | pub fn revision_height(&self) -> u64 { 45 | self.revision_height 46 | } 47 | 48 | pub fn add(&self, delta: u64) -> Height { 49 | Height { 50 | revision_number: self.revision_number, 51 | revision_height: self.revision_height + delta, 52 | } 53 | } 54 | 55 | pub fn increment(&self) -> Height { 56 | self.add(1) 57 | } 58 | 59 | pub fn sub(&self, delta: u64) -> Result { 60 | if self.revision_height <= delta { 61 | return Err(Error::InvalidHeightResult); 62 | } 63 | 64 | Ok(Height { 65 | revision_number: self.revision_number, 66 | revision_height: self.revision_height - delta, 67 | }) 68 | } 69 | 70 | pub fn decrement(&self) -> Result { 71 | self.sub(1) 72 | } 73 | } 74 | 75 | impl PartialOrd for Height { 76 | fn partial_cmp(&self, other: &Self) -> Option { 77 | Some(self.cmp(other)) 78 | } 79 | } 80 | 81 | impl Ord for Height { 82 | fn cmp(&self, other: &Self) -> Ordering { 83 | if self.revision_number < other.revision_number { 84 | Ordering::Less 85 | } else if self.revision_number > other.revision_number { 86 | Ordering::Greater 87 | } else if self.revision_height < other.revision_height { 88 | Ordering::Less 89 | } else if self.revision_height > other.revision_height { 90 | Ordering::Greater 91 | } else { 92 | Ordering::Equal 93 | } 94 | } 95 | } 96 | 97 | impl Protobuf for Height {} 98 | 99 | impl TryFrom for Height { 100 | type Error = Error; 101 | 102 | fn try_from(raw_height: RawHeight) -> Result { 103 | Height::new(raw_height.revision_number, raw_height.revision_height) 104 | } 105 | } 106 | 107 | impl From for RawHeight { 108 | fn from(ics_height: Height) -> Self { 109 | RawHeight { 110 | revision_number: ics_height.revision_number, 111 | revision_height: ics_height.revision_height, 112 | } 113 | } 114 | } 115 | 116 | impl core::fmt::Debug for Height { 117 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { 118 | f.debug_struct("Height") 119 | .field("revision", &self.revision_number) 120 | .field("height", &self.revision_height) 121 | .finish() 122 | } 123 | } 124 | 125 | /// Custom debug output to omit the packet data 126 | impl core::fmt::Display for Height { 127 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { 128 | write!(f, "{}-{}", self.revision_number, self.revision_height) 129 | } 130 | } 131 | 132 | /// An error while parsing a [`Height`]. 133 | #[derive(Debug, Display)] 134 | pub enum HeightParseError { 135 | /// cannot convert into a `Height` type from string `{height}` 136 | HeightConversion { 137 | height: String, 138 | error: ParseIntError, 139 | }, 140 | /// attempted to parse a height with invalid format (not in the form `revision_number-revision_height`) 141 | InvalidFormat, 142 | /// attempted to parse an invalid zero height 143 | ZeroHeight, 144 | } 145 | 146 | #[cfg(feature = "std")] 147 | impl std::error::Error for HeightParseError { 148 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 149 | match &self { 150 | HeightParseError::HeightConversion { error: e, .. } => Some(e), 151 | HeightParseError::ZeroHeight => None, 152 | HeightParseError::InvalidFormat => None, 153 | } 154 | } 155 | } 156 | 157 | impl TryFrom<&str> for Height { 158 | type Error = HeightParseError; 159 | 160 | fn try_from(value: &str) -> Result { 161 | let split: vec::Vec<&str> = value.split('-').collect(); 162 | 163 | if split.len() != 2 { 164 | return Err(HeightParseError::InvalidFormat); 165 | } 166 | 167 | let revision_number = 168 | split[0] 169 | .parse::() 170 | .map_err(|e| HeightParseError::HeightConversion { 171 | height: value.to_owned(), 172 | error: e, 173 | })?; 174 | let revision_height = 175 | split[1] 176 | .parse::() 177 | .map_err(|e| HeightParseError::HeightConversion { 178 | height: value.to_owned(), 179 | error: e, 180 | })?; 181 | 182 | Height::new(revision_number, revision_height).map_err(|_| HeightParseError::ZeroHeight) 183 | } 184 | } 185 | 186 | impl From for String { 187 | fn from(height: Height) -> Self { 188 | format!("{}-{}", height.revision_number, height.revision_height) 189 | } 190 | } 191 | 192 | impl FromStr for Height { 193 | type Err = HeightParseError; 194 | 195 | fn from_str(s: &str) -> Result { 196 | Height::try_from(s) 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /crates/ibc-types-core-client/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! IBC client-related types. 2 | #![no_std] 3 | // Requires nightly. 4 | #![cfg_attr(docsrs, feature(doc_auto_cfg))] 5 | 6 | extern crate alloc; 7 | #[cfg(any(test, feature = "std"))] 8 | extern crate std; 9 | 10 | mod client_id; 11 | mod client_type; 12 | mod error; 13 | mod height; 14 | 15 | mod prelude; 16 | 17 | pub mod events; 18 | pub mod msgs; 19 | 20 | pub use client_id::ClientId; 21 | pub use client_type::ClientType; 22 | pub use error::Error; 23 | pub use height::{Height, HeightParseError}; 24 | 25 | #[cfg(any(test, feature = "mocks", feature = "mocks-no-std"))] 26 | pub mod mock; 27 | -------------------------------------------------------------------------------- /crates/ibc-types-core-client/src/mock/client_state.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | 3 | use core::time::Duration; 4 | 5 | use ibc_proto::{google::protobuf::Any, ibc::mock::ClientState as RawMockClientState, Protobuf}; 6 | 7 | use crate::{error::Error, mock::header::MockHeader, ClientType, Height}; 8 | 9 | pub const MOCK_CLIENT_STATE_TYPE_URL: &str = "/ibc.mock.ClientState"; 10 | 11 | pub const MOCK_CLIENT_TYPE: &str = "9999-mock"; 12 | 13 | pub fn client_type() -> ClientType { 14 | ClientType::new(MOCK_CLIENT_TYPE.to_string()) 15 | } 16 | 17 | /// A mock of a client state. For an example of a real structure that this mocks, you can see 18 | /// `ClientState` of ics07_tendermint/client_state.rs. 19 | 20 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 21 | pub struct MockClientState { 22 | pub header: MockHeader, 23 | pub frozen_height: Option, 24 | pub trusting_period: Duration, 25 | } 26 | 27 | impl MockClientState { 28 | pub fn new(header: MockHeader) -> Self { 29 | Self { 30 | header, 31 | frozen_height: None, 32 | trusting_period: Duration::from_secs(64000), 33 | } 34 | } 35 | 36 | pub fn latest_height(&self) -> Height { 37 | self.header.height() 38 | } 39 | 40 | pub fn refresh_time(&self) -> Option { 41 | None 42 | } 43 | 44 | pub fn with_frozen_height(self, frozen_height: Height) -> Self { 45 | Self { 46 | frozen_height: Some(frozen_height), 47 | ..self 48 | } 49 | } 50 | 51 | pub fn unfrozen(self) -> Self { 52 | Self { 53 | frozen_height: None, 54 | ..self 55 | } 56 | } 57 | } 58 | 59 | impl Protobuf for MockClientState {} 60 | 61 | impl TryFrom for MockClientState { 62 | type Error = Error; 63 | 64 | fn try_from(raw: RawMockClientState) -> Result { 65 | Ok(Self::new(raw.header.unwrap().try_into()?)) 66 | } 67 | } 68 | 69 | impl From for RawMockClientState { 70 | fn from(value: MockClientState) -> Self { 71 | RawMockClientState { 72 | header: Some(ibc_proto::ibc::mock::Header { 73 | height: Some(value.header.height().into()), 74 | timestamp: value.header.timestamp.nanoseconds(), 75 | }), 76 | trusting_period: value.trusting_period.as_secs(), 77 | frozen: value.frozen_height.is_some(), 78 | } 79 | } 80 | } 81 | 82 | impl Protobuf for MockClientState {} 83 | 84 | impl TryFrom for MockClientState { 85 | type Error = Error; 86 | 87 | fn try_from(raw: Any) -> Result { 88 | use bytes::Buf; 89 | use core::ops::Deref; 90 | use prost::Message; 91 | 92 | fn decode_client_state(buf: B) -> Result { 93 | RawMockClientState::decode(buf) 94 | .map_err(Error::Decode)? 95 | .try_into() 96 | } 97 | 98 | match raw.type_url.as_str() { 99 | MOCK_CLIENT_STATE_TYPE_URL => { 100 | decode_client_state(raw.value.deref()).map_err(Into::into) 101 | } 102 | _ => Err(Error::UnknownClientStateType { 103 | client_state_type: raw.type_url, 104 | }), 105 | } 106 | } 107 | } 108 | 109 | impl From for Any { 110 | fn from(client_state: MockClientState) -> Self { 111 | Any { 112 | type_url: MOCK_CLIENT_STATE_TYPE_URL.to_string(), 113 | value: Protobuf::::encode_vec(client_state), 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /crates/ibc-types-core-client/src/mock/consensus_state.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | use alloc::vec; 3 | 4 | use ibc_proto::{ 5 | google::protobuf::Any, ibc::mock::ConsensusState as RawMockConsensusState, Protobuf, 6 | }; 7 | use ibc_types_timestamp::Timestamp; 8 | 9 | use crate::mock::header::MockHeader; 10 | 11 | use crate::error::Error; 12 | 13 | pub const MOCK_CONSENSUS_STATE_TYPE_URL: &str = "/ibc.mock.ConsensusState"; 14 | 15 | #[derive(Clone, Debug, PartialEq, Eq)] 16 | pub struct MockConsensusState { 17 | pub header: MockHeader, 18 | pub root: vec::Vec, 19 | } 20 | 21 | impl MockConsensusState { 22 | pub fn new(header: MockHeader) -> Self { 23 | MockConsensusState { 24 | header, 25 | root: vec![0], 26 | } 27 | } 28 | 29 | pub fn timestamp(&self) -> Timestamp { 30 | self.header.timestamp 31 | } 32 | } 33 | 34 | impl Protobuf for MockConsensusState {} 35 | 36 | impl TryFrom for MockConsensusState { 37 | type Error = Error; 38 | 39 | fn try_from(raw: RawMockConsensusState) -> Result { 40 | let raw_header = raw.header.ok_or(Error::MissingRawConsensusState)?; 41 | 42 | Ok(Self { 43 | header: MockHeader::try_from(raw_header)?, 44 | root: vec![0], 45 | }) 46 | } 47 | } 48 | 49 | impl From for RawMockConsensusState { 50 | fn from(value: MockConsensusState) -> Self { 51 | RawMockConsensusState { 52 | header: Some(ibc_proto::ibc::mock::Header { 53 | height: Some(value.header.height().into()), 54 | timestamp: value.header.timestamp.nanoseconds(), 55 | }), 56 | } 57 | } 58 | } 59 | 60 | impl Protobuf for MockConsensusState {} 61 | 62 | impl TryFrom for MockConsensusState { 63 | type Error = Error; 64 | 65 | fn try_from(raw: Any) -> Result { 66 | use bytes::Buf; 67 | use core::ops::Deref; 68 | use prost::Message; 69 | 70 | fn decode_consensus_state(buf: B) -> Result { 71 | RawMockConsensusState::decode(buf) 72 | .map_err(Error::Decode)? 73 | .try_into() 74 | } 75 | 76 | match raw.type_url.as_str() { 77 | MOCK_CONSENSUS_STATE_TYPE_URL => { 78 | decode_consensus_state(raw.value.deref()).map_err(Into::into) 79 | } 80 | _ => Err(Error::UnknownConsensusStateType { 81 | consensus_state_type: raw.type_url, 82 | }), 83 | } 84 | } 85 | } 86 | 87 | impl From for Any { 88 | fn from(consensus_state: MockConsensusState) -> Self { 89 | Any { 90 | type_url: MOCK_CONSENSUS_STATE_TYPE_URL.to_string(), 91 | value: Protobuf::::encode_vec(consensus_state), 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /crates/ibc-types-core-client/src/mock/header.rs: -------------------------------------------------------------------------------- 1 | use alloc::string::ToString; 2 | use core::fmt::{Display, Error as FmtError, Formatter}; 3 | 4 | use ibc_proto::{google::protobuf::Any, ibc::mock::Header as RawMockHeader, Protobuf}; 5 | use ibc_types_timestamp::Timestamp; 6 | 7 | use crate::{error::Error, Height}; 8 | 9 | pub const MOCK_HEADER_TYPE_URL: &str = "/ibc.mock.Header"; 10 | 11 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 12 | pub struct MockHeader { 13 | pub height: Height, 14 | pub timestamp: Timestamp, 15 | } 16 | 17 | impl Default for MockHeader { 18 | fn default() -> Self { 19 | Self { 20 | height: Height::new(0, 1).unwrap(), 21 | timestamp: Default::default(), 22 | } 23 | } 24 | } 25 | 26 | impl Display for MockHeader { 27 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { 28 | write!( 29 | f, 30 | "MockHeader {{ height: {}, timestamp: {} }}", 31 | self.height, self.timestamp 32 | ) 33 | } 34 | } 35 | 36 | impl Protobuf for MockHeader {} 37 | 38 | impl TryFrom for MockHeader { 39 | type Error = Error; 40 | 41 | fn try_from(raw: RawMockHeader) -> Result { 42 | Ok(MockHeader { 43 | height: raw 44 | .height 45 | .and_then(|raw_height| raw_height.try_into().ok()) 46 | .ok_or(Error::MissingRawHeader)?, 47 | 48 | timestamp: Timestamp::from_nanoseconds(raw.timestamp) 49 | .map_err(Error::InvalidPacketTimestamp)?, 50 | }) 51 | } 52 | } 53 | 54 | impl From for RawMockHeader { 55 | fn from(value: MockHeader) -> Self { 56 | RawMockHeader { 57 | height: Some(value.height.into()), 58 | timestamp: value.timestamp.nanoseconds(), 59 | } 60 | } 61 | } 62 | 63 | impl MockHeader { 64 | pub fn height(&self) -> Height { 65 | self.height 66 | } 67 | 68 | pub fn new(height: Height) -> Self { 69 | cfg_if::cfg_if! { 70 | if #[cfg(any(test, feature = "std"))] { 71 | Self { 72 | height, 73 | timestamp: Timestamp::now(), 74 | } 75 | } else { 76 | Self { 77 | height, 78 | timestamp: Timestamp::none(), 79 | } 80 | } 81 | } 82 | } 83 | 84 | pub fn with_timestamp(self, timestamp: Timestamp) -> Self { 85 | Self { timestamp, ..self } 86 | } 87 | } 88 | 89 | impl Protobuf for MockHeader {} 90 | 91 | impl TryFrom for MockHeader { 92 | type Error = Error; 93 | 94 | fn try_from(raw: Any) -> Result { 95 | match raw.type_url.as_str() { 96 | MOCK_HEADER_TYPE_URL => Ok(Protobuf::::decode_vec(&raw.value) 97 | .map_err(Error::InvalidRawHeader)?), 98 | _ => Err(Error::UnknownHeaderType { 99 | header_type: raw.type_url, 100 | }), 101 | } 102 | } 103 | } 104 | 105 | impl From for Any { 106 | fn from(header: MockHeader) -> Self { 107 | Any { 108 | type_url: MOCK_HEADER_TYPE_URL.to_string(), 109 | value: Protobuf::::encode_vec(header), 110 | } 111 | } 112 | } 113 | 114 | #[cfg(test)] 115 | mod tests { 116 | use super::*; 117 | use ibc_proto::Protobuf; 118 | 119 | #[test] 120 | fn encode_any() { 121 | let header = MockHeader::new(Height::new(1, 10).unwrap()).with_timestamp(Timestamp::none()); 122 | let bytes = >::encode_vec(header); 123 | 124 | assert_eq!( 125 | &bytes, 126 | &[ 127 | 10, 16, 47, 105, 98, 99, 46, 109, 111, 99, 107, 46, 72, 101, 97, 100, 101, 114, 18, 128 | 6, 10, 4, 8, 1, 16, 10 129 | ] 130 | ); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /crates/ibc-types-core-client/src/mock/misbehaviour.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | 3 | use bytes::Buf; 4 | use ibc_proto::{google::protobuf::Any, ibc::mock::Misbehaviour as RawMisbehaviour, Protobuf}; 5 | 6 | use crate::{error::Error, ClientId}; 7 | 8 | use super::header::MockHeader; 9 | 10 | pub const MOCK_MISBEHAVIOUR_TYPE_URL: &str = "/ibc.mock.Misbehavior"; 11 | 12 | #[derive(Clone, Debug, PartialEq, Eq)] 13 | pub struct Misbehaviour { 14 | pub client_id: ClientId, 15 | pub header1: MockHeader, 16 | pub header2: MockHeader, 17 | } 18 | 19 | impl Protobuf for Misbehaviour {} 20 | 21 | impl TryFrom for Misbehaviour { 22 | type Error = Error; 23 | 24 | fn try_from(raw: RawMisbehaviour) -> Result { 25 | Ok(Self { 26 | client_id: Default::default(), 27 | header1: raw 28 | .header1 29 | .ok_or(Error::MissingRawMisbehaviour)? 30 | .try_into()?, 31 | header2: raw 32 | .header2 33 | .ok_or(Error::MissingRawMisbehaviour)? 34 | .try_into()?, 35 | }) 36 | } 37 | } 38 | 39 | impl From for RawMisbehaviour { 40 | fn from(value: Misbehaviour) -> Self { 41 | RawMisbehaviour { 42 | client_id: value.client_id.to_string(), 43 | header1: Some(value.header1.into()), 44 | header2: Some(value.header2.into()), 45 | } 46 | } 47 | } 48 | 49 | impl Protobuf for Misbehaviour {} 50 | 51 | impl TryFrom for Misbehaviour { 52 | type Error = Error; 53 | 54 | fn try_from(raw: Any) -> Result { 55 | use core::ops::Deref; 56 | 57 | fn decode_misbehaviour(buf: B) -> Result { 58 | use prost::Message; 59 | 60 | RawMisbehaviour::decode(buf) 61 | .map_err(Error::Decode)? 62 | .try_into() 63 | } 64 | 65 | match raw.type_url.as_str() { 66 | MOCK_MISBEHAVIOUR_TYPE_URL => { 67 | decode_misbehaviour(raw.value.deref()).map_err(Into::into) 68 | } 69 | _ => Err(Error::UnknownMisbehaviourType { 70 | misbehaviour_type: raw.type_url, 71 | }), 72 | } 73 | } 74 | } 75 | 76 | impl From for Any { 77 | fn from(misbehaviour: Misbehaviour) -> Self { 78 | Any { 79 | type_url: MOCK_MISBEHAVIOUR_TYPE_URL.to_string(), 80 | value: Protobuf::::encode_vec(misbehaviour), 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /crates/ibc-types-core-client/src/mock/mod.rs: -------------------------------------------------------------------------------- 1 | //! Implementation of mocks for context, host chain, and client. 2 | 3 | #[cfg(any(test, feature = "mocks", feature = "mocks-no-std"))] 4 | pub mod client_state; 5 | #[cfg(any(test, feature = "mocks", feature = "mocks-no-std"))] 6 | pub mod consensus_state; 7 | #[cfg(any(test, feature = "mocks", feature = "mocks-no-std"))] 8 | pub mod header; 9 | #[cfg(any(test, feature = "mocks", feature = "mocks-no-std"))] 10 | pub mod misbehaviour; 11 | -------------------------------------------------------------------------------- /crates/ibc-types-core-client/src/msgs.rs: -------------------------------------------------------------------------------- 1 | //! Definitions of client-related messages that a relayer submits to a chain. 2 | //! 3 | //! Specific implementations of these messages can be found, for instance, in 4 | //! ICS 07 for Tendermint-specific chains. A chain handles these messages in two 5 | //! layers: first with the general ICS 02 client handler, which subsequently 6 | //! calls into the chain-specific (e.g., ICS 07) client handler. See: 7 | //! . 8 | 9 | mod create_client; 10 | mod misbehaviour; 11 | mod update_client; 12 | mod upgrade_client; 13 | 14 | pub use create_client::MsgCreateClient; 15 | pub use misbehaviour::MsgSubmitMisbehaviour; 16 | pub use update_client::MsgUpdateClient; 17 | pub use upgrade_client::MsgUpgradeClient; 18 | 19 | #[derive(Clone, Debug)] 20 | pub enum ClientMsg { 21 | CreateClient(MsgCreateClient), 22 | UpdateClient(MsgUpdateClient), 23 | Misbehaviour(MsgSubmitMisbehaviour), 24 | UpgradeClient(MsgUpgradeClient), 25 | } 26 | -------------------------------------------------------------------------------- /crates/ibc-types-core-client/src/msgs/create_client.rs: -------------------------------------------------------------------------------- 1 | //! Definition of domain type message `MsgCreateClient`. 2 | 3 | use crate::prelude::*; 4 | 5 | use ibc_proto::{ 6 | google::protobuf::Any, ibc::core::client::v1::MsgCreateClient as RawMsgCreateClient, 7 | }; 8 | use ibc_types_domain_type::DomainType; 9 | 10 | use crate::error::Error; 11 | 12 | /// A type of message that triggers the creation of a new on-chain (IBC) client. 13 | #[derive(Clone, Debug, PartialEq, Eq)] 14 | pub struct MsgCreateClient { 15 | pub client_state: Any, 16 | pub consensus_state: Any, 17 | pub signer: String, 18 | } 19 | 20 | impl DomainType for MsgCreateClient { 21 | type Proto = RawMsgCreateClient; 22 | } 23 | 24 | impl TryFrom for MsgCreateClient { 25 | type Error = Error; 26 | 27 | fn try_from(raw: RawMsgCreateClient) -> Result { 28 | let client_state = raw.client_state.ok_or(Error::MissingRawClientState)?; 29 | 30 | let consensus_state = raw.consensus_state.ok_or(Error::MissingRawConsensusState)?; 31 | 32 | Ok(MsgCreateClient { 33 | client_state, 34 | consensus_state, 35 | signer: raw.signer, 36 | }) 37 | } 38 | } 39 | 40 | impl From for RawMsgCreateClient { 41 | fn from(ics_msg: MsgCreateClient) -> Self { 42 | RawMsgCreateClient { 43 | client_state: Some(ics_msg.client_state), 44 | consensus_state: Some(ics_msg.consensus_state), 45 | signer: ics_msg.signer, 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /crates/ibc-types-core-client/src/msgs/misbehaviour.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | 3 | use ibc_proto::{ 4 | google::protobuf::Any as ProtoAny, 5 | ibc::core::client::v1::MsgSubmitMisbehaviour as RawMsgSubmitMisbehaviour, 6 | }; 7 | use ibc_types_domain_type::DomainType; 8 | 9 | use crate::{error::Error, ClientId}; 10 | 11 | /// A type of message that submits client misbehaviour proof. 12 | #[derive(Clone, Debug, PartialEq, Eq)] 13 | pub struct MsgSubmitMisbehaviour { 14 | /// client unique identifier 15 | pub client_id: ClientId, 16 | /// misbehaviour used for freezing the light client 17 | pub misbehaviour: ProtoAny, 18 | /// signer address 19 | pub signer: String, 20 | } 21 | 22 | impl DomainType for MsgSubmitMisbehaviour { 23 | type Proto = RawMsgSubmitMisbehaviour; 24 | } 25 | 26 | impl TryFrom for MsgSubmitMisbehaviour { 27 | type Error = Error; 28 | 29 | #[allow(deprecated)] 30 | fn try_from(raw: RawMsgSubmitMisbehaviour) -> Result { 31 | let raw_misbehaviour = raw.misbehaviour.ok_or(Error::MissingRawMisbehaviour)?; 32 | 33 | Ok(MsgSubmitMisbehaviour { 34 | client_id: raw 35 | .client_id 36 | .parse() 37 | .map_err(Error::InvalidRawMisbehaviour)?, 38 | misbehaviour: raw_misbehaviour, 39 | signer: raw.signer, 40 | }) 41 | } 42 | } 43 | 44 | impl From for RawMsgSubmitMisbehaviour { 45 | #[allow(deprecated)] 46 | fn from(ics_msg: MsgSubmitMisbehaviour) -> Self { 47 | RawMsgSubmitMisbehaviour { 48 | client_id: ics_msg.client_id.to_string(), 49 | misbehaviour: Some(ics_msg.misbehaviour), 50 | signer: ics_msg.signer, 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /crates/ibc-types-core-client/src/msgs/update_client.rs: -------------------------------------------------------------------------------- 1 | //! Definition of domain type message `MsgUpdateAnyClient`. 2 | 3 | use crate::prelude::*; 4 | 5 | use ibc_proto::{ 6 | google::protobuf::Any, ibc::core::client::v1::MsgUpdateClient as RawMsgUpdateClient, 7 | }; 8 | use ibc_types_domain_type::DomainType; 9 | 10 | use crate::{error::Error, ClientId}; 11 | 12 | /// A type of message that triggers the update of an on-chain (IBC) client with new headers. 13 | #[derive(Clone, Debug, PartialEq, Eq)] 14 | pub struct MsgUpdateClient { 15 | pub client_id: ClientId, 16 | pub client_message: Any, 17 | pub signer: String, 18 | } 19 | 20 | impl DomainType for MsgUpdateClient { 21 | type Proto = RawMsgUpdateClient; 22 | } 23 | 24 | impl TryFrom for MsgUpdateClient { 25 | type Error = Error; 26 | 27 | fn try_from(raw: RawMsgUpdateClient) -> Result { 28 | Ok(MsgUpdateClient { 29 | client_id: raw 30 | .client_id 31 | .parse() 32 | .map_err(Error::InvalidMsgUpdateClientId)?, 33 | client_message: raw.client_message.ok_or(Error::MissingRawClientMessage)?, 34 | signer: raw.signer, 35 | }) 36 | } 37 | } 38 | 39 | impl From for RawMsgUpdateClient { 40 | fn from(ics_msg: MsgUpdateClient) -> Self { 41 | RawMsgUpdateClient { 42 | client_id: ics_msg.client_id.to_string(), 43 | client_message: Some(ics_msg.client_message), 44 | signer: ics_msg.signer, 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /crates/ibc-types-core-client/src/msgs/upgrade_client.rs: -------------------------------------------------------------------------------- 1 | //! Definition of domain type msg `MsgUpgradeAnyClient`. 2 | 3 | use crate::prelude::*; 4 | 5 | use core::str::FromStr; 6 | 7 | use ibc_proto::{ 8 | google::protobuf::Any, 9 | ibc::core::{ 10 | client::v1::MsgUpgradeClient as RawMsgUpgradeClient, 11 | commitment::v1::MerkleProof as RawMerkleProof, 12 | }, 13 | }; 14 | use ibc_types_domain_type::DomainType; 15 | use prost::Message; 16 | 17 | use crate::{error::Error, ClientId}; 18 | 19 | /// A type of message that triggers the upgrade of an on-chain (IBC) client. 20 | #[derive(Clone, Debug, PartialEq)] 21 | pub struct MsgUpgradeClient { 22 | // client unique identifier 23 | pub client_id: ClientId, 24 | // Upgraded client state 25 | pub client_state: Any, 26 | // Upgraded consensus state, only contains enough information 27 | // to serve as a basis of trust in update logic 28 | pub consensus_state: Any, 29 | // proof that old chain committed to new client 30 | pub proof_upgrade_client: RawMerkleProof, 31 | // proof that old chain committed to new consensus state 32 | pub proof_upgrade_consensus_state: RawMerkleProof, 33 | // signer address 34 | pub signer: String, 35 | } 36 | 37 | impl DomainType for MsgUpgradeClient { 38 | type Proto = RawMsgUpgradeClient; 39 | } 40 | 41 | impl From for RawMsgUpgradeClient { 42 | fn from(dm_msg: MsgUpgradeClient) -> RawMsgUpgradeClient { 43 | RawMsgUpgradeClient { 44 | client_id: dm_msg.client_id.to_string(), 45 | client_state: Some(dm_msg.client_state), 46 | consensus_state: Some(dm_msg.consensus_state), 47 | proof_upgrade_client: dm_msg.proof_upgrade_client.encode_to_vec(), 48 | proof_upgrade_consensus_state: dm_msg.proof_upgrade_consensus_state.encode_to_vec(), 49 | signer: dm_msg.signer, 50 | } 51 | } 52 | } 53 | 54 | impl TryFrom for MsgUpgradeClient { 55 | type Error = Error; 56 | 57 | fn try_from(proto_msg: RawMsgUpgradeClient) -> Result { 58 | let raw_client_state = proto_msg.client_state.ok_or(Error::MissingRawClientState)?; 59 | 60 | let raw_consensus_state = proto_msg 61 | .consensus_state 62 | .ok_or(Error::MissingRawConsensusState)?; 63 | 64 | Ok(MsgUpgradeClient { 65 | client_id: ClientId::from_str(&proto_msg.client_id) 66 | .map_err(Error::InvalidClientIdentifier)?, 67 | client_state: raw_client_state, 68 | consensus_state: raw_consensus_state, 69 | proof_upgrade_client: RawMerkleProof::decode(proto_msg.proof_upgrade_client.as_ref()) 70 | .map_err(Error::InvalidUpgradeClientProof)?, 71 | proof_upgrade_consensus_state: RawMerkleProof::decode( 72 | proto_msg.proof_upgrade_consensus_state.as_ref(), 73 | ) 74 | .map_err(Error::InvalidUpgradeConsensusStateProof)?, 75 | signer: proto_msg.signer, 76 | }) 77 | } 78 | } 79 | 80 | #[cfg(test)] 81 | mod tests { 82 | use super::*; 83 | 84 | use crate::Height; 85 | 86 | use crate::mock::{ 87 | client_state::MockClientState, consensus_state::MockConsensusState, header::MockHeader, 88 | }; 89 | 90 | /// Returns a dummy `RawMerkleProof`, for testing only! 91 | pub fn get_dummy_merkle_proof() -> RawMerkleProof { 92 | use ibc_proto::ics23::CommitmentProof; 93 | 94 | let parsed = CommitmentProof { proof: None }; 95 | let mproofs: vec::Vec = vec![parsed]; 96 | RawMerkleProof { proofs: mproofs } 97 | } 98 | 99 | /* 100 | pub fn get_dummy_proof() -> Vec { 101 | "Y29uc2Vuc3VzU3RhdGUvaWJjb25lY2xpZW50LzIy" 102 | .as_bytes() 103 | .to_vec() 104 | } 105 | 106 | /// Returns a dummy `RawMsgUpgradeClient`, for testing only! 107 | pub fn get_dummy_raw_msg_upgrade_client(height: Height) -> RawMsgUpgradeClient { 108 | RawMsgUpgradeClient { 109 | client_id: "tendermint".parse().unwrap(), 110 | client_state: Some(MockClientState::new(MockHeader::new(height)).into()), 111 | consensus_state: Some(MockConsensusState::new(MockHeader::new(height)).into()), 112 | proof_upgrade_client: get_dummy_proof(), 113 | proof_upgrade_consensus_state: get_dummy_proof(), 114 | signer: "dummy_signer".to_string(), 115 | } 116 | } 117 | */ 118 | 119 | #[test] 120 | fn msg_upgrade_client_serialization() { 121 | let client_id: ClientId = "tendermint".parse().unwrap(); 122 | let signer = "dummy_signer".to_string(); 123 | 124 | let height = Height::new(1, 1).unwrap(); 125 | 126 | let client_state = MockClientState::new(MockHeader::new(height)); 127 | let consensus_state = MockConsensusState::new(MockHeader::new(height)); 128 | 129 | let proof = get_dummy_merkle_proof(); 130 | 131 | let msg = MsgUpgradeClient { 132 | client_id, 133 | client_state: client_state.into(), 134 | consensus_state: consensus_state.into(), 135 | proof_upgrade_client: proof.clone(), 136 | proof_upgrade_consensus_state: proof, 137 | signer, 138 | }; 139 | 140 | let raw: RawMsgUpgradeClient = RawMsgUpgradeClient::from(msg.clone()); 141 | let msg_back = MsgUpgradeClient::try_from(raw.clone()).unwrap(); 142 | let raw_back: RawMsgUpgradeClient = RawMsgUpgradeClient::from(msg_back.clone()); 143 | assert_eq!(msg, msg_back); 144 | assert_eq!(raw, raw_back); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /crates/ibc-types-core-client/src/prelude.rs: -------------------------------------------------------------------------------- 1 | pub use core::prelude::v1::*; 2 | 3 | // Re-export according to alloc::prelude::v1 because it is not yet stabilized 4 | // https://doc.rust-lang.org/src/alloc/prelude/v1.rs.html 5 | pub use alloc::{ 6 | borrow::ToOwned, 7 | string::{String, ToString}, 8 | }; 9 | 10 | pub use alloc::{format, vec}; 11 | -------------------------------------------------------------------------------- /crates/ibc-types-core-commitment/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ibc-types-core-commitment" 3 | version = "0.16.0" 4 | edition = "2021" 5 | license = "Apache-2.0" 6 | readme = "../../README.md" 7 | keywords = ["blockchain", "consensus", "cosmos", "ibc", "tendermint"] 8 | repository = "https://github.com/penumbra-zone/ibc-types" 9 | authors = ["Penumbra Labs Option<&(dyn std::error::Error + 'static)> { 15 | None 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /crates/ibc-types-core-commitment/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! IBC client-related types. 2 | #![no_std] 3 | // Requires nightly. 4 | #![cfg_attr(docsrs, feature(doc_auto_cfg))] 5 | 6 | extern crate alloc; 7 | #[cfg(any(test, feature = "std"))] 8 | extern crate std; 9 | 10 | mod prelude; 11 | 12 | mod error; 13 | mod path; 14 | mod prefix; 15 | mod proof; 16 | mod root; 17 | 18 | pub use error::Error; 19 | pub use path::MerklePath; 20 | pub use prefix::MerklePrefix; 21 | pub use proof::MerkleProof; 22 | pub use root::MerkleRoot; 23 | 24 | #[cfg(any(test, feature = "mocks", feature = "mocks-no-std"))] 25 | pub mod mock; 26 | -------------------------------------------------------------------------------- /crates/ibc-types-core-commitment/src/mock.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /crates/ibc-types-core-commitment/src/path.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | 3 | use ibc_proto::ibc::core::commitment::v1::MerklePath as RawMerklePath; 4 | use ibc_types_domain_type::DomainType; 5 | 6 | #[derive(Clone, Debug, PartialEq, Eq)] 7 | pub struct MerklePath { 8 | pub key_path: Vec, 9 | } 10 | 11 | impl DomainType for MerklePath { 12 | type Proto = RawMerklePath; 13 | } 14 | 15 | impl From for RawMerklePath { 16 | fn from(value: MerklePath) -> RawMerklePath { 17 | RawMerklePath { 18 | key_path: value.key_path, 19 | } 20 | } 21 | } 22 | 23 | impl TryFrom for MerklePath { 24 | type Error = anyhow::Error; 25 | fn try_from(value: RawMerklePath) -> Result { 26 | Ok(MerklePath { 27 | key_path: value.key_path, 28 | }) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /crates/ibc-types-core-commitment/src/prefix.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | use crate::MerklePath; 3 | 4 | use ibc_proto::ibc::core::commitment::v1::MerklePrefix as RawMerklePrefix; 5 | use ibc_types_domain_type::DomainType; 6 | 7 | #[derive(Clone, Debug, PartialEq, Eq, Default, Hash)] 8 | #[cfg_attr( 9 | feature = "with_serde", 10 | derive(serde::Serialize, serde::Deserialize), 11 | serde(try_from = "RawMerklePrefix", into = "RawMerklePrefix") 12 | )] 13 | pub struct MerklePrefix { 14 | pub key_prefix: Vec, 15 | } 16 | 17 | impl MerklePrefix { 18 | /// apply the prefix to the supplied paths 19 | pub fn apply(&self, paths: Vec) -> MerklePath { 20 | let commitment_str = 21 | core::str::from_utf8(&self.key_prefix).expect("commitment prefix is not valid utf-8"); 22 | let mut key_path: Vec = vec![commitment_str.to_string()]; 23 | key_path.append(paths.clone().as_mut()); 24 | 25 | MerklePath { key_path } 26 | } 27 | } 28 | 29 | impl DomainType for MerklePrefix { 30 | type Proto = RawMerklePrefix; 31 | } 32 | 33 | impl From> for MerklePrefix { 34 | fn from(value: Vec) -> MerklePrefix { 35 | MerklePrefix { key_prefix: value } 36 | } 37 | } 38 | 39 | impl From for RawMerklePrefix { 40 | fn from(value: MerklePrefix) -> RawMerklePrefix { 41 | RawMerklePrefix { 42 | key_prefix: value.key_prefix, 43 | } 44 | } 45 | } 46 | 47 | impl From for MerklePrefix { 48 | fn from(value: RawMerklePrefix) -> MerklePrefix { 49 | MerklePrefix { 50 | key_prefix: value.key_prefix, 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /crates/ibc-types-core-commitment/src/prelude.rs: -------------------------------------------------------------------------------- 1 | pub use core::prelude::v1::*; 2 | 3 | // Re-export according to alloc::prelude::v1 because it is not yet stabilized 4 | // https://doc.rust-lang.org/src/alloc/prelude/v1.rs.html 5 | 6 | pub use alloc::string::{String, ToString}; 7 | pub use alloc::vec::Vec; 8 | 9 | pub use alloc::vec; 10 | -------------------------------------------------------------------------------- /crates/ibc-types-core-commitment/src/root.rs: -------------------------------------------------------------------------------- 1 | use core::fmt::Debug; 2 | 3 | use crate::prelude::*; 4 | 5 | use ibc_proto::ibc::core::commitment::v1::MerkleRoot as RawMerkleRoot; 6 | use ibc_types_domain_type::DomainType; 7 | 8 | #[derive(Clone, PartialEq, Eq)] 9 | pub struct MerkleRoot { 10 | pub hash: Vec, 11 | } 12 | 13 | impl Debug for MerkleRoot { 14 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 15 | write!(f, "{:?}", hex::encode(&self.hash)) 16 | } 17 | } 18 | 19 | impl DomainType for MerkleRoot { 20 | type Proto = RawMerkleRoot; 21 | } 22 | 23 | impl From for RawMerkleRoot { 24 | fn from(value: MerkleRoot) -> RawMerkleRoot { 25 | RawMerkleRoot { hash: value.hash } 26 | } 27 | } 28 | 29 | impl TryFrom for MerkleRoot { 30 | type Error = anyhow::Error; 31 | fn try_from(value: RawMerkleRoot) -> Result { 32 | Ok(MerkleRoot { hash: value.hash }) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /crates/ibc-types-core-connection/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ibc-types-core-connection" 3 | version = "0.16.0" 4 | edition = "2021" 5 | license = "Apache-2.0" 6 | readme = "../../README.md" 7 | keywords = ["blockchain", "consensus", "cosmos", "ibc", "tendermint"] 8 | repository = "https://github.com/penumbra-zone/ibc-types" 9 | authors = ["Penumbra Labs Option<&(dyn std::error::Error + 'static)> { 71 | match &self { 72 | Self::Client(e) => Some(e), 73 | Self::InvalidIdentifier(e) => Some(e), 74 | Self::VerifyConnectionState(e) => Some(e), 75 | Self::ConsensusStateVerificationFailure { 76 | client_error: e, .. 77 | } => Some(e), 78 | Self::ClientStateVerificationFailure { 79 | client_error: e, .. 80 | } => Some(e), 81 | _ => None, 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /crates/ibc-types-core-connection/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! IBC connection-related types. 2 | #![no_std] 3 | // Requires nightly. 4 | #![cfg_attr(docsrs, feature(doc_auto_cfg))] 5 | 6 | extern crate alloc; 7 | #[cfg(any(test, feature = "std"))] 8 | extern crate std; 9 | 10 | mod connection; 11 | mod error; 12 | mod identifier; 13 | mod prelude; 14 | mod version; 15 | 16 | pub use connection::{ClientPaths, ConnectionEnd, Counterparty, IdentifiedConnectionEnd, State}; 17 | pub use error::ConnectionError; 18 | pub use identifier::{ChainId, ConnectionId}; 19 | pub use version::Version; 20 | 21 | pub mod events; 22 | pub mod msgs; 23 | 24 | #[cfg(any(test, feature = "mocks", feature = "mocks-no-std"))] 25 | pub mod mocks; 26 | -------------------------------------------------------------------------------- /crates/ibc-types-core-connection/src/mocks.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /crates/ibc-types-core-connection/src/msgs.rs: -------------------------------------------------------------------------------- 1 | //! Message definitions for the connection handshake datagrams. 2 | //! 3 | //! We define each of the four messages in the connection handshake protocol as a `struct`. 4 | //! Each such message comprises the same fields as the datagrams defined in ICS3 English spec: 5 | //! . 6 | //! 7 | //! One departure from ICS3 is that we abstract the three counterparty fields (connection id, 8 | //! prefix, and client id) into a single field of type `Counterparty`; this applies to messages 9 | //! `MsgConnectionOpenInit` and `MsgConnectionOpenTry`. One other difference with regards to 10 | //! abstraction is that all proof-related attributes in a message are encapsulated in `Proofs` type. 11 | //! 12 | //! Another difference to ICS3 specs is that each message comprises an additional field called 13 | //! `signer` which is specific to Cosmos-SDK. 14 | 15 | mod conn_open_ack; 16 | mod conn_open_confirm; 17 | mod conn_open_init; 18 | mod conn_open_try; 19 | 20 | pub use conn_open_ack::MsgConnectionOpenAck; 21 | pub use conn_open_confirm::MsgConnectionOpenConfirm; 22 | pub use conn_open_init::MsgConnectionOpenInit; 23 | pub use conn_open_try::MsgConnectionOpenTry; 24 | 25 | /// Enumeration of all possible messages that the ICS3 protocol processes. 26 | #[derive(Clone, Debug, PartialEq)] 27 | pub enum ConnectionMsg { 28 | OpenInit(MsgConnectionOpenInit), 29 | OpenTry(MsgConnectionOpenTry), 30 | OpenAck(MsgConnectionOpenAck), 31 | OpenConfirm(MsgConnectionOpenConfirm), 32 | } 33 | 34 | #[cfg(test)] 35 | pub mod test_util { 36 | use crate::{prelude::*, ConnectionId}; 37 | 38 | use ibc_proto::ibc::core::{ 39 | commitment::v1::MerklePrefix, connection::v1::Counterparty as RawCounterparty, 40 | }; 41 | use ibc_types_core_client::ClientId; 42 | use ibc_types_core_commitment::MerkleProof; 43 | use ibc_types_domain_type::DomainType; 44 | use ics23::CommitmentProof; 45 | 46 | pub fn get_dummy_raw_counterparty(conn_id: Option) -> RawCounterparty { 47 | let connection_id = match conn_id { 48 | Some(id) => ConnectionId::new(id).to_string(), 49 | None => "".to_string(), 50 | }; 51 | RawCounterparty { 52 | client_id: ClientId::default().to_string(), 53 | connection_id, 54 | prefix: Some(MerklePrefix { 55 | key_prefix: b"ibc".to_vec(), 56 | }), 57 | } 58 | } 59 | 60 | pub fn get_dummy_bech32_account() -> String { 61 | "cosmos1wxeyh7zgn4tctjzs0vtqpc6p5cxq5t2muzl7ng".to_string() 62 | } 63 | 64 | pub fn get_dummy_proof() -> Vec { 65 | MerkleProof { 66 | proofs: vec![CommitmentProof::default()], 67 | } 68 | .encode_to_vec() 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /crates/ibc-types-core-connection/src/msgs/conn_open_confirm.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | use ibc_proto::ibc::core::connection::v1::MsgConnectionOpenConfirm as RawMsgConnectionOpenConfirm; 3 | 4 | use ibc_types_core_client::Height; 5 | use ibc_types_core_commitment::MerkleProof; 6 | use ibc_types_domain_type::DomainType; 7 | 8 | use crate::{ConnectionError, ConnectionId}; 9 | 10 | /// Per our convention, this message is sent to chain B. 11 | /// The handler will check proofs of chain A. 12 | #[derive(Clone, Debug, PartialEq)] 13 | pub struct MsgConnectionOpenConfirm { 14 | /// ConnectionId that chain B has chosen for it's ConnectionEnd 15 | pub conn_id_on_b: ConnectionId, 16 | /// proof of ConnectionEnd stored on Chain A during ConnOpenInit 17 | pub proof_conn_end_on_a: MerkleProof, 18 | /// Height at which `proof_conn_end_on_a` in this message was taken 19 | pub proof_height_on_a: Height, 20 | pub signer: String, 21 | } 22 | 23 | impl DomainType for MsgConnectionOpenConfirm { 24 | type Proto = RawMsgConnectionOpenConfirm; 25 | } 26 | 27 | impl TryFrom for MsgConnectionOpenConfirm { 28 | type Error = ConnectionError; 29 | 30 | fn try_from(msg: RawMsgConnectionOpenConfirm) -> Result { 31 | Ok(Self { 32 | conn_id_on_b: msg 33 | .connection_id 34 | .parse() 35 | .map_err(ConnectionError::InvalidIdentifier)?, 36 | proof_conn_end_on_a: MerkleProof::decode(msg.proof_ack.as_ref()) 37 | .map_err(|_| ConnectionError::InvalidProof)?, 38 | proof_height_on_a: msg 39 | .proof_height 40 | .and_then(|raw_height| raw_height.try_into().ok()) 41 | .ok_or(ConnectionError::MissingProofHeight)?, 42 | signer: msg.signer, 43 | }) 44 | } 45 | } 46 | 47 | impl From for RawMsgConnectionOpenConfirm { 48 | fn from(msg: MsgConnectionOpenConfirm) -> Self { 49 | RawMsgConnectionOpenConfirm { 50 | connection_id: msg.conn_id_on_b.as_str().to_string(), 51 | proof_ack: msg.proof_conn_end_on_a.encode_to_vec(), 52 | proof_height: Some(msg.proof_height_on_a.into()), 53 | signer: msg.signer.to_string(), 54 | } 55 | } 56 | } 57 | 58 | #[cfg(test)] 59 | pub mod test_util { 60 | use super::MsgConnectionOpenConfirm; 61 | use crate::prelude::*; 62 | use ibc_proto::ibc::core::{ 63 | client::v1::Height, connection::v1::MsgConnectionOpenConfirm as RawMsgConnectionOpenConfirm, 64 | }; 65 | 66 | use crate::msgs::test_util::{get_dummy_bech32_account, get_dummy_proof}; 67 | 68 | /// Testing-specific helper methods. 69 | impl MsgConnectionOpenConfirm { 70 | /// Returns a new `MsgConnectionOpenConfirm` with dummy values. 71 | pub fn new_dummy() -> Self { 72 | MsgConnectionOpenConfirm::try_from(get_dummy_raw_msg_conn_open_confirm()).unwrap() 73 | } 74 | } 75 | 76 | pub fn get_dummy_raw_msg_conn_open_confirm() -> RawMsgConnectionOpenConfirm { 77 | RawMsgConnectionOpenConfirm { 78 | connection_id: "srcconnection".to_string(), 79 | proof_ack: get_dummy_proof(), 80 | proof_height: Some(Height { 81 | revision_number: 0, 82 | revision_height: 10, 83 | }), 84 | signer: get_dummy_bech32_account(), 85 | } 86 | } 87 | } 88 | 89 | #[cfg(test)] 90 | mod tests { 91 | use crate::prelude::*; 92 | 93 | use test_log::test; 94 | 95 | use ibc_proto::ibc::core::{ 96 | client::v1::Height, connection::v1::MsgConnectionOpenConfirm as RawMsgConnectionOpenConfirm, 97 | }; 98 | 99 | use super::{test_util::*, *}; 100 | 101 | #[test] 102 | fn parse_connection_open_confirm_msg() { 103 | #[derive(Clone, Debug, PartialEq)] 104 | struct Test { 105 | name: String, 106 | raw: RawMsgConnectionOpenConfirm, 107 | want_pass: bool, 108 | } 109 | 110 | let default_ack_msg = get_dummy_raw_msg_conn_open_confirm(); 111 | let tests: Vec = vec![ 112 | Test { 113 | name: "Good parameters".to_string(), 114 | raw: default_ack_msg.clone(), 115 | want_pass: true, 116 | }, 117 | Test { 118 | name: "Bad connection id, non-alpha".to_string(), 119 | raw: RawMsgConnectionOpenConfirm { 120 | connection_id: "con007".to_string(), 121 | ..default_ack_msg.clone() 122 | }, 123 | want_pass: false, 124 | }, 125 | Test { 126 | name: "Bad proof height, height is 0".to_string(), 127 | raw: RawMsgConnectionOpenConfirm { 128 | proof_height: Some(Height { 129 | revision_number: 1, 130 | revision_height: 0, 131 | }), 132 | ..default_ack_msg 133 | }, 134 | want_pass: false, 135 | }, 136 | ] 137 | .into_iter() 138 | .collect(); 139 | 140 | for test in tests { 141 | let msg = MsgConnectionOpenConfirm::try_from(test.raw.clone()); 142 | 143 | assert_eq!( 144 | test.want_pass, 145 | msg.is_ok(), 146 | "MsgConnOpenTry::new failed for test {}, \nmsg {:?} with error {:?}", 147 | test.name, 148 | test.raw, 149 | msg.err(), 150 | ); 151 | } 152 | } 153 | 154 | #[test] 155 | fn to_and_from() { 156 | let raw = get_dummy_raw_msg_conn_open_confirm(); 157 | let msg = MsgConnectionOpenConfirm::try_from(raw.clone()).unwrap(); 158 | let raw_back = RawMsgConnectionOpenConfirm::from(msg.clone()); 159 | let msg_back = MsgConnectionOpenConfirm::try_from(raw_back.clone()).unwrap(); 160 | assert_eq!(raw, raw_back); 161 | assert_eq!(msg, msg_back); 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /crates/ibc-types-core-connection/src/prelude.rs: -------------------------------------------------------------------------------- 1 | pub use core::prelude::v1::*; 2 | 3 | // Re-export according to alloc::prelude::v1 because it is not yet stabilized 4 | // https://doc.rust-lang.org/src/alloc/prelude/v1.rs.html 5 | pub use alloc::{ 6 | string::{String, ToString}, 7 | vec::Vec, 8 | }; 9 | 10 | pub use alloc::{format, vec}; 11 | -------------------------------------------------------------------------------- /crates/ibc-types-domain-type/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ibc-types-domain-type" 3 | version = "0.16.0" 4 | edition = "2021" 5 | license = "Apache-2.0" 6 | publish = true 7 | keywords = ["blockchain", "consensus", "cosmos", "ibc", "tendermint"] 8 | repository = "https://github.com/penumbra-zone/ibc-types" 9 | authors = ["Penumbra Labs , 17 | Self::Proto: prost::Message + prost::Name + Default + From + Send + Sync + 'static, 18 | >::Error: Into + Send + Sync + 'static, 19 | { 20 | type Proto; 21 | 22 | /// Encode this domain type to a byte vector, via proto type `P`. 23 | fn encode_to_vec(&self) -> Vec { 24 | use prost::Message; 25 | self.to_proto().encode_to_vec() 26 | } 27 | 28 | /// Convert this domain type to the associated proto type. 29 | /// 30 | /// This uses the `From` impl internally, so it works exactly 31 | /// like `.into()`, but does not require type inference. 32 | fn to_proto(&self) -> Self::Proto { 33 | Self::Proto::from(self.clone()) 34 | } 35 | 36 | /// Decode this domain type from a byte buffer, via proto type `P`. 37 | fn decode(buf: B) -> Result { 38 | ::decode(buf) 39 | .map_err(anyhow::Error::msg)? 40 | .try_into() 41 | .map_err(Into::into) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /crates/ibc-types-domain-type/src/prelude.rs: -------------------------------------------------------------------------------- 1 | pub use core::prelude::v1::*; 2 | 3 | // Re-export according to alloc::prelude::v1 because it is not yet stabilized 4 | // https://doc.rust-lang.org/src/alloc/prelude/v1.rs.html 5 | 6 | pub use alloc::vec::Vec; 7 | -------------------------------------------------------------------------------- /crates/ibc-types-identifier/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ibc-types-identifier" 3 | version = "0.16.0" 4 | edition = "2021" 5 | license = "Apache-2.0" 6 | readme = "../../README.md" 7 | keywords = ["blockchain", "consensus", "cosmos", "ibc", "tendermint"] 8 | repository = "https://github.com/penumbra-zone/ibc-types" 9 | authors = ["Penumbra Labs ` 28 | InvalidCharacter { id: String }, 29 | /// identifier cannot be empty 30 | Empty, 31 | /// Invalid channel id in counterparty 32 | InvalidCounterpartyChannelId, 33 | } 34 | 35 | #[cfg(feature = "std")] 36 | impl std::error::Error for IdentifierError {} 37 | 38 | /// Path separator (ie. forward slash '/') 39 | const PATH_SEPARATOR: char = '/'; 40 | const VALID_SPECIAL_CHARS: &str = "._+-#[]<>"; 41 | 42 | /// Default validator function for identifiers. 43 | /// 44 | /// A valid identifier only contain lowercase alphabetic characters, and be of a given min and max 45 | /// length. 46 | pub fn validate_identifier(id: &str, min: usize, max: usize) -> Result<(), IdentifierError> { 47 | assert!(max >= min); 48 | 49 | // Check identifier is not empty 50 | if id.is_empty() { 51 | return Err(IdentifierError::Empty); 52 | } 53 | 54 | // Check identifier does not contain path separators 55 | if id.contains(PATH_SEPARATOR) { 56 | return Err(IdentifierError::ContainSeparator { id: id.into() }); 57 | } 58 | 59 | // Check identifier length is between given min/max 60 | if id.len() < min || id.len() > max { 61 | return Err(IdentifierError::InvalidLength { 62 | id: id.into(), 63 | length: id.len(), 64 | min, 65 | max, 66 | }); 67 | } 68 | 69 | // Check that the identifier comprises only valid characters: 70 | // - Alphanumeric 71 | // - `.`, `_`, `+`, `-`, `#` 72 | // - `[`, `]`, `<`, `>` 73 | if !id 74 | .chars() 75 | .all(|c| c.is_alphanumeric() || VALID_SPECIAL_CHARS.contains(c)) 76 | { 77 | return Err(IdentifierError::InvalidCharacter { id: id.into() }); 78 | } 79 | 80 | // All good! 81 | Ok(()) 82 | } 83 | 84 | /// Default validator function for Client identifiers. 85 | /// 86 | /// A valid identifier must be between 9-64 characters and only contain lowercase 87 | /// alphabetic characters, 88 | pub fn validate_client_identifier(id: &str) -> Result<(), IdentifierError> { 89 | validate_identifier(id, 9, 64) 90 | } 91 | 92 | /// Default validator function for Connection identifiers. 93 | /// 94 | /// A valid Identifier must be between 10-64 characters and only contain lowercase 95 | /// alphabetic characters, 96 | pub fn validate_connection_identifier(id: &str) -> Result<(), IdentifierError> { 97 | validate_identifier(id, 10, 64) 98 | } 99 | 100 | /// Default validator function for Port identifiers. 101 | /// 102 | /// A valid Identifier must be between 2-128 characters and only contain lowercase 103 | /// alphabetic characters, 104 | pub fn validate_port_identifier(id: &str) -> Result<(), IdentifierError> { 105 | validate_identifier(id, 2, 128) 106 | } 107 | 108 | /// Default validator function for Channel identifiers. 109 | /// 110 | /// A valid identifier must be between 8-64 characters and only contain 111 | /// alphabetic characters, 112 | pub fn validate_channel_identifier(id: &str) -> Result<(), IdentifierError> { 113 | validate_identifier(id, 8, 64) 114 | } 115 | 116 | #[cfg(test)] 117 | mod tests { 118 | use super::*; 119 | use test_log::test; 120 | 121 | #[test] 122 | fn parse_invalid_port_id_min() { 123 | // invalid min port id 124 | let id = validate_port_identifier("p"); 125 | assert!(id.is_err()) 126 | } 127 | 128 | #[test] 129 | fn parse_invalid_port_id_max() { 130 | // invalid max port id (test string length is 130 chars) 131 | let id = validate_port_identifier( 132 | "9anxkcme6je544d5lnj46zqiiiygfqzf8w4bjecbnyj4lj6s7zlpst67yln64tixp9anxkcme6je544d5lnj46zqiiiygfqzf8w4bjecbnyj4lj6s7zlpst67yln64tixp", 133 | ); 134 | assert!(id.is_err()) 135 | } 136 | 137 | #[test] 138 | fn parse_invalid_connection_id_min() { 139 | // invalid min connection id 140 | let id = validate_connection_identifier("connect01"); 141 | assert!(id.is_err()) 142 | } 143 | 144 | #[test] 145 | fn parse_connection_id_max() { 146 | // invalid max connection id (test string length is 65) 147 | let id = validate_connection_identifier( 148 | "ihhankr30iy4nna65hjl2wjod7182io1t2s7u3ip3wqtbbn1sl0rgcntqc540r36r", 149 | ); 150 | assert!(id.is_err()) 151 | } 152 | 153 | #[test] 154 | fn parse_invalid_channel_id_min() { 155 | // invalid channel id, must be at least 8 characters 156 | let id = validate_channel_identifier("channel"); 157 | assert!(id.is_err()) 158 | } 159 | 160 | #[test] 161 | fn parse_channel_id_max() { 162 | // invalid channel id (test string length is 65) 163 | let id = validate_channel_identifier( 164 | "ihhankr30iy4nna65hjl2wjod7182io1t2s7u3ip3wqtbbn1sl0rgcntqc540r36r", 165 | ); 166 | assert!(id.is_err()) 167 | } 168 | 169 | #[test] 170 | fn parse_invalid_client_id_min() { 171 | // invalid min client id 172 | let id = validate_client_identifier("client"); 173 | assert!(id.is_err()) 174 | } 175 | 176 | #[test] 177 | fn parse_client_id_max() { 178 | // invalid max client id (test string length is 65) 179 | let id = validate_client_identifier( 180 | "f0isrs5enif9e4td3r2jcbxoevhz6u1fthn4aforq7ams52jn5m48eiesfht9ckpn", 181 | ); 182 | assert!(id.is_err()) 183 | } 184 | 185 | #[test] 186 | fn parse_invalid_id_chars() { 187 | // invalid id chars 188 | let id = validate_identifier("channel@01", 1, 10); 189 | assert!(id.is_err()) 190 | } 191 | 192 | #[test] 193 | fn parse_invalid_id_empty() { 194 | // invalid id empty 195 | let id = validate_identifier("", 1, 10); 196 | assert!(id.is_err()) 197 | } 198 | 199 | #[test] 200 | fn parse_invalid_id_path_separator() { 201 | // invalid id with path separator 202 | let id = validate_identifier("id/1", 1, 10); 203 | assert!(id.is_err()) 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /crates/ibc-types-identifier/src/prelude.rs: -------------------------------------------------------------------------------- 1 | pub use core::prelude::v1::*; 2 | 3 | // Re-export according to alloc::prelude::v1 because it is not yet stabilized 4 | // https://doc.rust-lang.org/src/alloc/prelude/v1.rs.html 5 | 6 | pub use alloc::string::String; 7 | -------------------------------------------------------------------------------- /crates/ibc-types-lightclients-tendermint/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ibc-types-lightclients-tendermint" 3 | version = "0.16.0" 4 | edition = "2021" 5 | license = "Apache-2.0" 6 | readme = "../../README.md" 7 | keywords = ["blockchain", "consensus", "cosmos", "ibc", "tendermint"] 8 | repository = "https://github.com/penumbra-zone/ibc-types" 9 | authors = ["Penumbra Labs Self { 26 | Self { 27 | timestamp, 28 | root, 29 | next_validators_hash, 30 | } 31 | } 32 | } 33 | 34 | impl DomainType for ConsensusState { 35 | type Proto = Any; 36 | } 37 | 38 | impl Protobuf for ConsensusState {} 39 | 40 | impl TryFrom for ConsensusState { 41 | type Error = Error; 42 | 43 | fn try_from(raw: RawConsensusState) -> Result { 44 | let ibc_proto::google::protobuf::Timestamp { seconds, nanos } = 45 | raw.timestamp.ok_or(Error::InvalidRawClientState { 46 | reason: "missing timestamp".into(), 47 | })?; 48 | // FIXME: shunts like this are necessary due to 49 | // https://github.com/informalsystems/tendermint-rs/issues/1053 50 | let proto_timestamp = tpb::Timestamp { seconds, nanos }; 51 | let timestamp = proto_timestamp 52 | .try_into() 53 | .map_err(|e| Error::InvalidRawClientState { 54 | reason: format!("invalid timestamp: {e}"), 55 | })?; 56 | 57 | Ok(Self { 58 | root: raw 59 | .root 60 | .ok_or_else(|| Error::InvalidRawClientState { 61 | reason: "missing commitment root".into(), 62 | })? 63 | .try_into() 64 | .map_err(|e| Error::InvalidRawClientState { 65 | reason: format!("invalid commitment root: {e}"), 66 | })?, 67 | timestamp, 68 | next_validators_hash: Hash::from_bytes(Algorithm::Sha256, &raw.next_validators_hash) 69 | .map_err(|e| Error::InvalidRawClientState { 70 | reason: e.to_string(), 71 | })?, 72 | }) 73 | } 74 | } 75 | 76 | impl From for RawConsensusState { 77 | fn from(value: ConsensusState) -> Self { 78 | // FIXME: shunts like this are necessary due to 79 | // https://github.com/informalsystems/tendermint-rs/issues/1053 80 | let tpb::Timestamp { seconds, nanos } = value.timestamp.into(); 81 | let timestamp = ibc_proto::google::protobuf::Timestamp { seconds, nanos }; 82 | 83 | RawConsensusState { 84 | timestamp: Some(timestamp), 85 | root: Some(value.root.into()), 86 | next_validators_hash: value.next_validators_hash.as_bytes().to_vec(), 87 | } 88 | } 89 | } 90 | 91 | impl Protobuf for ConsensusState {} 92 | 93 | impl TryFrom for ConsensusState { 94 | type Error = Error; 95 | 96 | fn try_from(raw: Any) -> Result { 97 | use bytes::Buf; 98 | use core::ops::Deref; 99 | use prost::Message; 100 | 101 | fn decode_consensus_state(buf: B) -> Result { 102 | RawConsensusState::decode(buf) 103 | .map_err(Error::Decode)? 104 | .try_into() 105 | } 106 | 107 | match raw.type_url.as_str() { 108 | TENDERMINT_CONSENSUS_STATE_TYPE_URL => { 109 | decode_consensus_state(raw.value.deref()).map_err(Into::into) 110 | } 111 | _ => Err(Error::WrongTypeUrl { url: raw.type_url }), 112 | } 113 | } 114 | } 115 | 116 | impl From for Any { 117 | fn from(consensus_state: ConsensusState) -> Self { 118 | Any { 119 | type_url: TENDERMINT_CONSENSUS_STATE_TYPE_URL.to_string(), 120 | value: Protobuf::::encode_vec(consensus_state), 121 | } 122 | } 123 | } 124 | 125 | impl From for ConsensusState { 126 | fn from(header: tendermint::block::Header) -> Self { 127 | Self { 128 | root: MerkleRoot { 129 | hash: header.app_hash.as_bytes().to_vec(), 130 | }, 131 | timestamp: header.time, 132 | next_validators_hash: header.next_validators_hash, 133 | } 134 | } 135 | } 136 | 137 | impl From
for ConsensusState { 138 | fn from(header: Header) -> Self { 139 | Self::from(header.signed_header.header) 140 | } 141 | } 142 | 143 | /* 144 | #[cfg(test)] 145 | #[cfg(feature = "serde")] 146 | mod tests { 147 | use tendermint_rpc::endpoint::abci_query::AbciQuery; 148 | use test_log::test; 149 | 150 | use crate::test::test_serialization_roundtrip; 151 | 152 | #[test] 153 | fn serialization_roundtrip_no_proof() { 154 | let json_data = 155 | include_str!("../../../tests/support/query/serialization/consensus_state.json"); 156 | test_serialization_roundtrip::(json_data); 157 | } 158 | 159 | #[test] 160 | fn serialization_roundtrip_with_proof() { 161 | let json_data = 162 | include_str!("../../../tests/support/query/serialization/consensus_state_proof.json"); 163 | test_serialization_roundtrip::(json_data); 164 | } 165 | } 166 | */ 167 | -------------------------------------------------------------------------------- /crates/ibc-types-lightclients-tendermint/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! ICS 07: Tendermint Client implements a client verification algorithm for blockchains which use 2 | //! the Tendermint consensus algorithm. 3 | 4 | // Requires nightly. 5 | #![cfg_attr(docsrs, feature(doc_auto_cfg))] 6 | 7 | extern crate alloc; 8 | 9 | use alloc::string::ToString; 10 | 11 | use ibc_types_core_client::ClientType; 12 | 13 | mod prelude; 14 | 15 | mod trust_threshold; 16 | pub use trust_threshold::TrustThreshold; 17 | 18 | mod error; 19 | pub use error::{Error, VerificationError}; 20 | 21 | pub mod client_state; 22 | pub mod consensus_state; 23 | pub mod header; 24 | pub mod misbehaviour; 25 | 26 | pub use consensus_state::ConsensusState; 27 | 28 | pub const TENDERMINT_CLIENT_TYPE: &str = "07-tendermint"; 29 | 30 | pub fn client_type() -> ClientType { 31 | ClientType::new(TENDERMINT_CLIENT_TYPE.to_string()) 32 | } 33 | -------------------------------------------------------------------------------- /crates/ibc-types-lightclients-tendermint/src/misbehaviour.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | 3 | use bytes::Buf; 4 | use ibc_proto::google::protobuf::Any; 5 | use ibc_proto::ibc::lightclients::tendermint::v1::Misbehaviour as RawMisbehaviour; 6 | use ibc_proto::Protobuf; 7 | use prost::Message; 8 | use tendermint_light_client_verifier::ProdVerifier; 9 | 10 | use crate::{error::IntoResult, header::Header, Error}; 11 | use ibc_types_core_client::ClientId; 12 | use ibc_types_core_connection::ChainId; 13 | 14 | pub const TENDERMINT_MISBEHAVIOUR_TYPE_URL: &str = "/ibc.lightclients.tendermint.v1.Misbehaviour"; 15 | 16 | #[derive(Clone, Debug, PartialEq, Eq)] 17 | pub struct Misbehaviour { 18 | pub client_id: ClientId, 19 | pub header1: Header, 20 | pub header2: Header, 21 | } 22 | 23 | impl Misbehaviour { 24 | pub fn new(client_id: ClientId, header1: Header, header2: Header) -> Result { 25 | if header1.signed_header.header.chain_id != header2.signed_header.header.chain_id { 26 | return Err(Error::InvalidRawMisbehaviour { 27 | reason: "headers must have identical chain_ids".to_owned(), 28 | }); 29 | } 30 | 31 | if header1.height() < header2.height() { 32 | return Err(Error::InvalidRawMisbehaviour { 33 | reason: format!( 34 | "headers1 height is less than header2 height ({} < {})", 35 | header1.height(), 36 | header2.height() 37 | ), 38 | }); 39 | } 40 | 41 | let untrusted_state_1 = header1.as_untrusted_block_state(); 42 | let untrusted_state_2 = header2.as_untrusted_block_state(); 43 | 44 | let verifier = ProdVerifier::default(); 45 | 46 | verifier 47 | .verify_validator_sets(&untrusted_state_1) 48 | .into_result()?; 49 | verifier 50 | .verify_validator_sets(&untrusted_state_2) 51 | .into_result()?; 52 | 53 | verifier.verify_commit(&untrusted_state_1).into_result()?; 54 | verifier.verify_commit(&untrusted_state_2).into_result()?; 55 | 56 | Ok(Self { 57 | client_id, 58 | header1, 59 | header2, 60 | }) 61 | } 62 | 63 | pub fn client_id(&self) -> &ClientId { 64 | &self.client_id 65 | } 66 | 67 | pub fn header1(&self) -> &Header { 68 | &self.header1 69 | } 70 | 71 | pub fn header2(&self) -> &Header { 72 | &self.header2 73 | } 74 | 75 | pub fn chain_id_matches(&self, chain_id: &ChainId) -> bool { 76 | assert_eq!( 77 | self.header1.signed_header.header.chain_id, self.header2.signed_header.header.chain_id, 78 | "this is enforced by the ctor" 79 | ); 80 | 81 | self.header1.signed_header.header.chain_id.as_str() == chain_id.as_str() 82 | } 83 | } 84 | 85 | impl Protobuf for Misbehaviour {} 86 | 87 | impl TryFrom for Misbehaviour { 88 | type Error = Error; 89 | 90 | #[allow(deprecated)] 91 | fn try_from(raw: RawMisbehaviour) -> Result { 92 | let client_id = raw 93 | .client_id 94 | .parse() 95 | .map_err(|_| Error::InvalidRawClientId { 96 | client_id: raw.client_id.clone(), 97 | })?; 98 | let header1: Header = raw 99 | .header_1 100 | .ok_or_else(|| Error::InvalidRawMisbehaviour { 101 | reason: "missing header1".into(), 102 | })? 103 | .try_into()?; 104 | let header2: Header = raw 105 | .header_2 106 | .ok_or_else(|| Error::InvalidRawMisbehaviour { 107 | reason: "missing header2".into(), 108 | })? 109 | .try_into()?; 110 | 111 | Self::new(client_id, header1, header2) 112 | } 113 | } 114 | 115 | impl From for RawMisbehaviour { 116 | #[allow(deprecated)] 117 | fn from(value: Misbehaviour) -> Self { 118 | RawMisbehaviour { 119 | client_id: value.client_id.to_string(), 120 | header_1: Some(value.header1.into()), 121 | header_2: Some(value.header2.into()), 122 | } 123 | } 124 | } 125 | 126 | impl Protobuf for Misbehaviour {} 127 | 128 | impl TryFrom for Misbehaviour { 129 | type Error = Error; 130 | 131 | fn try_from(raw: Any) -> Result { 132 | use core::ops::Deref; 133 | 134 | fn decode_misbehaviour(buf: B) -> Result { 135 | RawMisbehaviour::decode(buf) 136 | .map_err(Error::Decode)? 137 | .try_into() 138 | } 139 | 140 | match raw.type_url.as_str() { 141 | TENDERMINT_MISBEHAVIOUR_TYPE_URL => { 142 | decode_misbehaviour(raw.value.deref()).map_err(Into::into) 143 | } 144 | _ => Err(Error::WrongTypeUrl { url: raw.type_url }), 145 | } 146 | } 147 | } 148 | 149 | impl From for Any { 150 | fn from(misbehaviour: Misbehaviour) -> Self { 151 | Any { 152 | type_url: TENDERMINT_MISBEHAVIOUR_TYPE_URL.to_string(), 153 | value: Protobuf::::encode_vec(misbehaviour), 154 | } 155 | } 156 | } 157 | 158 | pub fn decode_misbehaviour(buf: B) -> Result { 159 | RawMisbehaviour::decode(buf) 160 | .map_err(Error::Decode)? 161 | .try_into() 162 | } 163 | 164 | impl core::fmt::Display for Misbehaviour { 165 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { 166 | write!( 167 | f, 168 | "{} h1: {}-{} h2: {}-{}", 169 | self.client_id, 170 | self.header1.height(), 171 | self.header1.trusted_height, 172 | self.header2.height(), 173 | self.header2.trusted_height, 174 | ) 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /crates/ibc-types-lightclients-tendermint/src/prelude.rs: -------------------------------------------------------------------------------- 1 | pub use core::prelude::v1::*; 2 | 3 | // Re-export according to alloc::prelude::v1 because it is not yet stabilized 4 | // https://doc.rust-lang.org/src/alloc/prelude/v1.rs.html 5 | pub use alloc::{ 6 | borrow::ToOwned, 7 | string::{String, ToString}, 8 | vec::Vec, 9 | }; 10 | -------------------------------------------------------------------------------- /crates/ibc-types-lightclients-tendermint/src/trust_threshold.rs: -------------------------------------------------------------------------------- 1 | //! IBC Domain type definition for [`TrustThreshold`] 2 | //! represented as a fraction with valid values in the 3 | //! range `[0, 1)`. 4 | 5 | use core::{ 6 | convert::TryFrom, 7 | fmt::{Display, Error as FmtError, Formatter}, 8 | }; 9 | 10 | use ibc_proto::{ibc::lightclients::tendermint::v1::Fraction, Protobuf}; 11 | use tendermint::trust_threshold::TrustThresholdFraction; 12 | 13 | use crate::error::Error; 14 | 15 | /// Defines the level of trust that a client has towards a set of validators of a chain. 16 | /// 17 | /// A trust threshold is represented as a fraction, i.e., a numerator and 18 | /// and a denominator. 19 | /// A typical trust threshold is 1/3 in practice. 20 | /// This type accepts even a value of 0, (numerator = 0, denominator = 0), 21 | /// which is used in the client state of an upgrading client. 22 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 23 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 24 | pub struct TrustThreshold { 25 | pub numerator: u64, 26 | pub denominator: u64, 27 | } 28 | 29 | impl TrustThreshold { 30 | /// Constant for a trust threshold of 1/3. 31 | pub const ONE_THIRD: Self = Self { 32 | numerator: 1, 33 | denominator: 3, 34 | }; 35 | 36 | /// Constant for a trust threshold of 2/3. 37 | pub const TWO_THIRDS: Self = Self { 38 | numerator: 2, 39 | denominator: 3, 40 | }; 41 | 42 | /// Constant for a trust threshold of 0/0. 43 | pub const ZERO: Self = Self { 44 | numerator: 0, 45 | denominator: 0, 46 | }; 47 | 48 | /// Instantiate a TrustThreshold with the given denominator and 49 | /// numerator. 50 | /// 51 | /// The constructor succeeds if long as the resulting fraction 52 | /// is in the range`[0, 1)`. 53 | pub fn new(numerator: u64, denominator: u64) -> Result { 54 | // The two parameters cannot yield a fraction that is bigger or equal to 1 55 | if (numerator > denominator) 56 | || (denominator == 0 && numerator != 0) 57 | || (numerator == denominator && numerator != 0) 58 | { 59 | return Err(Error::FailedTrustThresholdConversion { 60 | numerator, 61 | denominator, 62 | }); 63 | } 64 | 65 | Ok(Self { 66 | numerator, 67 | denominator, 68 | }) 69 | } 70 | 71 | /// The numerator of the fraction underlying this trust threshold. 72 | pub fn numerator(&self) -> u64 { 73 | self.numerator 74 | } 75 | 76 | /// The denominator of the fraction underlying this trust threshold. 77 | pub fn denominator(&self) -> u64 { 78 | self.denominator 79 | } 80 | } 81 | 82 | /// Conversion from Tendermint domain type into 83 | /// IBC domain type. 84 | impl From for TrustThreshold { 85 | fn from(t: TrustThresholdFraction) -> Self { 86 | Self { 87 | numerator: t.numerator(), 88 | denominator: t.denominator(), 89 | } 90 | } 91 | } 92 | 93 | /// Conversion from IBC domain type into 94 | /// Tendermint domain type. 95 | impl TryFrom for TrustThresholdFraction { 96 | type Error = Error; 97 | 98 | fn try_from(t: TrustThreshold) -> Result { 99 | Self::new(t.numerator, t.denominator).map_err(|_| Error::FailedTrustThresholdConversion { 100 | numerator: t.numerator, 101 | denominator: t.denominator, 102 | }) 103 | } 104 | } 105 | 106 | impl Protobuf for TrustThreshold {} 107 | 108 | impl From for Fraction { 109 | fn from(t: TrustThreshold) -> Self { 110 | Self { 111 | numerator: t.numerator, 112 | denominator: t.denominator, 113 | } 114 | } 115 | } 116 | 117 | impl TryFrom for TrustThreshold { 118 | type Error = Error; 119 | 120 | fn try_from(value: Fraction) -> Result { 121 | Self::new(value.numerator, value.denominator) 122 | } 123 | } 124 | 125 | impl Default for TrustThreshold { 126 | fn default() -> Self { 127 | Self::ONE_THIRD 128 | } 129 | } 130 | 131 | impl Display for TrustThreshold { 132 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { 133 | write!(f, "{}/{}", self.numerator, self.denominator) 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /crates/ibc-types-path/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ibc-types-path" 3 | version = "0.16.0" 4 | edition = "2021" 5 | license = "Apache-2.0" 6 | readme = "../../README.md" 7 | keywords = ["blockchain", "consensus", "cosmos", "ibc", "tendermint"] 8 | repository = "https://github.com/penumbra-zone/ibc-types" 9 | authors = ["Penumbra Labs Self { 34 | Self::Success(ConstAckSuccess::Success) 35 | } 36 | 37 | pub fn is_successful(&self) -> bool { 38 | matches!(self, TokenTransferAcknowledgement::Success(_)) 39 | } 40 | } 41 | 42 | impl Display for TokenTransferAcknowledgement { 43 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { 44 | match self { 45 | TokenTransferAcknowledgement::Success(_) => write!(f, "{ACK_SUCCESS_B64}"), 46 | TokenTransferAcknowledgement::Error(err_str) => write!(f, "{err_str}"), 47 | } 48 | } 49 | } 50 | 51 | impl From for Vec { 52 | fn from(ack: TokenTransferAcknowledgement) -> Self { 53 | // WARNING: Make sure all branches always return a non-empty vector. 54 | // Otherwise, the conversion to `Acknowledgement` will panic. 55 | match ack { 56 | TokenTransferAcknowledgement::Success(_) => r#"{"result":"AQ=="}"#.as_bytes().into(), 57 | TokenTransferAcknowledgement::Error(s) => alloc::format!(r#"{{"error":"{s}"}}"#).into(), 58 | } 59 | } 60 | } 61 | 62 | #[cfg(test)] 63 | mod test { 64 | use super::*; 65 | 66 | #[cfg(feature = "with_serde")] 67 | #[test] 68 | fn test_ack_ser() { 69 | use crate::alloc::borrow::ToOwned; 70 | fn ser_json_assert_eq(ack: TokenTransferAcknowledgement, json_str: &str) { 71 | let ser = serde_json::to_string(&ack).unwrap(); 72 | assert_eq!(ser, json_str) 73 | } 74 | 75 | ser_json_assert_eq( 76 | TokenTransferAcknowledgement::success(), 77 | r#"{"result":"AQ=="}"#, 78 | ); 79 | ser_json_assert_eq( 80 | TokenTransferAcknowledgement::Error( 81 | "cannot unmarshal ICS-20 transfer packet data".to_owned(), 82 | ), 83 | r#"{"error":"cannot unmarshal ICS-20 transfer packet data"}"#, 84 | ); 85 | } 86 | 87 | #[cfg(feature = "with_serde")] 88 | #[test] 89 | fn test_ack_success_to_vec() { 90 | let ack_success: Vec = TokenTransferAcknowledgement::success().into(); 91 | 92 | // Check that it's the same output as ibc-go 93 | // Note: this also implicitly checks that the ack bytes are non-empty, 94 | // which would make the conversion to `Acknowledgement` panic 95 | assert_eq!(ack_success, r#"{"result":"AQ=="}"#.as_bytes()); 96 | } 97 | 98 | #[cfg(feature = "with_serde")] 99 | #[test] 100 | fn test_ack_error_to_vec() { 101 | use crate::alloc::string::ToString; 102 | let ack_error: Vec = TokenTransferAcknowledgement::Error( 103 | "cannot unmarshal ICS-20 transfer packet data".to_string(), 104 | ) 105 | .into(); 106 | 107 | // Check that it's the same output as ibc-go 108 | // Note: this also implicitly checks that the ack bytes are non-empty, 109 | // which would make the conversion to `Acknowledgement` panic 110 | assert_eq!( 111 | ack_error, 112 | r#"{"error":"cannot unmarshal ICS-20 transfer packet data"}"#.as_bytes() 113 | ); 114 | } 115 | 116 | #[cfg(feature = "with_serde")] 117 | #[test] 118 | fn test_ack_de() { 119 | use crate::alloc::borrow::ToOwned; 120 | fn de_json_assert_eq(json_str: &str, ack: TokenTransferAcknowledgement) { 121 | let de = serde_json::from_str::(json_str).unwrap(); 122 | assert_eq!(de, ack) 123 | } 124 | 125 | de_json_assert_eq( 126 | r#"{"result":"AQ=="}"#, 127 | TokenTransferAcknowledgement::success(), 128 | ); 129 | de_json_assert_eq( 130 | r#"{"error":"cannot unmarshal ICS-20 transfer packet data"}"#, 131 | TokenTransferAcknowledgement::Error( 132 | "cannot unmarshal ICS-20 transfer packet data".to_owned(), 133 | ), 134 | ); 135 | 136 | assert!( 137 | serde_json::from_str::(r#"{"result":"AQ="}"#).is_err() 138 | ); 139 | assert!( 140 | serde_json::from_str::(r#"{"success":"AQ=="}"#).is_err() 141 | ); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /crates/ibc-types-transfer/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! IBC transfer types. 2 | #![no_std] 3 | // Requires nightly. 4 | #![cfg_attr(docsrs, feature(doc_auto_cfg))] 5 | 6 | extern crate alloc; 7 | #[cfg(any(test, feature = "std"))] 8 | extern crate std; 9 | 10 | mod prelude; 11 | use prelude::*; 12 | 13 | pub mod acknowledgement; 14 | -------------------------------------------------------------------------------- /crates/ibc-types-transfer/src/prelude.rs: -------------------------------------------------------------------------------- 1 | pub use core::prelude::v1::*; 2 | 3 | // Re-export according to alloc::prelude::v1 because it is not yet stabilized 4 | // https://doc.rust-lang.org/src/alloc/prelude/v1.rs.html 5 | 6 | pub use alloc::string::String; 7 | pub use alloc::vec::Vec; 8 | -------------------------------------------------------------------------------- /crates/ibc-types/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ibc-types" 3 | version = "0.16.0" 4 | edition = "2021" 5 | license = "Apache-2.0" 6 | readme = "../../README.md" 7 | keywords = ["blockchain", "consensus", "cosmos", "ibc", "tendermint"] 8 | repository = "https://github.com/penumbra-zone/ibc-types" 9 | authors = ["Penumbra Labs 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. 9 | ## Decision 10 | 11 | > This section explains all of the details of the proposed solution, including implementation details. 12 | It should also describe affects / corollary items that may need to be changed as a part of this. 13 | If the proposed change will be large, please also indicate a way to do the change to maximize ease of review. 14 | (e.g. the optimal split of things to do between separate PR's) 15 | 16 | ## Status 17 | 18 | > A decision may be "proposed" if it hasn't been agreed upon yet, or "accepted" once it is agreed upon. If a later ADR changes or reverses a decision, it may be marked as "deprecated" or "superseded" with a reference to its replacement. 19 | 20 | {Deprecated|Proposed|Accepted} 21 | 22 | ## Consequences 23 | 24 | > This section describes the consequences, after applying the decision. All consequences should be summarized here, not just the "positive" ones. 25 | 26 | ### Positive 27 | 28 | ### Negative 29 | 30 | ### Neutral 31 | 32 | ## References 33 | 34 | > Are there any relevant PR comments, issues that led up to this, or articles referrenced for why we made the given design choice? If so link them here! 35 | 36 | * {reference link} 37 | -------------------------------------------------------------------------------- /docs/architecture/assets/adr05.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/penumbra-zone/ibc-types/957a0f48a637e772950be5d8911866acc72acda6/docs/architecture/assets/adr05.jpg -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "crane": { 4 | "inputs": { 5 | "nixpkgs": [ 6 | "nixpkgs" 7 | ] 8 | }, 9 | "locked": { 10 | "lastModified": 1716156051, 11 | "narHash": "sha256-TjUX7WWRcrhuUxDHsR8pDR2N7jitqZehgCVSy3kBeS8=", 12 | "owner": "ipetkov", 13 | "repo": "crane", 14 | "rev": "7443df1c478947bf96a2e699209f53b2db26209d", 15 | "type": "github" 16 | }, 17 | "original": { 18 | "owner": "ipetkov", 19 | "repo": "crane", 20 | "type": "github" 21 | } 22 | }, 23 | "flake-utils": { 24 | "inputs": { 25 | "systems": "systems" 26 | }, 27 | "locked": { 28 | "lastModified": 1710146030, 29 | "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", 30 | "owner": "numtide", 31 | "repo": "flake-utils", 32 | "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", 33 | "type": "github" 34 | }, 35 | "original": { 36 | "owner": "numtide", 37 | "repo": "flake-utils", 38 | "type": "github" 39 | } 40 | }, 41 | "nixpkgs": { 42 | "locked": { 43 | "lastModified": 1716137900, 44 | "narHash": "sha256-sowPU+tLQv8GlqtVtsXioTKeaQvlMz/pefcdwg8MvfM=", 45 | "owner": "nixos", 46 | "repo": "nixpkgs", 47 | "rev": "6c0b7a92c30122196a761b440ac0d46d3d9954f1", 48 | "type": "github" 49 | }, 50 | "original": { 51 | "owner": "nixos", 52 | "ref": "nixos-unstable", 53 | "repo": "nixpkgs", 54 | "type": "github" 55 | } 56 | }, 57 | "root": { 58 | "inputs": { 59 | "crane": "crane", 60 | "flake-utils": "flake-utils", 61 | "nixpkgs": "nixpkgs", 62 | "rust-overlay": "rust-overlay" 63 | } 64 | }, 65 | "rust-overlay": { 66 | "inputs": { 67 | "flake-utils": [ 68 | "flake-utils" 69 | ], 70 | "nixpkgs": [ 71 | "nixpkgs" 72 | ] 73 | }, 74 | "locked": { 75 | "lastModified": 1716257780, 76 | "narHash": "sha256-R+NjvJzKEkTVCmdrKRfPE4liX/KMGVqGUwwS5H8ET8A=", 77 | "owner": "oxalica", 78 | "repo": "rust-overlay", 79 | "rev": "4e5e3d2c5c9b2721bd266f9e43c14e96811b89d2", 80 | "type": "github" 81 | }, 82 | "original": { 83 | "owner": "oxalica", 84 | "repo": "rust-overlay", 85 | "type": "github" 86 | } 87 | }, 88 | "systems": { 89 | "locked": { 90 | "lastModified": 1681028828, 91 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 92 | "owner": "nix-systems", 93 | "repo": "default", 94 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 95 | "type": "github" 96 | }, 97 | "original": { 98 | "owner": "nix-systems", 99 | "repo": "default", 100 | "type": "github" 101 | } 102 | } 103 | }, 104 | "root": "root", 105 | "version": 7 106 | } 107 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "A nix development shell and build environment for ibc-types"; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; 6 | flake-utils.url = "github:numtide/flake-utils"; 7 | rust-overlay = { 8 | url = "github:oxalica/rust-overlay"; 9 | inputs = { 10 | nixpkgs.follows = "nixpkgs"; 11 | flake-utils.follows = "flake-utils"; 12 | }; 13 | }; 14 | crane = { 15 | url = "github:ipetkov/crane"; 16 | inputs = { nixpkgs.follows = "nixpkgs"; }; 17 | }; 18 | }; 19 | 20 | outputs = { self, nixpkgs, flake-utils, rust-overlay, crane, ... }: 21 | flake-utils.lib.eachDefaultSystem 22 | (system: 23 | let 24 | # Set up for Rust builds, use the stable Rust toolchain 25 | overlays = [ (import rust-overlay) ]; 26 | pkgs = import nixpkgs { inherit system overlays; }; 27 | rustToolchain = pkgs.rust-bin.stable."1.78.0".default; 28 | craneLib = (crane.mkLib pkgs).overrideToolchain rustToolchain; 29 | src = craneLib.cleanCargoSource (craneLib.path ./.); 30 | # Important environment variables so that the build can find the necessary libraries 31 | PKG_CONFIG_PATH="${pkgs.openssl.dev}/lib/pkgconfig"; 32 | in with pkgs; with pkgs.lib; 33 | let 34 | ibc-types = (craneLib.buildPackage { 35 | pname = "ibc-types"; 36 | version = "0.14.1"; 37 | nativeBuildInputs = [ pkg-config ]; 38 | buildInputs = [ openssl ]; 39 | inherit src system PKG_CONFIG_PATH; 40 | cargoVendorDir = null; 41 | cargoExtraArgs = "-p ibc-types"; 42 | meta = { 43 | description = "Common data structures for Inter-Blockchain Communication (IBC) messages"; 44 | homepage = "https://github.com/penumbra-zone/ibc-types"; 45 | license = [ licenses.mit licenses.asl20 ]; 46 | }; 47 | }).overrideAttrs (_: { doCheck = false; }); # Disable tests to improve build times 48 | in rec { 49 | packages = { inherit ibc-types; }; 50 | devShells.default = craneLib.devShell { 51 | inputsFrom = [ ibc-types ]; 52 | packages = [ cargo-watch cargo-nextest ]; 53 | shellHook = '' 54 | export RUST_SRC_PATH=${pkgs.rustPlatform.rustLibSrc} # Required for rust-analyzer 55 | ''; 56 | }; 57 | } 58 | ); 59 | } 60 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # release.sh will hopefully allow us to publish all of the necessary crates in 4 | # this repo in the right order. It is assumed that only one person will be 5 | # releasing all crates at the same time. 6 | # 7 | # It has a default set of crates it will publish, which can be overridden by 8 | # way of command line arguments: 9 | # 10 | # # Release all packages, prompting for each package as to whether to publish 11 | # ./release.sh 12 | # 13 | # # Just release the proto and tendermint crates, but nothing else 14 | # ./release.sh proto tendermint 15 | 16 | set -e 17 | 18 | # A space-separated list of all the crates we want to publish, in the order in 19 | # which they must be published. It's important to respect this order, since 20 | # each subsequent crate depends on one or more of the preceding ones. 21 | DEFAULT_CRATES=" 22 | ibc-types-domain-type \ 23 | ibc-types-identifier \ 24 | ibc-types-timestamp \ 25 | ibc-types-core-commitment \ 26 | ibc-types-core-client \ 27 | ibc-types-transfer \ 28 | ibc-types-core-connection \ 29 | ibc-types-core-channel \ 30 | ibc-types-lightclients-tendermint \ 31 | ibc-types-path \ 32 | ibc-types" 33 | 34 | # Allows us to override the crates we want to publish. 35 | CRATES=${*:-${DEFAULT_CRATES}} 36 | 37 | # Additional flags to pass to the "cargo publish" operation for every crate we 38 | # publish. 39 | CARGO_PUBLISH_FLAGS="" 40 | 41 | # Allow us to specify a crates.io API token via environment variables. Mostly 42 | # for CI use. 43 | if [ -n "${CRATES_TOKEN}" ]; then 44 | CARGO_PUBLISH_FLAGS="${CARGO_PUBLISH_FLAGS} --token ${CRATES_TOKEN}" 45 | fi 46 | 47 | get_manifest_path() { 48 | cargo metadata --format-version 1 | jq -r '.packages[]|select(.name == "'"${1}"'")|.manifest_path' 49 | } 50 | 51 | get_local_version() { 52 | cargo metadata --format-version 1 | jq -r '.packages[]|select(.name == "'"${1}"'")|.version' 53 | } 54 | 55 | check_version_online() { 56 | curl -s "https://crates.io/api/v1/crates/${1}" | jq -r 'try .versions[]|select(.num == "'"${2}"'").updated_at' 57 | } 58 | 59 | publish() { 60 | echo "Publishing crate $1..." 61 | cargo publish --manifest-path "$(get_manifest_path "${1}")" ${CARGO_PUBLISH_FLAGS} 62 | echo "" 63 | } 64 | 65 | wait_until_available() { 66 | echo "Waiting for crate ${1} to become available via crates.io..." 67 | for retry in {1..5}; do 68 | sleep 5 69 | ONLINE_DATE="$(check_version_online "${1}" "${2}")" 70 | if [ -n "${ONLINE_DATE}" ]; then 71 | echo "Crate ${crate} is now available online" 72 | break 73 | else 74 | if [ "${retry}" == 5 ]; then 75 | echo "ERROR: Crate should have become available by now" 76 | exit 1 77 | else 78 | echo "Not available just yet. Waiting a few seconds..." 79 | fi 80 | fi 81 | done 82 | echo "Waiting an additional 10 seconds for crate to propagate through CDN..." 83 | sleep 10 84 | } 85 | 86 | echo "Checking that each crate compiles" 87 | for crate in ${CRATES}; do 88 | ./check_crates.sh 89 | done 90 | 91 | echo "Attempting to publish crate(s): ${CRATES}" 92 | 93 | # Since crates.io has rate-limit and we always want to publish 94 | # every crate in lockstep, we need to wait 5min every 5 pubs 95 | COUNTER=1 96 | 97 | for crate in ${CRATES}; do 98 | VERSION="$(get_local_version "${crate}")" 99 | ONLINE_DATE="$(check_version_online "${crate}" "${VERSION}")" 100 | echo "${crate} version number: ${VERSION}" 101 | if [ -n "${ONLINE_DATE}" ]; then 102 | echo "${crate} ${VERSION} has already been published at ${ONLINE_DATE}, skipping" 103 | continue 104 | fi 105 | 106 | if [ $COUNTER -eq 5 ]; then 107 | echo "Reached 5 crates, waiting for 5 minutes..." 108 | sleep 300 # Wait for 5 minutes 109 | COUNTER=1 # Reset the counter 110 | fi 111 | 112 | publish "${crate}" 113 | wait_until_available "${crate}" "${VERSION}" 114 | ((COUNTER++)) 115 | done 116 | 117 | 118 | echo "Attempting to publish crate(s): ${CRATES}" 119 | 120 | # Since crates.io has rate-limit and we always want to publish 121 | # every crate in lockstep, we need to wait 5min every 5 pubs 122 | COUNTER=1 123 | 124 | for crate in ${CRATES}; do 125 | VERSION="$(get_local_version "${crate}")" 126 | ONLINE_DATE="$(check_version_online "${crate}" "${VERSION}")" 127 | echo "${crate} version number: ${VERSION}" 128 | if [ -n "${ONLINE_DATE}" ]; then 129 | echo "${crate} ${VERSION} has already been published at ${ONLINE_DATE}, skipping" 130 | continue 131 | fi 132 | 133 | if [ $COUNTER -eq 5 ]; then 134 | echo "Reached 5 crates, waiting for 5 minutes..." 135 | sleep 300 # Wait for 5 minutes 136 | COUNTER=1 # Reset the counter 137 | fi 138 | 139 | publish "${crate}" 140 | wait_until_available "${crate}" "${VERSION}" 141 | ((COUNTER++)) 142 | done 143 | --------------------------------------------------------------------------------