diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 30fdcc648..bddd90801 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -70,7 +70,7 @@ jobs: - name: Add wasm toolchain run: | - rustup update nightly + rustup update nightly-2024-05-20 rustup target add wasm32-unknown-unknown --toolchain nightly rustup target add wasm32-unknown-unknown rustup component add rust-src @@ -129,7 +129,7 @@ jobs: - name: Add wasm toolchain run: | - rustup update nightly + rustup update nightly-2024-05-20 rustup target add wasm32-unknown-unknown --toolchain nightly rustup target add wasm32-unknown-unknown rustup component add rust-src @@ -150,7 +150,7 @@ jobs: - name: Install toolchain uses: dtolnay/rust-toolchain@nightly with: - toolchain: nightly + toolchain: nightly-2024-05-20 components: rustfmt - name: Cargo fmt @@ -198,7 +198,7 @@ jobs: - name: Add wasm toolchain run: | - rustup update nightly + rustup update nightly-2024-05-20 rustup target add wasm32-unknown-unknown --toolchain nightly rustup target add wasm32-unknown-unknown rustup component add rust-src @@ -247,7 +247,7 @@ jobs: - name: Add wasm toolchain run: | - rustup update nightly + rustup update nightly-2024-05-20 rustup target add wasm32-unknown-unknown --toolchain nightly rustup target add wasm32-unknown-unknown rustup component add rust-src diff --git a/Cargo.lock b/Cargo.lock index 23f6fb0a4..5f7fb5b83 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -679,7 +679,7 @@ dependencies = [ "proc-macro2", "quote", "syn 1.0.109", - "synstructure", + "synstructure 0.12.6", ] [[package]] @@ -4737,7 +4737,7 @@ dependencies = [ "proc-macro2", "quote", "syn 1.0.109", - "synstructure", + "synstructure 0.12.6", ] [[package]] @@ -6712,6 +6712,7 @@ dependencies = [ "ismp", "ismp-solidity-abi", "js-sys", + "mmr-primitives", "pallet-ismp", "parity-scale-codec", "primitive-types", @@ -6719,6 +6720,7 @@ dependencies = [ "serde-wasm-bindgen", "serde_json", "sp-core 28.0.0", + "sp-mmr-primitives", "substrate-state-machine", "subxt", "subxt-utils", @@ -6726,6 +6728,7 @@ dependencies = [ "tracing", "tracing-subscriber 0.3.18", "tracing-wasm", + "url", "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-test", @@ -6756,6 +6759,124 @@ dependencies = [ "cc", ] +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f8ac670d7422d7f76b32e17a5db556510825b29ec9154f235977c9caba61036" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -6775,12 +6896,14 @@ dependencies = [ [[package]] name = "idna" -version = "0.5.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "4716a3a0933a1d01c2f72450e89596eb51dd34ef3c211ccd875acdf1f8fe47ed" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "icu_normalizer", + "icu_properties", + "smallvec", + "utf8_iter", ] [[package]] @@ -8427,6 +8550,12 @@ dependencies = [ "keystream", ] +[[package]] +name = "litemap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" + [[package]] name = "lock_api" version = "0.4.12" @@ -9365,7 +9494,7 @@ dependencies = [ "proc-macro2", "quote", "syn 1.0.109", - "synstructure", + "synstructure 0.12.6", ] [[package]] @@ -9390,7 +9519,7 @@ dependencies = [ "proc-macro2", "quote", "syn 1.0.109", - "synstructure", + "synstructure 0.12.6", ] [[package]] @@ -11013,6 +11142,7 @@ dependencies = [ "staging-xcm-executor", "substrate-state-machine", "subxt", + "subxt-utils", "tokio", "trie-db 0.28.0", "xcm-simulator", @@ -20112,6 +20242,17 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + [[package]] name = "system-configuration" version = "0.5.1" @@ -20606,6 +20747,16 @@ dependencies = [ "crunchy", ] +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -21616,12 +21767,12 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.0" +version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +checksum = "f7c25da092f0a868cdf09e8674cd3b7ef3a7d92a24253e663a2fb85e2496de56" dependencies = [ "form_urlencoded", - "idna 0.5.0", + "idna 1.0.0", "percent-encoding", "serde", ] @@ -21656,6 +21807,18 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" version = "0.2.1" @@ -22783,6 +22946,18 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + [[package]] name = "ws_stream_wasm" version = "0.7.4" @@ -22953,6 +23128,30 @@ dependencies = [ "time", ] +[[package]] +name = "yoke" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", + "synstructure 0.13.1", +] + [[package]] name = "zerocopy" version = "0.7.32" @@ -22973,6 +23172,27 @@ dependencies = [ "syn 2.0.60", ] +[[package]] +name = "zerofrom" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", + "synstructure 0.13.1", +] + [[package]] name = "zeroize" version = "1.7.0" @@ -22993,6 +23213,28 @@ dependencies = [ "syn 2.0.60", ] +[[package]] +name = "zerovec" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb2cc8827d6c0994478a15c53f374f46fbd41bea663d809b14744bc42e6b109c" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97cf56601ee5052b4417d90c8755c6683473c926039908196cf35d99f893ebe7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + [[package]] name = "zip" version = "0.6.6" diff --git a/modules/hyperclient/Cargo.toml b/modules/hyperclient/Cargo.toml index 9b29be0a7..0834e6f0e 100644 --- a/modules/hyperclient/Cargo.toml +++ b/modules/hyperclient/Cargo.toml @@ -31,6 +31,7 @@ wasm-timer = { package = "fluvio-wasm-timer", version = "0.2.5" } hashbrown = { version = "0.14.3", features = ["serde"] } primitive-types = { version = "0.12.2", default-features = false, features = ["serde"] } tracing = { version = "0.1.40", default-features = false } +url = "2.5.1" tracing-wasm = "0.2.1" console_error_panic_hook = "0.1.7" @@ -45,6 +46,8 @@ substrate-state-machine = { workspace = true } ismp-solidity-abi = { workspace = true } ethereum-triedb = { workspace = true } subxt-utils = { workspace = true } +mmr-primitives = { workspace = true } +sp-mmr-primitives = { workspace = true } hex = "0.4.3" gql_client = "=1.0.7" diff --git a/modules/hyperclient/src/any_client.rs b/modules/hyperclient/src/any_client.rs new file mode 100644 index 000000000..9eda240aa --- /dev/null +++ b/modules/hyperclient/src/any_client.rs @@ -0,0 +1,269 @@ +use subxt_utils::{BlakeSubstrateChain, Hyperbridge}; + +use crate::providers::{evm::EvmClient, interface::Client, substrate::SubstrateClient}; + +#[derive(Clone)] +pub enum AnyClient { + Evm(EvmClient), + BlakeSubstrateChain(SubstrateClient), + KeccakSubstrateChain(SubstrateClient), +} + +impl Client for AnyClient { + async fn query_latest_block_height(&self) -> Result { + match self { + AnyClient::Evm(inner) => inner.query_latest_block_height().await, + AnyClient::BlakeSubstrateChain(inner) => inner.query_latest_block_height().await, + AnyClient::KeccakSubstrateChain(inner) => inner.query_latest_block_height().await, + } + } + + fn state_machine_id(&self) -> ismp::consensus::StateMachineId { + match self { + AnyClient::Evm(inner) => inner.state_machine_id(), + AnyClient::BlakeSubstrateChain(inner) => inner.state_machine_id(), + AnyClient::KeccakSubstrateChain(inner) => inner.state_machine_id(), + } + } + + async fn query_timestamp(&self) -> Result { + match self { + AnyClient::Evm(inner) => inner.query_timestamp().await, + AnyClient::BlakeSubstrateChain(inner) => inner.query_timestamp().await, + AnyClient::KeccakSubstrateChain(inner) => inner.query_timestamp().await, + } + } + + async fn query_request_receipt( + &self, + request_hash: sp_core::H256, + ) -> Result { + match self { + AnyClient::Evm(inner) => inner.query_request_receipt(request_hash).await, + AnyClient::BlakeSubstrateChain(inner) => + inner.query_request_receipt(request_hash).await, + AnyClient::KeccakSubstrateChain(inner) => + inner.query_request_receipt(request_hash).await, + } + } + + async fn query_state_proof( + &self, + at: u64, + key: Vec>, + ) -> Result, anyhow::Error> { + match self { + AnyClient::Evm(inner) => inner.query_state_proof(at, key).await, + AnyClient::BlakeSubstrateChain(inner) => inner.query_state_proof(at, key).await, + AnyClient::KeccakSubstrateChain(inner) => inner.query_state_proof(at, key).await, + } + } + + async fn query_requests_proof( + &self, + at: u64, + keys: Vec, + ) -> Result, anyhow::Error> { + match self { + AnyClient::Evm(inner) => inner.query_requests_proof(at, keys).await, + AnyClient::BlakeSubstrateChain(inner) => inner.query_requests_proof(at, keys).await, + AnyClient::KeccakSubstrateChain(inner) => inner.query_requests_proof(at, keys).await, + } + } + + async fn query_responses_proof( + &self, + at: u64, + keys: Vec, + ) -> Result, anyhow::Error> { + match self { + AnyClient::Evm(inner) => inner.query_responses_proof(at, keys).await, + AnyClient::BlakeSubstrateChain(inner) => inner.query_responses_proof(at, keys).await, + AnyClient::KeccakSubstrateChain(inner) => inner.query_responses_proof(at, keys).await, + } + } + + async fn query_response_receipt( + &self, + request_commitment: sp_core::H256, + ) -> Result { + match self { + AnyClient::Evm(inner) => inner.query_response_receipt(request_commitment).await, + AnyClient::BlakeSubstrateChain(inner) => + inner.query_response_receipt(request_commitment).await, + AnyClient::KeccakSubstrateChain(inner) => + inner.query_response_receipt(request_commitment).await, + } + } + + async fn ismp_events_stream( + &self, + item: crate::providers::interface::RequestOrResponse, + initial_height: u64, + ) -> Result< + crate::types::BoxStream>, + anyhow::Error, + > { + match self { + AnyClient::Evm(inner) => inner.ismp_events_stream(item, initial_height).await, + AnyClient::BlakeSubstrateChain(inner) => + inner.ismp_events_stream(item, initial_height).await, + AnyClient::KeccakSubstrateChain(inner) => + inner.ismp_events_stream(item, initial_height).await, + } + } + + async fn query_ismp_event( + &self, + range: std::ops::RangeInclusive, + ) -> Result>, anyhow::Error> + { + match self { + AnyClient::Evm(inner) => inner.query_ismp_event(range).await, + AnyClient::BlakeSubstrateChain(inner) => inner.query_ismp_event(range).await, + AnyClient::KeccakSubstrateChain(inner) => inner.query_ismp_event(range).await, + } + } + + async fn post_request_handled_stream( + &self, + commitment: sp_core::H256, + initial_height: u64, + ) -> Result< + crate::types::BoxStream< + crate::providers::interface::WithMetadata< + ismp_solidity_abi::evm_host::PostRequestHandledFilter, + >, + >, + anyhow::Error, + > { + match self { + AnyClient::Evm(inner) => + inner.post_request_handled_stream(commitment, initial_height).await, + AnyClient::BlakeSubstrateChain(inner) => + inner.post_request_handled_stream(commitment, initial_height).await, + AnyClient::KeccakSubstrateChain(inner) => + inner.post_request_handled_stream(commitment, initial_height).await, + } + } + + async fn query_latest_state_machine_height( + &self, + state_machine: ismp::consensus::StateMachineId, + ) -> Result { + match self { + AnyClient::Evm(inner) => inner.query_latest_state_machine_height(state_machine).await, + AnyClient::BlakeSubstrateChain(inner) => + inner.query_latest_state_machine_height(state_machine).await, + AnyClient::KeccakSubstrateChain(inner) => + inner.query_latest_state_machine_height(state_machine).await, + } + } + + async fn query_state_machine_commitment( + &self, + id: ismp::consensus::StateMachineHeight, + ) -> Result { + match self { + AnyClient::Evm(inner) => inner.query_state_machine_commitment(id).await, + AnyClient::BlakeSubstrateChain(inner) => inner.query_state_machine_commitment(id).await, + AnyClient::KeccakSubstrateChain(inner) => + inner.query_state_machine_commitment(id).await, + } + } + + async fn state_machine_update_notification( + &self, + counterparty_state_id: ismp::consensus::StateMachineId, + ) -> Result< + crate::types::BoxStream< + crate::providers::interface::WithMetadata, + >, + anyhow::Error, + > { + match self { + AnyClient::Evm(inner) => + inner.state_machine_update_notification(counterparty_state_id).await, + AnyClient::BlakeSubstrateChain(inner) => + inner.state_machine_update_notification(counterparty_state_id).await, + AnyClient::KeccakSubstrateChain(inner) => + inner.state_machine_update_notification(counterparty_state_id).await, + } + } + + fn request_commitment_full_key(&self, commitment: sp_core::H256) -> Vec { + match self { + AnyClient::Evm(inner) => inner.request_commitment_full_key(commitment), + AnyClient::BlakeSubstrateChain(inner) => inner.request_commitment_full_key(commitment), + AnyClient::KeccakSubstrateChain(inner) => inner.request_commitment_full_key(commitment), + } + } + + fn request_receipt_full_key(&self, commitment: sp_core::H256) -> Vec { + match self { + AnyClient::Evm(inner) => inner.request_receipt_full_key(commitment), + AnyClient::BlakeSubstrateChain(inner) => inner.request_receipt_full_key(commitment), + AnyClient::KeccakSubstrateChain(inner) => inner.request_receipt_full_key(commitment), + } + } + + fn response_commitment_full_key(&self, commitment: sp_core::H256) -> Vec { + match self { + AnyClient::Evm(inner) => inner.response_commitment_full_key(commitment), + AnyClient::BlakeSubstrateChain(inner) => inner.response_commitment_full_key(commitment), + AnyClient::KeccakSubstrateChain(inner) => + inner.response_commitment_full_key(commitment), + } + } + + fn response_receipt_full_key(&self, commitment: sp_core::H256) -> Vec { + match self { + AnyClient::Evm(inner) => inner.response_receipt_full_key(commitment), + AnyClient::BlakeSubstrateChain(inner) => inner.response_receipt_full_key(commitment), + AnyClient::KeccakSubstrateChain(inner) => inner.response_receipt_full_key(commitment), + } + } + + fn encode(&self, msg: ismp::messaging::Message) -> Result, anyhow::Error> { + match self { + AnyClient::Evm(inner) => inner.encode(msg), + AnyClient::BlakeSubstrateChain(inner) => inner.encode(msg), + AnyClient::KeccakSubstrateChain(inner) => inner.encode(msg), + } + } + + async fn submit( + &self, + msg: ismp::messaging::Message, + ) -> Result { + match self { + AnyClient::Evm(inner) => inner.submit(msg).await, + AnyClient::BlakeSubstrateChain(inner) => inner.submit(msg).await, + AnyClient::KeccakSubstrateChain(inner) => inner.submit(msg).await, + } + } + + async fn query_state_machine_update_time( + &self, + height: ismp::consensus::StateMachineHeight, + ) -> Result { + match self { + AnyClient::Evm(inner) => inner.query_state_machine_update_time(height).await, + AnyClient::BlakeSubstrateChain(inner) => + inner.query_state_machine_update_time(height).await, + AnyClient::KeccakSubstrateChain(inner) => + inner.query_state_machine_update_time(height).await, + } + } + + async fn query_challenge_period( + &self, + id: ismp::consensus::ConsensusStateId, + ) -> Result { + match self { + AnyClient::Evm(inner) => inner.query_challenge_period(id).await, + AnyClient::BlakeSubstrateChain(inner) => inner.query_challenge_period(id).await, + AnyClient::KeccakSubstrateChain(inner) => inner.query_challenge_period(id).await, + } + } +} diff --git a/modules/hyperclient/src/indexing.rs b/modules/hyperclient/src/indexing.rs index d250044ab..fd02eb0cb 100644 --- a/modules/hyperclient/src/indexing.rs +++ b/modules/hyperclient/src/indexing.rs @@ -1,5 +1,9 @@ #![allow(non_camel_case_types)] -use crate::{types::EventMetadata, MessageStatusWithMetadata}; +use crate::{ + internals::{encode_request_call_data, encode_response_call_data}, + types::EventMetadata, + HyperClient, MessageStatusWithMetadata, +}; use anyhow::anyhow; use ismp::{ host::{Ethereum, StateMachine}, @@ -173,125 +177,145 @@ pub type BigInt = primitive_types::U256; pub async fn query_request_status_from_indexer( request: Request, + hyperclient: &HyperClient, ) -> Result, anyhow::Error> { let commitment = hash_request::(&request); let id = format!("{commitment:?}"); - let indexer_api = std::env::var("INDEXER_URL").unwrap_or("http://localhost:3000".to_string()); - - let client = Client::new(indexer_api); - let vars = RequestResponseVariables { id }; - let response_body = client - .query_with_vars::(REQUEST_QUERY, vars) - .await - .map_err(|e| anyhow!("Failed to query request from indexer {e:?}"))?; - - let mut metadata = response_body - .ok_or_else(|| anyhow!("Request not found in indexer db"))? - .request - .status_metadata - .nodes - .into_iter() - .collect::>(); - metadata.sort_by(|a, b| status_weight(&a.status).cmp(&status_weight(&b.status))); - - if let Some(latest_status) = metadata.last().cloned() { - // transform to message status with metadata - let StatusMetadataNode { status, transaction_hash, block_number, block_hash, .. } = - latest_status; - - let status = match status { - Status::SOURCE => { - // Try and fetch state machine update for source chain on hyperbridge - let vars = StateMachineUpdateVariables { - chain: SupportedChain::HYPERBRIDGE, - state_machine_id: request.source_chain().to_string(), - height: block_number.parse::()?, - }; - - let response_body = client - .query_with_vars::(STATE_MACHINE_QUERY, vars) - .await - .map_err(|e| { - anyhow!("Failed to query state machine update from indexer {e:?}") - })?; - - let meta = if let Some(data) = response_body.and_then(|data| { - data.state_machine_update_events.and_then(|update| update.nodes.get(0).cloned()) - }) { - EventMetadata { - block_hash: H256::from_slice(&from_hex(&data.block_hash)?), - transaction_hash: H256::from_slice(&from_hex(&data.transaction_hash)?), - block_number: data.block_number.low_u64(), - } - } else { - Default::default() - }; - - MessageStatusWithMetadata::SourceFinalized { - finalized_height: block_number.parse()?, - meta, - } - }, - Status::MESSAGE_RELAYED => { - // Try and fetch state machine update for hyperbridge on destination chain - let vars = StateMachineUpdateVariables { - chain: { - match request.dest_chain() { - StateMachine::Ethereum(Ethereum::ExecutionLayer) => - SupportedChain::ETHE, - StateMachine::Ethereum(Ethereum::Base) => SupportedChain::BASE, - StateMachine::Ethereum(Ethereum::Arbitrum) => SupportedChain::ARBI, - StateMachine::Ethereum(Ethereum::Optimism) => SupportedChain::OPTI, - StateMachine::Bsc => SupportedChain::BSC, - StateMachine::Polygon => SupportedChain::POLY, - _ => Err(anyhow!("Unsupported chain for indexer"))?, + if let Some(indexer_api) = hyperclient.get_indexer_url() { + let client = Client::new(indexer_api); + let vars = RequestResponseVariables { id }; + let response_body = client + .query_with_vars::(REQUEST_QUERY, vars) + .await + .map_err(|e| anyhow!("Failed to query request from indexer {e:?}"))?; + + let mut metadata = response_body + .ok_or_else(|| anyhow!("Request not found in indexer db"))? + .request + .status_metadata + .nodes + .into_iter() + .collect::>(); + metadata.sort_by(|a, b| status_weight(&a.status).cmp(&status_weight(&b.status))); + + if let Some(latest_status) = metadata.last().cloned() { + // transform to message status with metadata + let StatusMetadataNode { status, transaction_hash, block_number, block_hash, .. } = + latest_status; + + let status = match status { + Status::SOURCE => { + // Try and fetch state machine update for source chain on hyperbridge + let vars = StateMachineUpdateVariables { + chain: SupportedChain::HYPERBRIDGE, + state_machine_id: request.source_chain().to_string(), + height: block_number.parse::()?, + }; + + let response_body = client + .query_with_vars::(STATE_MACHINE_QUERY, vars) + .await + .map_err(|e| { + anyhow!("Failed to query state machine update from indexer {e:?}") + })?; + + let meta = if let Some(data) = response_body.and_then(|data| { + data.state_machine_update_events + .and_then(|update| update.nodes.get(0).cloned()) + }) { + EventMetadata { + block_hash: H256::from_slice(&from_hex(&data.block_hash)?), + transaction_hash: H256::from_slice(&from_hex(&data.transaction_hash)?), + block_number: data.block_number.low_u64(), } - }, - state_machine_id: request.dest_chain().to_string(), - height: block_number.parse::()?, - }; - let response_body = client - .query_with_vars::(STATE_MACHINE_QUERY, vars) - .await - .map_err(|e| { - anyhow!("Failed to query state machine update from indexer {e:?}") - })?; - - if let Some(data) = response_body.and_then(|data| { - data.state_machine_update_events.and_then(|update| update.nodes.get(0).cloned()) - }) { - let meta = EventMetadata { - block_hash: H256::from_slice(&from_hex(&data.block_hash)?), - transaction_hash: H256::from_slice(&from_hex(&data.transaction_hash)?), - block_number: data.block_number.low_u64(), + } else { + Default::default() }; - MessageStatusWithMetadata::HyperbridgeFinalized { - finalized_height: data.height.low_u64(), + MessageStatusWithMetadata::SourceFinalized { + finalized_height: block_number.parse()?, meta, } - } else { - MessageStatusWithMetadata::HyperbridgeDelivered { - meta: EventMetadata { - block_hash: H256::from_slice(&from_hex(&block_hash)?), - transaction_hash: H256::from_slice(&from_hex(&transaction_hash)?), - block_number: block_number.parse()?, + }, + Status::MESSAGE_RELAYED => { + // Try and fetch state machine update for hyperbridge on destination chain + let vars = StateMachineUpdateVariables { + chain: { + match request.dest_chain() { + StateMachine::Ethereum(Ethereum::ExecutionLayer) => + SupportedChain::ETHE, + StateMachine::Ethereum(Ethereum::Base) => SupportedChain::BASE, + StateMachine::Ethereum(Ethereum::Arbitrum) => SupportedChain::ARBI, + StateMachine::Ethereum(Ethereum::Optimism) => SupportedChain::OPTI, + StateMachine::Bsc => SupportedChain::BSC, + StateMachine::Polygon => SupportedChain::POLY, + _ => Err(anyhow!("Unsupported chain for indexer"))?, + } }, + state_machine_id: request.dest_chain().to_string(), + height: block_number.parse::()?, + }; + let response_body = client + .query_with_vars::(STATE_MACHINE_QUERY, vars) + .await + .map_err(|e| { + anyhow!("Failed to query state machine update from indexer {e:?}") + })?; + + if let Some(data) = response_body.and_then(|data| { + data.state_machine_update_events + .and_then(|update| update.nodes.get(0).cloned()) + }) { + let meta = EventMetadata { + block_hash: H256::from_slice(&from_hex(&data.block_hash)?), + transaction_hash: H256::from_slice(&from_hex(&data.transaction_hash)?), + block_number: data.block_number.low_u64(), + }; + + let calldata = match request { + Request::Post(post) => { + let dest_client = hyperclient.dest.clone(); + let hyperbridge = hyperclient.hyperbridge.clone(); + encode_request_call_data( + &hyperbridge, + &dest_client, + post, + commitment, + data.height.low_u64(), + ) + .await? + }, + _ => Default::default(), + }; + + MessageStatusWithMetadata::HyperbridgeFinalized { + finalized_height: data.height.low_u64(), + meta, + calldata, + } + } else { + MessageStatusWithMetadata::HyperbridgeDelivered { + meta: EventMetadata { + block_hash: H256::from_slice(&from_hex(&block_hash)?), + transaction_hash: H256::from_slice(&from_hex(&transaction_hash)?), + block_number: block_number.parse()?, + }, + } } - } - }, - Status::DEST => MessageStatusWithMetadata::DestinationDelivered { - meta: EventMetadata { - block_hash: H256::from_slice(&from_hex(&block_hash)?), - transaction_hash: H256::from_slice(&from_hex(&transaction_hash)?), - block_number: block_number.parse()?, }, - }, - Status::TIMED_OUT => MessageStatusWithMetadata::Timeout, - Status::Other(_) => MessageStatusWithMetadata::Pending, - }; - return Ok(Some(status)) + Status::DEST => MessageStatusWithMetadata::DestinationDelivered { + meta: EventMetadata { + block_hash: H256::from_slice(&from_hex(&block_hash)?), + transaction_hash: H256::from_slice(&from_hex(&transaction_hash)?), + block_number: block_number.parse()?, + }, + }, + Status::TIMED_OUT => MessageStatusWithMetadata::Timeout, + Status::Other(_) => MessageStatusWithMetadata::Pending, + }; + return Ok(Some(status)) + } } Ok(None) @@ -309,126 +333,146 @@ fn status_weight(status: &Status) -> u8 { pub async fn query_response_status_from_indexer( response: Response, + hyperclient: &HyperClient, ) -> Result, anyhow::Error> { let commitment = hash_response::(&response); let id = format!("{commitment:?}"); - let indexer_api = std::env::var("INDEXER_URL").unwrap_or("http://localhost:3000".to_string()); - - let client = Client::new(indexer_api); - let vars = RequestResponseVariables { id }; - let response_body = client - .query_with_vars::(RESPONSE_QUERY, vars) - .await - .map_err(|e| anyhow!("Failed to query request from indexer {e:?}"))?; - - let mut metadata = response_body - .ok_or_else(|| anyhow!("Request not found in indexer db"))? - .response - .status_metadata - .nodes - .into_iter() - .collect::>(); - metadata.sort_by(|a, b| status_weight(&a.status).cmp(&status_weight(&b.status))); - - if let Some(latest_status) = metadata.last().cloned() { - // transform to message status with metadata - let StatusMetadataNode { status, transaction_hash, block_number, block_hash, .. } = - latest_status; - - let status = match status { - Status::SOURCE => { - // Try and fetch state machine update for source chain on hyperbridge - let vars = StateMachineUpdateVariables { - chain: SupportedChain::HYPERBRIDGE, - state_machine_id: response.source_chain().to_string(), - height: block_number.parse::()?, - }; - - let response_body = client - .query_with_vars::(STATE_MACHINE_QUERY, vars) - .await - .map_err(|e| { - anyhow!("Failed to query state machine update from indexer {e:?}") - })?; - - let meta = if let Some(data) = response_body.and_then(|data| { - data.state_machine_update_events.and_then(|update| update.nodes.get(0).cloned()) - }) { - EventMetadata { - block_hash: H256::from_slice(&from_hex(&data.block_hash)?), - transaction_hash: H256::from_slice(&from_hex(&data.transaction_hash)?), - block_number: data.block_number.low_u64(), - } - } else { - Default::default() - }; - - MessageStatusWithMetadata::SourceFinalized { - finalized_height: block_number.parse()?, - meta, - } - }, - Status::MESSAGE_RELAYED => { - // Try and fetch state machine update for hyperbridge on destination chain - let vars = StateMachineUpdateVariables { - chain: { - match response.dest_chain() { - StateMachine::Ethereum(Ethereum::ExecutionLayer) => - SupportedChain::ETHE, - StateMachine::Ethereum(Ethereum::Base) => SupportedChain::BASE, - StateMachine::Ethereum(Ethereum::Arbitrum) => SupportedChain::ARBI, - StateMachine::Ethereum(Ethereum::Optimism) => SupportedChain::OPTI, - StateMachine::Bsc => SupportedChain::BSC, - StateMachine::Polygon => SupportedChain::POLY, - _ => Err(anyhow!("Unsupported chain for indexer"))?, + if let Some(indexer_api) = hyperclient.get_indexer_url() { + let client = Client::new(indexer_api); + let vars = RequestResponseVariables { id }; + let response_body = client + .query_with_vars::(RESPONSE_QUERY, vars) + .await + .map_err(|e| anyhow!("Failed to query request from indexer {e:?}"))?; + + let mut metadata = response_body + .ok_or_else(|| anyhow!("Request not found in indexer db"))? + .response + .status_metadata + .nodes + .into_iter() + .collect::>(); + metadata.sort_by(|a, b| status_weight(&a.status).cmp(&status_weight(&b.status))); + + if let Some(latest_status) = metadata.last().cloned() { + // transform to message status with metadata + let StatusMetadataNode { status, transaction_hash, block_number, block_hash, .. } = + latest_status; + + let status = match status { + Status::SOURCE => { + // Try and fetch state machine update for source chain on hyperbridge + let vars = StateMachineUpdateVariables { + chain: SupportedChain::HYPERBRIDGE, + state_machine_id: response.source_chain().to_string(), + height: block_number.parse::()?, + }; + + let response_body = client + .query_with_vars::(STATE_MACHINE_QUERY, vars) + .await + .map_err(|e| { + anyhow!("Failed to query state machine update from indexer {e:?}") + })?; + + let meta = if let Some(data) = response_body.and_then(|data| { + data.state_machine_update_events + .and_then(|update| update.nodes.get(0).cloned()) + }) { + EventMetadata { + block_hash: H256::from_slice(&from_hex(&data.block_hash)?), + transaction_hash: H256::from_slice(&from_hex(&data.transaction_hash)?), + block_number: data.block_number.low_u64(), } - }, - state_machine_id: response.dest_chain().to_string(), - height: block_number.parse::()?, - }; - let response_body = client - .query_with_vars::(STATE_MACHINE_QUERY, vars) - .await - .map_err(|e| { - anyhow!("Failed to query state machine update from indexer {e:?}") - })?; - - if let Some(data) = response_body.and_then(|data| { - data.state_machine_update_events.and_then(|update| update.nodes.get(0).cloned()) - }) { - let meta = EventMetadata { - block_hash: H256::from_slice(&from_hex(&data.block_hash)?), - transaction_hash: H256::from_slice(&from_hex(&data.transaction_hash)?), - block_number: data.block_number.low_u64(), + } else { + Default::default() }; - MessageStatusWithMetadata::HyperbridgeFinalized { - finalized_height: data.height.low_u64(), + MessageStatusWithMetadata::SourceFinalized { + finalized_height: block_number.parse()?, meta, } - } else { - MessageStatusWithMetadata::HyperbridgeDelivered { - meta: EventMetadata { - block_hash: H256::from_slice(&from_hex(&block_hash)?), - transaction_hash: H256::from_slice(&from_hex(&transaction_hash)?), - block_number: block_number.parse()?, + }, + Status::MESSAGE_RELAYED => { + // Try and fetch state machine update for hyperbridge on destination chain + let vars = StateMachineUpdateVariables { + chain: { + match response.dest_chain() { + StateMachine::Ethereum(Ethereum::ExecutionLayer) => + SupportedChain::ETHE, + StateMachine::Ethereum(Ethereum::Base) => SupportedChain::BASE, + StateMachine::Ethereum(Ethereum::Arbitrum) => SupportedChain::ARBI, + StateMachine::Ethereum(Ethereum::Optimism) => SupportedChain::OPTI, + StateMachine::Bsc => SupportedChain::BSC, + StateMachine::Polygon => SupportedChain::POLY, + _ => Err(anyhow!("Unsupported chain for indexer"))?, + } }, + state_machine_id: response.dest_chain().to_string(), + height: block_number.parse::()?, + }; + let response_body = client + .query_with_vars::(STATE_MACHINE_QUERY, vars) + .await + .map_err(|e| { + anyhow!("Failed to query state machine update from indexer {e:?}") + })?; + + if let Some(data) = response_body.and_then(|data| { + data.state_machine_update_events + .and_then(|update| update.nodes.get(0).cloned()) + }) { + let meta = EventMetadata { + block_hash: H256::from_slice(&from_hex(&data.block_hash)?), + transaction_hash: H256::from_slice(&from_hex(&data.transaction_hash)?), + block_number: data.block_number.low_u64(), + }; + + let calldata = match response { + Response::Post(post) => { + let dest_client = hyperclient.dest.clone(); + let hyperbridge = &hyperclient.hyperbridge; + encode_response_call_data( + hyperbridge, + &dest_client, + post, + commitment, + data.height.low_u64(), + ) + .await? + }, + _ => Default::default(), + }; + + MessageStatusWithMetadata::HyperbridgeFinalized { + finalized_height: data.height.low_u64(), + meta, + calldata, + } + } else { + MessageStatusWithMetadata::HyperbridgeDelivered { + meta: EventMetadata { + block_hash: H256::from_slice(&from_hex(&block_hash)?), + transaction_hash: H256::from_slice(&from_hex(&transaction_hash)?), + block_number: block_number.parse()?, + }, + } } - } - }, - Status::DEST => MessageStatusWithMetadata::DestinationDelivered { - meta: EventMetadata { - block_hash: H256::from_slice(&from_hex(&block_hash)?), - transaction_hash: H256::from_slice(&from_hex(&transaction_hash)?), - block_number: block_number.parse()?, }, - }, - Status::TIMED_OUT => MessageStatusWithMetadata::Timeout, - Status::Other(_) => MessageStatusWithMetadata::Pending, - }; - return Ok(Some(status)) - } + Status::DEST => MessageStatusWithMetadata::DestinationDelivered { + meta: EventMetadata { + block_hash: H256::from_slice(&from_hex(&block_hash)?), + transaction_hash: H256::from_slice(&from_hex(&transaction_hash)?), + block_number: block_number.parse()?, + }, + }, + Status::TIMED_OUT => MessageStatusWithMetadata::Timeout, + Status::Other(_) => MessageStatusWithMetadata::Pending, + }; + return Ok(Some(status)) + } + }; Ok(None) } diff --git a/modules/hyperclient/src/interfaces.rs b/modules/hyperclient/src/interfaces.rs index 6efd9a81c..c4b1c5865 100644 --- a/modules/hyperclient/src/interfaces.rs +++ b/modules/hyperclient/src/interfaces.rs @@ -27,6 +27,7 @@ pub struct JsClientConfig { pub source: JsChainConfig, pub dest: JsChainConfig, pub hyperbridge: JsHyperbridgeConfig, + pub indexer: String, } impl TryFrom for ClientConfig { @@ -80,6 +81,12 @@ impl TryFrom for ClientConfig { } }; + let indexer = if value.indexer.is_empty() { + None + } else { + Some(url::Url::parse(&value.indexer)?.to_string()) + }; + let to_hyperbridge_config = |val: &JsHyperbridgeConfig| { let conf = SubstrateConfig { rpc_url: val.rpc_url.clone(), @@ -94,7 +101,7 @@ impl TryFrom for ClientConfig { let dest_config = to_config(&value.dest)?; let hyperbridge = to_hyperbridge_config(&value.hyperbridge)?; - Ok(ClientConfig { source: source_config, dest: dest_config, hyperbridge }) + Ok(ClientConfig { source: source_config, dest: dest_config, hyperbridge, indexer }) } } @@ -202,6 +209,7 @@ mod tests { source: ChainConfig::Evm(source_chain.clone()), dest: ChainConfig::Evm(dest_chain.clone()), hyperbridge: ChainConfig::Substrate(hyperbrige_config), + indexer: Some("http://localhost:3000/".to_string()), }; let js_source = JsChainConfig { @@ -222,8 +230,12 @@ mod tests { let js_hyperbridge = JsHyperbridgeConfig { rpc_url: "ws://127.0.0.1:9990".to_string() }; - let js_client_conf = - JsClientConfig { source: js_source, dest: js_dest, hyperbridge: js_hyperbridge }; + let js_client_conf = JsClientConfig { + source: js_source, + dest: js_dest, + hyperbridge: js_hyperbridge, + indexer: "http://localhost:3000/".to_string(), + }; assert_eq!(config, js_client_conf.try_into().unwrap()); } diff --git a/modules/hyperclient/src/internals.rs b/modules/hyperclient/src/internals.rs index cbb7b5fc3..b5c55cf99 100644 --- a/modules/hyperclient/src/internals.rs +++ b/modules/hyperclient/src/internals.rs @@ -1,9 +1,12 @@ ///! This module contains the internal implementation of HyperClient. use crate::{ - providers::interface::RequestOrResponse, - providers::interface::{wait_for_challenge_period, Client}, - types::{BoxStream, MessageStatus, TimeoutStatus}, - types::{MessageStatusWithMetadata, PostStreamState}, + any_client::AnyClient, + indexing::query_response_status_from_indexer, + providers::{ + interface::{wait_for_challenge_period, Client, Query, RequestOrResponse}, + substrate::SubstrateClient, + }, + types::{BoxStream, MessageStatusWithMetadata, PostStreamState, TimeoutStatus}, HyperClient, Keccak256, }; use anyhow::anyhow; @@ -11,9 +14,11 @@ use ethers::prelude::H160; use futures::{stream, StreamExt}; use ismp::{ consensus::StateMachineHeight, - messaging::{hash_request, Message, Proof, TimeoutMessage}, + messaging::{hash_request, Message, Proof, RequestMessage, ResponseMessage, TimeoutMessage}, router::{Post, PostResponse, Request, Response}, }; +use sp_core::H256; +use subxt_utils::Hyperbridge; use crate::indexing::query_request_status_from_indexer; use ismp::events::Event; @@ -24,71 +29,84 @@ use std::time::Duration; pub async fn query_request_status_internal( client: &HyperClient, post: Post, -) -> Result { +) -> Result { let destination_current_timestamp = client.dest.query_timestamp().await?; let req = Request::Post(post.clone()); let hash = hash_request::(&req); let relayer_address = client.dest.query_request_receipt(hash).await?; - + if let Some(ref status) = query_request_status_from_indexer(Request::Post(post.clone()), client) + .await + .ok() + .flatten() + { + return Ok(status.clone()) + } if relayer_address != H160::zero() { // This means the message has gotten the destination chain - return Ok(MessageStatus::DestinationDelivered); + return Ok(MessageStatusWithMetadata::DestinationDelivered { meta: Default::default() }); } // Checking to see if the messaging has timed-out if destination_current_timestamp.as_secs() >= post.timeout_timestamp { // request timed out before reaching the destination chain - return Ok(MessageStatus::Timeout); + return Ok(MessageStatusWithMetadata::Timeout); } let hyperbridge_current_timestamp = client.hyperbridge.query_timestamp().await?; let relayer = client.hyperbridge.query_request_receipt(hash).await?; if relayer != H160::zero() { - return Ok(MessageStatus::HyperbridgeDelivered); + return Ok(MessageStatusWithMetadata::HyperbridgeDelivered { meta: Default::default() }); } if hyperbridge_current_timestamp.as_secs() > post.timeout_timestamp { // the request timed out before getting to hyper bridge - return Ok(MessageStatus::Timeout); + return Ok(MessageStatusWithMetadata::Timeout); } - Ok(MessageStatus::Pending) + Ok(MessageStatusWithMetadata::Pending) } /// `query_response_status_internal` function returns the status of a response pub async fn query_response_status_internal( hyperclient: &HyperClient, post_response: PostResponse, -) -> Result { +) -> Result { let response_destination_timeout = hyperclient.dest.query_timestamp().await?; let res = Response::Post(post_response.clone()); let req_hash = hash_request::(&res.request()); let response_receipt_relayer = hyperclient.dest.query_response_receipt(req_hash).await?; - + if let Some(ref status) = + query_response_status_from_indexer(Response::Post(post_response.clone()), hyperclient) + .await + .ok() + .flatten() + { + return Ok(status.clone()) + } if response_receipt_relayer != H160::zero() { - return Ok(MessageStatus::DestinationDelivered); + return Ok(MessageStatusWithMetadata::DestinationDelivered { meta: Default::default() }); } if response_destination_timeout.as_secs() > post_response.timeout_timestamp { // response timed out before reaching the destination chain - return Ok(MessageStatus::Timeout); + return Ok(MessageStatusWithMetadata::Timeout); } let relayer = hyperclient.hyperbridge.query_response_receipt(req_hash).await?; if relayer != H160::zero() { - return Ok(MessageStatus::HyperbridgeDelivered); + return Ok(MessageStatusWithMetadata::HyperbridgeDelivered { meta: Default::default() }); } let hyperbridge_current_timestamp = hyperclient.hyperbridge.latest_timestamp().await?; if hyperbridge_current_timestamp.as_secs() > post_response.timeout_timestamp { // the request timed out before getting to hyper bridge - return Ok(MessageStatus::Timeout); + return Ok(MessageStatusWithMetadata::Timeout); } - Ok(MessageStatus::Pending) + Ok(MessageStatusWithMetadata::Pending) } #[derive(Debug, PartialEq, Eq, Clone, Copy)] @@ -121,6 +139,7 @@ pub async fn timeout_request_stream( let source_client = source_client.clone(); let req = Request::Post(post.clone()); let hash = hash_request::(&req); + async move { let lambda = || async { match state { @@ -351,6 +370,7 @@ pub async fn request_status_stream( let source_client = hyperclient.source.clone(); let dest_client = hyperclient.dest.clone(); let hyperbridge_client = hyperclient.hyperbridge.clone(); + let hyperclient_clone = hyperclient.clone(); let stream = stream::unfold(PostStreamState::Pending, move |post_request_status| { let dest_client = dest_client.clone(); @@ -359,6 +379,7 @@ pub async fn request_status_stream( let req = Request::Post(post.clone()); let hash = hash_request::(&req); let post = post.clone(); + let hyperclient_clone = hyperclient_clone.clone(); async move { let lambda = || async { match post_request_status { @@ -366,10 +387,13 @@ pub async fn request_status_stream( let destination_current_timestamp = dest_client.query_timestamp().await?; let relayer_address = dest_client.query_request_receipt(hash).await?; - if let Some(msg_status) = - query_request_status_from_indexer(req.clone()).await.ok().flatten() + if let Some(ref msg_status) = + query_request_status_from_indexer(req.clone(), &hyperclient_clone) + .await + .ok() + .flatten() { - match &msg_status { + match msg_status { MessageStatusWithMetadata::SourceFinalized { finalized_height, .. @@ -508,10 +532,13 @@ pub async fn request_status_stream( PostStreamState::SourceFinalized(finalized_height) => { let relayer = hyperbridge_client.query_request_receipt(hash).await?; - if let Some(msg_status) = - query_request_status_from_indexer(req.clone()).await.ok().flatten() + if let Some(ref msg_status) = + query_request_status_from_indexer(req.clone(), &hyperclient_clone) + .await + .ok() + .flatten() { - match &msg_status { + match msg_status { MessageStatusWithMetadata::HyperbridgeDelivered { meta } => { return Ok::< Option<(Result<_, anyhow::Error>, PostStreamState)>, @@ -602,10 +629,13 @@ pub async fn request_status_stream( PostStreamState::HyperbridgeDelivered(height) => { let res = dest_client.query_request_receipt(hash).await?; - if let Some(msg_status) = - query_request_status_from_indexer(req.clone()).await.ok().flatten() + if let Some(ref msg_status) = + query_request_status_from_indexer(req.clone(), &hyperclient_clone) + .await + .ok() + .flatten() { - match &msg_status { + match msg_status { MessageStatusWithMetadata::HyperbridgeFinalized { finalized_height, .. @@ -660,19 +690,39 @@ pub async fn request_status_stream( }); let Some((meta, update)) = meta else { + let calldata = + encode_request_message_and_wait_for_challenge_period( + &hyperbridge_client, + &dest_client, + post.clone(), + hash, + latest_hyperbridge_height, + ) + .await?; return Ok(Some(( Ok(MessageStatusWithMetadata::HyperbridgeFinalized { finalized_height: height, meta: Default::default(), + calldata, }), PostStreamState::HyperbridgeFinalized(latest_height), ))); }; + let calldata = encode_request_message_and_wait_for_challenge_period( + &hyperbridge_client, + &dest_client, + post.clone(), + hash, + update.latest_height, + ) + .await?; + return Ok(Some(( Ok(MessageStatusWithMetadata::HyperbridgeFinalized { finalized_height: update.latest_height, meta: meta.clone(), + calldata, }), PostStreamState::HyperbridgeFinalized(meta.block_number), ))); @@ -687,10 +737,20 @@ pub async fn request_status_stream( match update { Ok(event) => if event.event.latest_height >= height { + let calldata = + encode_request_message_and_wait_for_challenge_period( + &hyperbridge_client, + &dest_client, + post.clone(), + hash, + event.event.latest_height, + ) + .await?; return Ok(Some(( Ok(MessageStatusWithMetadata::HyperbridgeFinalized { finalized_height: event.event.latest_height, meta: event.meta, + calldata, }), PostStreamState::HyperbridgeFinalized( event.meta.block_number, @@ -717,7 +777,10 @@ pub async fn request_status_stream( let res = dest_client.query_request_receipt(hash).await?; if let Some(msg_status) = - query_request_status_from_indexer(req.clone()).await.ok().flatten() + query_request_status_from_indexer(req.clone(), &hyperclient_clone) + .await + .ok() + .flatten() { match &msg_status { MessageStatusWithMetadata::DestinationDelivered { .. } => { @@ -827,3 +890,74 @@ pub async fn request_timeout_stream( Box::pin(stream) } + +pub async fn encode_request_call_data( + hyperbridge: &SubstrateClient, + dest_client: &AnyClient, + post: Post, + commitment: H256, + height: u64, +) -> Result, anyhow::Error> { + let proof = hyperbridge + .query_requests_proof( + height, + vec![Query { source_chain: post.source, dest_chain: post.dest, commitment }], + ) + .await?; + let proof_height = StateMachineHeight { id: hyperbridge.state_machine, height }; + + let message = Message::Request(RequestMessage { + requests: vec![post.clone()], + proof: Proof { height: proof_height, proof }, + signer: H160::zero().0.to_vec(), + }); + let calldata = dest_client.encode(message)?; + Ok(calldata) +} + +pub async fn encode_response_call_data( + hyperbridge: &SubstrateClient, + dest_client: &AnyClient, + post_response: PostResponse, + commitment: H256, + height: u64, +) -> Result, anyhow::Error> { + let proof = hyperbridge + .query_responses_proof( + height, + vec![Query { + source_chain: post_response.post.dest, + dest_chain: post_response.post.source, + commitment, + }], + ) + .await?; + let proof_height = StateMachineHeight { id: hyperbridge.state_machine, height }; + + let message = Message::Response(ResponseMessage { + datagram: ismp::router::RequestResponse::Response(vec![Response::Post(post_response)]), + proof: Proof { height: proof_height, proof }, + signer: H160::zero().0.to_vec(), + }); + let calldata = dest_client.encode(message)?; + Ok(calldata) +} +// Encodes the call data for the message but waits for the challenge period before yielding +pub async fn encode_request_message_and_wait_for_challenge_period( + hyperbridge: &SubstrateClient, + dest_client: &AnyClient, + post: Post, + commitment: H256, + height: u64, +) -> Result, anyhow::Error> { + let calldata = + encode_request_call_data(hyperbridge, dest_client, post, commitment, height).await?; + let proof_height = StateMachineHeight { id: hyperbridge.state_machine, height }; + let challenge_period = dest_client + .query_challenge_period(hyperbridge.state_machine_id().consensus_state_id) + .await?; + let update_time = dest_client.query_state_machine_update_time(proof_height).await?; + wait_for_challenge_period(dest_client, update_time, challenge_period).await?; + + Ok(calldata) +} diff --git a/modules/hyperclient/src/lib.rs b/modules/hyperclient/src/lib.rs index 93fc41ecc..a5e5c832a 100644 --- a/modules/hyperclient/src/lib.rs +++ b/modules/hyperclient/src/lib.rs @@ -2,7 +2,10 @@ pub mod internals; pub mod providers; +use any_client::AnyClient; +use providers::interface::Client; pub use subxt_utils::gargantua as runtime; +pub mod any_client; pub mod types; pub mod interfaces; @@ -11,12 +14,11 @@ extern crate alloc; extern crate core; use crate::types::ClientConfig; -use anyhow::anyhow; use crate::{ interfaces::{JsClientConfig, JsPost, JsPostResponse}, - providers::{evm::EvmClient, substrate::SubstrateClient}, - types::{ChainConfig, MessageStatusWithMetadata, TimeoutStatus}, + providers::substrate::SubstrateClient, + types::{MessageStatusWithMetadata, TimeoutStatus}, }; use ethers::{types::H256, utils::keccak256}; use futures::StreamExt; @@ -42,6 +44,8 @@ interface IConfig { dest: IChainConfig; // confuration object for hyperbridge hyperbridge: IHyperbridgeConfig; + // Indexer url + indexer: string; } interface IChainConfig { @@ -249,29 +253,25 @@ extern "C" { #[derive(Clone)] pub struct HyperClient { #[wasm_bindgen(skip)] - pub source: EvmClient, + pub source: AnyClient, #[wasm_bindgen(skip)] - pub dest: EvmClient, + pub dest: AnyClient, #[wasm_bindgen(skip)] pub hyperbridge: SubstrateClient, + #[wasm_bindgen(skip)] + pub indexer: Option, } impl HyperClient { /// Initialize the Hyperclient pub async fn new(config: ClientConfig) -> Result { - // todo: we'll need an AnyClient to make this generic - let ChainConfig::Evm(ref source_config) = config.source else { - Err(anyhow!("Expected EvmConfig"))? - }; - let ChainConfig::Evm(ref dest_config) = config.dest else { - Err(anyhow!("Expected EvmConfig"))? - }; let hyperbridge = config.hyperbridge_client().await?; Ok(Self { - source: source_config.into_client().await?, - dest: dest_config.into_client().await?, + source: config.source_chain().await?, + dest: config.dest_chain().await?, hyperbridge, + indexer: config.indexer.clone(), }) } } @@ -292,7 +292,7 @@ impl HyperClient { }) } - /// Queries the status of a request and returns `MessageStatus` + /// Queries the status of a request and returns `MessageStatusWithMetadata` pub async fn query_request_status(&self, request: IPostRequest) -> Result { let lambda = || async move { let post = serde_wasm_bindgen::from_value::(request.into()).unwrap(); @@ -304,12 +304,13 @@ impl HyperClient { lambda().await.map_err(|err: anyhow::Error| { JsError::new(&format!( "Failed to query request status for {:?}->{:?}: {err:?}", - self.source.state_machine, self.dest.state_machine, + self.source.state_machine_id().state_id, + self.dest.state_machine_id().state_id, )) }) } - /// Accepts a post response and returns a `MessageStatus` + /// Accepts a post response and returns a `MessageStatusWithMetadata` pub async fn query_response_status(&self, response: IPostResponse) -> Result { let lambda = || async move { let post = serde_wasm_bindgen::from_value::(response.into()).unwrap(); @@ -321,7 +322,8 @@ impl HyperClient { lambda().await.map_err(|err: anyhow::Error| { JsError::new(&format!( "Failed to query response status for {:?}->{:?}: {err:?}", - self.source.state_machine, self.dest.state_machine, + self.source.state_machine_id().state_id, + self.dest.state_machine_id().state_id, )) }) } @@ -396,6 +398,10 @@ impl HyperClient { JsError::new(&format!("Failed to create post request timeout stream: {err:?}")) }) } + + pub fn get_indexer_url(&self) -> Option { + self.indexer.clone() + } } #[derive(Clone, Default)] diff --git a/modules/hyperclient/src/providers/evm.rs b/modules/hyperclient/src/providers/evm.rs index 1f98f9dee..a1f4f5019 100644 --- a/modules/hyperclient/src/providers/evm.rs +++ b/modules/hyperclient/src/providers/evm.rs @@ -2,12 +2,14 @@ use crate::{ providers::interface::{Client, RequestOrResponse}, types::BoxStream, }; +use codec::{Decode, Encode}; use ethereum_triedb::StorageProof; use ethers::prelude::Middleware; use evm_common::presets::{ REQUEST_COMMITMENTS_SLOT, REQUEST_RECEIPTS_SLOT, RESPONSE_COMMITMENTS_SLOT, RESPONSE_RECEIPTS_SLOT, }; +use sp_mmr_primitives::utils::NodesUtils; use crate::{ providers::interface::WithMetadata, @@ -25,15 +27,22 @@ use ismp::{ consensus::{ConsensusStateId, StateCommitment, StateMachineHeight, StateMachineId}, events::{Event, StateMachineUpdated}, host::StateMachine, - messaging::{Message, TimeoutMessage}, - router::Request, + messaging::{Message, ResponseMessage, TimeoutMessage}, + router::{Request, RequestResponse, Response}, }; use ismp_solidity_abi::{ evm_host::{EvmHost, EvmHostEvents, GetRequest, PostRequestHandledFilter}, - handler::{GetTimeoutMessage, Handler, PostRequestTimeoutMessage, PostResponseTimeoutMessage}, + handler::{ + GetTimeoutMessage, Handler, PostRequestLeaf, PostRequestMessage, PostRequestTimeoutMessage, + PostResponseLeaf, PostResponseMessage, PostResponseTimeoutMessage, Proof, + }, }; +use mmr_primitives::mmr_position_to_k_index; +use pallet_ismp::mmr::{LeafIndexAndPos, Proof as MmrProof}; use std::{collections::BTreeMap, ops::RangeInclusive, sync::Arc}; +use super::interface::Query; + #[derive(Debug, Clone)] pub struct EvmClient { // A WS rpc url of the EVM chain @@ -123,6 +132,55 @@ impl Client for EvmClient { Ok(relayer) } + async fn query_requests_proof(&self, at: u64, keys: Vec) -> Result, Error> { + let keys = keys + .into_iter() + .map(|query| self.request_commitment_key(query.commitment)) + .collect(); + + let proof = self.client.get_proof(self.host_address, keys, Some(at.into())).await?; + let proof = EvmStateProof { + contract_proof: proof.account_proof.into_iter().map(|bytes| bytes.0.into()).collect(), + storage_proof: { + let storage_proofs = proof.storage_proof.into_iter().map(|proof| { + StorageProof::new(proof.proof.into_iter().map(|bytes| bytes.0.into())) + }); + let merged_proofs = StorageProof::merge(storage_proofs); + vec![( + self.host_address.0.to_vec(), + merged_proofs.into_nodes().into_iter().collect(), + )] + .into_iter() + .collect() + }, + }; + Ok(proof.encode()) + } + + async fn query_responses_proof(&self, at: u64, keys: Vec) -> Result, Error> { + let keys = keys + .into_iter() + .map(|query| self.response_commitment_key(query.commitment)) + .collect(); + let proof = self.client.get_proof(self.host_address, keys, Some(at.into())).await?; + let proof = EvmStateProof { + contract_proof: proof.account_proof.into_iter().map(|bytes| bytes.0.into()).collect(), + storage_proof: { + let storage_proofs = proof.storage_proof.into_iter().map(|proof| { + StorageProof::new(proof.proof.into_iter().map(|bytes| bytes.0.into())) + }); + let merged_proofs = StorageProof::merge(storage_proofs); + vec![( + self.host_address.0.to_vec(), + merged_proofs.into_nodes().into_iter().collect(), + )] + .into_iter() + .collect() + }, + }; + Ok(proof.encode()) + } + async fn query_state_proof(&self, at: u64, keys: Vec>) -> Result, Error> { use codec::Encode; let mut map: BTreeMap, Vec>> = BTreeMap::new(); @@ -477,7 +535,111 @@ impl Client for EvmClient { Ok(call.tx.data().cloned().expect("Infallible").to_vec()) }, - _ => Err(anyhow!("Only timeout messages are suported"))?, + + Message::Request(msg) => { + let membership_proof = MmrProof::::decode(&mut msg.proof.proof.as_slice())?; + let mmr_size = NodesUtils::new(membership_proof.leaf_count).size(); + let k_and_leaf_indices = membership_proof + .leaf_indices_and_pos + .into_iter() + .map(|LeafIndexAndPos { pos, leaf_index }| { + let k_index = mmr_position_to_k_index(vec![pos], mmr_size)[0].1; + (k_index, leaf_index) + }) + .collect::>(); + + let mut leaves = msg + .requests + .into_iter() + .zip(k_and_leaf_indices) + .map(|(post, (k_index, leaf_index))| PostRequestLeaf { + request: post.into(), + index: leaf_index.into(), + k_index: k_index.into(), + }) + .collect::>(); + leaves.sort_by_key(|leaf| leaf.index); + let post_message = PostRequestMessage { + proof: Proof { + height: ismp_solidity_abi::shared_types::StateMachineHeight { + state_machine_id: { + match msg.proof.height.id.state_id { + StateMachine::Polkadot(id) | StateMachine::Kusama(id) => + id.into(), + _ => + Err(anyhow!("Expected polkadot or kusama state machines"))?, + } + }, + height: msg.proof.height.height.into(), + }, + multiproof: membership_proof.items.into_iter().map(|node| node.0).collect(), + leaf_count: membership_proof.leaf_count.into(), + }, + requests: leaves, + }; + + let call = contract.handle_post_requests(self.host_address, post_message); + Ok(call.tx.data().cloned().expect("Infallible").to_vec()) + }, + Message::Response(ResponseMessage { datagram, proof, .. }) => { + let membership_proof = MmrProof::::decode(&mut proof.proof.as_slice())?; + let mmr_size = NodesUtils::new(membership_proof.leaf_count).size(); + let k_and_leaf_indices = membership_proof + .leaf_indices_and_pos + .into_iter() + .map(|LeafIndexAndPos { pos, leaf_index }| { + let k_index = mmr_position_to_k_index(vec![pos], mmr_size)[0].1; + (k_index, leaf_index) + }) + .collect::>(); + + match datagram { + RequestResponse::Response(responses) => { + let mut leaves = responses + .into_iter() + .zip(k_and_leaf_indices) + .filter_map(|(res, (k_index, leaf_index))| match res { + Response::Post(res) => Some(PostResponseLeaf { + response: res.into(), + index: leaf_index.into(), + k_index: k_index.into(), + }), + _ => None, + }) + .collect::>(); + leaves.sort_by_key(|leaf| leaf.index); + let message = PostResponseMessage { + proof: Proof { + height: ismp_solidity_abi::shared_types::StateMachineHeight { + state_machine_id: { + match proof.height.id.state_id { + StateMachine::Polkadot(id) | + StateMachine::Kusama(id) => id.into(), + _ => Err(anyhow!( + "Expected polkadot or kusama state machines" + ))?, + } + }, + height: proof.height.height.into(), + }, + multiproof: membership_proof + .items + .into_iter() + .map(|node| node.0) + .collect(), + leaf_count: membership_proof.leaf_count.into(), + }, + responses: leaves, + }; + + let call = contract.handle_post_responses(self.host_address, message); + Ok(call.tx.data().cloned().expect("Infallible").to_vec()) + }, + RequestResponse::Request(..) => + Err(anyhow!("Get requests are not supported yet"))?, + } + }, + _ => Err(anyhow!("Unsupported message"))?, } } diff --git a/modules/hyperclient/src/providers/interface.rs b/modules/hyperclient/src/providers/interface.rs index 7ea18f64f..593149d20 100644 --- a/modules/hyperclient/src/providers/interface.rs +++ b/modules/hyperclient/src/providers/interface.rs @@ -6,6 +6,7 @@ use ethers::{prelude::H256, types::H160}; use ismp::{ consensus::{ConsensusStateId, StateCommitment, StateMachineHeight, StateMachineId}, events::{Event, StateMachineUpdated}, + host::StateMachine, messaging::Message, router::{Post, PostResponse}, }; @@ -19,6 +20,16 @@ pub enum RequestOrResponse { Response(PostResponse), } +/// Provides an interface for accessing new events and ISMP data on the chain which must be +/// relayed to the counterparty chain. + +#[derive(Copy, Clone, Debug)] +pub struct Query { + pub source_chain: StateMachine, + pub dest_chain: StateMachine, + pub commitment: H256, +} + /// Holds an event along with relevant metadata about the event #[derive(Serialize, Deserialize, Clone, Debug)] pub struct WithMetadata { @@ -45,6 +56,22 @@ pub trait Client: Clone + Send + Sync + 'static { async fn query_state_proof(&self, at: u64, key: Vec>) -> Result, anyhow::Error>; + /// Query a requests proof + /// Return the scale encoded proof + async fn query_requests_proof( + &self, + at: u64, + keys: Vec, + ) -> Result, anyhow::Error>; + + /// Query a responses proof + /// Return the scale encoded proof + async fn query_responses_proof( + &self, + at: u64, + keys: Vec, + ) -> Result, anyhow::Error>; + // Query the response receipt from the ISMP host on the destination chain async fn query_response_receipt(&self, request_commitment: H256) -> Result; diff --git a/modules/hyperclient/src/providers/substrate.rs b/modules/hyperclient/src/providers/substrate.rs index 0e1614e8a..7b3042c61 100644 --- a/modules/hyperclient/src/providers/substrate.rs +++ b/modules/hyperclient/src/providers/substrate.rs @@ -17,15 +17,33 @@ use ismp::{ messaging::Message, }; use ismp_solidity_abi::evm_host::PostRequestHandledFilter; -use pallet_ismp::{child_trie::CHILD_TRIE_PREFIX, ResponseReceipt}; +use pallet_ismp::{ + child_trie::{ + request_commitment_storage_key, response_commitment_storage_key, CHILD_TRIE_PREFIX, + }, + mmr::ProofKeys, + ResponseReceipt, +}; use serde::{Deserialize, Serialize}; use sp_core::storage::ChildInfo; use std::ops::RangeInclusive; use substrate_state_machine::StateMachineProof; use subxt::{ - config::Header, rpc::types::StorageData, rpc_params, storage::StorageKey, OnlineClient, + config::Header, rpc::types::StorageData, rpc_params, storage::StorageKey, tx::TxPayload, + OnlineClient, }; +use super::interface::Query; + +/// Contains a scale encoded Mmr Proof or Trie proof +#[derive(Serialize, Deserialize)] +pub struct Proof { + /// Scale encoded `MmrProof` or state trie proof `Vec>` + pub proof: Vec, + /// Height at which proof was recovered + pub height: u32, +} + #[derive(Debug, Clone)] pub struct SubstrateClient { /// RPC url of a hyperbridge node @@ -154,6 +172,87 @@ impl Client for SubstrateClient { Ok(proof.encode()) } + async fn query_requests_proof( + &self, + at: u64, + keys: Vec, + ) -> Result, anyhow::Error> { + if keys.is_empty() { + Err(anyhow!("No queries provided"))? + } + match keys[0].dest_chain { + // Use mmr proofs for queries going to EVM chains + StateMachine::Ethereum(_) | StateMachine::Bsc | StateMachine::Polygon => { + let keys = + ProofKeys::Requests(keys.into_iter().map(|key| key.commitment).collect()); + let params = rpc_params![at, keys]; + let response: Proof = + self.client.rpc().request("ismp_queryMmrProof", params).await?; + Ok(response.proof) + }, + // Use child trie proofs for queries going to substrate chains + StateMachine::Polkadot(_) | + StateMachine::Kusama(_) | + StateMachine::Grandpa(_) | + StateMachine::Beefy(_) => { + let keys: Vec<_> = keys + .into_iter() + .map(|key| request_commitment_storage_key(key.commitment)) + .collect(); + let params = rpc_params![at, keys]; + let response: Proof = + self.client.rpc().request("ismp_queryChildTrieProof", params).await?; + let storage_proof: Vec> = Decode::decode(&mut &*response.proof)?; + let proof = SubstrateStateProof::OverlayProof(StateMachineProof { + hasher: self.hashing.clone(), + storage_proof, + }); + Ok(proof.encode()) + }, + } + } + + async fn query_responses_proof( + &self, + at: u64, + keys: Vec, + ) -> Result, anyhow::Error> { + if keys.is_empty() { + Err(anyhow!("No queries provided"))? + } + + match keys[0].dest_chain { + // Use mmr proofs for queries going to EVM chains + StateMachine::Ethereum(_) | StateMachine::Bsc | StateMachine::Polygon => { + let keys = + ProofKeys::Responses(keys.into_iter().map(|key| key.commitment).collect()); + let params = rpc_params![at, keys]; + let response: Proof = + self.client.rpc().request("ismp_queryMmrProof", params).await?; + Ok(response.proof) + }, + // Use child trie proofs for queries going to substrate chains + StateMachine::Polkadot(_) | + StateMachine::Kusama(_) | + StateMachine::Grandpa(_) | + StateMachine::Beefy(_) => { + let keys: Vec<_> = keys + .into_iter() + .map(|key| response_commitment_storage_key(key.commitment)) + .collect(); + let params = rpc_params![at, keys]; + let response: Proof = + self.client.rpc().request("ismp_queryChildTrieProof", params).await?; + let storage_proof: Vec> = Decode::decode(&mut &*response.proof)?; + let proof = SubstrateStateProof::OverlayProof(StateMachineProof { + hasher: self.hashing.clone(), + storage_proof, + }); + Ok(proof.encode()) + }, + } + } + async fn query_response_receipt(&self, request_commitment: H256) -> Result { let key = self.response_receipt_full_key(request_commitment); let child_storage_key = ChildInfo::new_default(CHILD_TRIE_PREFIX).prefixed_storage_key(); @@ -362,9 +461,17 @@ impl Client for SubstrateClient { fn encode(&self, msg: Message) -> Result, Error> { let call = vec![msg].encode(); - let hyper_bridge_timeout_extrinsic = Extrinsic::new("Ismp", "handle_unsigned", call); - let ext = self.client.tx().create_unsigned(&hyper_bridge_timeout_extrinsic)?; - Ok(ext.into_encoded()) + if let Some(_) = + self.client.metadata().pallet_by_name_err("Ismp")?.call_hash("handle_unsigned") + { + let extrinsic = Extrinsic::new("Ismp", "handle_unsigned", call); + let ext = self.client.tx().create_unsigned(&extrinsic)?; + Ok(ext.into_encoded()) + } else { + let extrinsic = Extrinsic::new("Ismp", "handle", call); + let call_data = extrinsic.encode_call_data(&self.client.metadata())?; + Ok(call_data) + } } async fn query_ismp_event( @@ -376,7 +483,9 @@ impl Client for SubstrateClient { async fn submit(&self, msg: Message) -> Result { let call = vec![msg].encode(); + let hyper_bridge_timeout_extrinsic = Extrinsic::new("Ismp", "handle_unsigned", call); + let ext = self.client.tx().create_unsigned(&hyper_bridge_timeout_extrinsic)?; let in_block = ext.submit_and_watch().await?.wait_for_finalized_success().await?; diff --git a/modules/hyperclient/src/testing.rs b/modules/hyperclient/src/testing.rs index 954cc3934..d3ba54b3b 100644 --- a/modules/hyperclient/src/testing.rs +++ b/modules/hyperclient/src/testing.rs @@ -79,6 +79,7 @@ pub async fn subscribe_to_request_status() -> Result<(), anyhow::Error> { source: ChainConfig::Evm(source_chain.clone()), dest: ChainConfig::Evm(dest_chain.clone()), hyperbridge: ChainConfig::Substrate(hyperbrige_config), + indexer: None, }; let hyperclient = HyperClient::new(config).await?; @@ -178,6 +179,7 @@ pub async fn test_timeout_request() -> Result<(), anyhow::Error> { source: ChainConfig::Evm(source_chain.clone()), dest: ChainConfig::Evm(dest_chain.clone()), hyperbridge: ChainConfig::Substrate(hyperbrige_config), + indexer: None, }; let hyperclient = HyperClient::new(config).await?; diff --git a/modules/hyperclient/src/tests.rs b/modules/hyperclient/src/tests.rs index 5a0321279..d98804390 100644 --- a/modules/hyperclient/src/tests.rs +++ b/modules/hyperclient/src/tests.rs @@ -2,14 +2,16 @@ use std::str::FromStr; use ismp::{ - host::StateMachine, + host::{Ethereum, StateMachine}, router::{PostResponse, Request}, }; +use substrate_state_machine::HashAlgorithm; use crate::{ indexing::{query_request_status_from_indexer, query_response_status_from_indexer}, testing::{subscribe_to_request_status, test_timeout_request}, - types::MessageStatusWithMetadata, + types::{ChainConfig, ClientConfig, EvmConfig, MessageStatusWithMetadata, SubstrateConfig}, + HyperClient, }; pub fn setup_logging() { @@ -57,7 +59,38 @@ async fn test_query_status_from_indexer() -> Result<(), anyhow::Error> { let request = Request::Post(post); - let status = query_request_status_from_indexer(request).await?.unwrap(); + let source_chain = EvmConfig { + rpc_url: "https://bsc-testnet.blockpi.network/v1/rpc/public".to_string(), + state_machine: StateMachine::Bsc, + host_address: Default::default(), + handler_address: Default::default(), + consensus_state_id: *b"BSC0", + }; + + let dest_chain = EvmConfig { + rpc_url: "https://optimism-sepolia.blockpi.network/v1/rpc/public".to_string(), + state_machine: StateMachine::Ethereum(Ethereum::Optimism), + host_address: Default::default(), + handler_address: Default::default(), + consensus_state_id: *b"ETH0", + }; + + let hyperbrige_config = SubstrateConfig { + rpc_url: "wss://hyperbridge-paseo-rpc.blockops.network:443".to_string(), + consensus_state_id: *b"PARA", + hash_algo: HashAlgorithm::Keccak, + }; + + let config = ClientConfig { + source: ChainConfig::Evm(source_chain.clone()), + dest: ChainConfig::Evm(dest_chain.clone()), + hyperbridge: ChainConfig::Substrate(hyperbrige_config), + indexer: Some("http://localhost:3000".to_string()), + }; + + let hyperclient = HyperClient::new(config).await.unwrap(); + + let status = query_request_status_from_indexer(request, &hyperclient).await?.unwrap(); dbg!(&status); assert!(matches!(status, MessageStatusWithMetadata::DestinationDelivered { .. })); @@ -106,9 +139,41 @@ async fn test_query_response_status_from_indexer() -> Result<(), anyhow::Error> timeout_timestamp: 3432417653, }; - let status = query_response_status_from_indexer(ismp::router::Response::Post(response)) - .await? - .unwrap(); + let source_chain = EvmConfig { + rpc_url: "https://bsc-testnet.blockpi.network/v1/rpc/public".to_string(), + state_machine: StateMachine::Bsc, + host_address: Default::default(), + handler_address: Default::default(), + consensus_state_id: *b"BSC0", + }; + + let dest_chain = EvmConfig { + rpc_url: "https://optimism-sepolia.blockpi.network/v1/rpc/public".to_string(), + state_machine: StateMachine::Ethereum(Ethereum::Optimism), + host_address: Default::default(), + handler_address: Default::default(), + consensus_state_id: *b"ETH0", + }; + + let hyperbrige_config = SubstrateConfig { + rpc_url: "wss://hyperbridge-paseo-rpc.blockops.network:443".to_string(), + consensus_state_id: *b"PARA", + hash_algo: HashAlgorithm::Keccak, + }; + + let config = ClientConfig { + source: ChainConfig::Evm(source_chain.clone()), + dest: ChainConfig::Evm(dest_chain.clone()), + hyperbridge: ChainConfig::Substrate(hyperbrige_config), + indexer: Some("http://localhost:3000".to_string()), + }; + + let hyperclient = HyperClient::new(config).await.unwrap(); + + let status = + query_response_status_from_indexer(ismp::router::Response::Post(response), &hyperclient) + .await? + .unwrap(); dbg!(&status); assert!(matches!(status, MessageStatusWithMetadata::DestinationDelivered { .. })); diff --git a/modules/hyperclient/src/types.rs b/modules/hyperclient/src/types.rs index 04724bcaa..4f509b221 100644 --- a/modules/hyperclient/src/types.rs +++ b/modules/hyperclient/src/types.rs @@ -1,6 +1,8 @@ -use crate::providers::{evm::EvmClient, interface::Client, substrate::SubstrateClient}; +use crate::{ + any_client::AnyClient, + providers::{evm::EvmClient, substrate::SubstrateClient}, +}; use anyhow::anyhow; -use codec::Encode; use core::pin::Pin; use ethers::types::H160; pub use evm_common::types::EvmStateProof; @@ -8,8 +10,9 @@ use futures::Stream; use ismp::{consensus::ConsensusStateId, host::StateMachine}; use serde::{Deserialize, Serialize}; pub use substrate_state_machine::{HashAlgorithm, SubstrateStateProof}; -use subxt::{tx::TxPayload, utils::H256, Config, Metadata}; -use subxt_utils::Hyperbridge; +use subxt::{utils::H256, Config}; +pub use subxt_utils::Extrinsic; +use subxt_utils::{BlakeSubstrateChain, Hyperbridge}; // ======================================== // TYPES @@ -71,6 +74,7 @@ pub struct ClientConfig { pub source: ChainConfig, pub dest: ChainConfig, pub hyperbridge: ChainConfig, + pub indexer: Option, } #[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq, Default, Copy)] @@ -124,6 +128,8 @@ pub enum MessageStatusWithMetadata { /// Metadata about the event on the destination chain #[serde(flatten)] meta: EventMetadata, + /// Calldata that encodes the proof for the message to be sent to the destination. + calldata: Vec, }, /// Delivered to destination DestinationDelivered { @@ -191,66 +197,42 @@ pub enum TimeoutStatus { }, } -/// Implements [`TxPayload`] for extrinsic encoding -pub struct Extrinsic { - /// The pallet name, used to query the metadata - pallet_name: String, - /// The call name - call_name: String, - /// The encoded pallet call. Note that this should be the pallet call. Not runtime call - encoded: Vec, -} - -// ======================================= -// IMPLs = -// ======================================= -impl Extrinsic { - /// Creates a new extrinsic ready to be sent with subxt. - pub fn new( - pallet_name: impl Into, - call_name: impl Into, - encoded_call: Vec, - ) -> Self { - Extrinsic { - pallet_name: pallet_name.into(), - call_name: call_name.into(), - encoded: encoded_call, - } - } -} - -impl TxPayload for Extrinsic { - fn encode_call_data_to( - &self, - metadata: &Metadata, - out: &mut Vec, - ) -> Result<(), subxt::Error> { - // encode the pallet index - let pallet = metadata.pallet_by_name_err(&self.pallet_name).unwrap(); - let call_index = pallet.call_variant_by_name(&self.call_name).unwrap().index; - let pallet_index = pallet.index(); - pallet_index.encode_to(out); - call_index.encode_to(out); - - // copy the encoded call to out - out.extend_from_slice(&self.encoded); - - Ok(()) - } -} - impl ClientConfig { - pub async fn dest_chain(&self) -> Result { + pub async fn dest_chain(&self) -> Result { match &self.dest { - ChainConfig::Evm(config) => config.into_client().await, - _ => Err(anyhow!("Support for substrate coming: requires an AnyClient implementation")), + ChainConfig::Evm(config) => { + let client = config.into_client().await?; + Ok(AnyClient::Evm(client)) + }, + ChainConfig::Substrate(config) => match config.hash_algo { + HashAlgorithm::Keccak => { + let client = config.into_client::().await?; + Ok(AnyClient::KeccakSubstrateChain(client)) + }, + HashAlgorithm::Blake2 => { + let client = config.into_client::().await?; + Ok(AnyClient::BlakeSubstrateChain(client)) + }, + }, } } - pub async fn source_chain(&self) -> Result { + pub async fn source_chain(&self) -> Result { match &self.source { - ChainConfig::Evm(config) => config.into_client().await, - _ => Err(anyhow!("Support for substrate coming: requires an AnyClient implementation")), + ChainConfig::Evm(config) => { + let client = config.into_client().await?; + Ok(AnyClient::Evm(client)) + }, + ChainConfig::Substrate(config) => match config.hash_algo { + HashAlgorithm::Keccak => { + let client = config.into_client::().await?; + Ok(AnyClient::KeccakSubstrateChain(client)) + }, + HashAlgorithm::Blake2 => { + let client = config.into_client::().await?; + Ok(AnyClient::BlakeSubstrateChain(client)) + }, + }, } } diff --git a/modules/ismp/pallets/pallet/src/mmr.rs b/modules/ismp/pallets/pallet/src/mmr.rs index 719e5dc00..b077cc9e6 100644 --- a/modules/ismp/pallets/pallet/src/mmr.rs +++ b/modules/ismp/pallets/pallet/src/mmr.rs @@ -72,8 +72,7 @@ impl FullLeaf for Leaf { } /// Distinguish between requests and responses -#[derive(TypeInfo, Encode, Decode)] -#[cfg_attr(feature = "std", derive(serde::Deserialize, serde::Serialize))] +#[derive(TypeInfo, Encode, Decode, serde::Deserialize, serde::Serialize)] pub enum ProofKeys { /// Request commitments Requests(Vec), diff --git a/modules/ismp/pallets/testsuite/Cargo.toml b/modules/ismp/pallets/testsuite/Cargo.toml index 2f65e33d5..401aaea74 100644 --- a/modules/ismp/pallets/testsuite/Cargo.toml +++ b/modules/ismp/pallets/testsuite/Cargo.toml @@ -67,6 +67,7 @@ cumulus-primitives-core = { workspace = true, default-features = true } cumulus-pallet-xcmp-queue = { workspace = true, default-features = true } cumulus-pallet-parachain-system = { workspace = true, default-features = true } subxt = { workspace = true, features = ["substrate-compat"], default-features = true } +subxt-utils = { workspace = true, default-features = true } [dev-dependencies] hex = "0.4.3" diff --git a/modules/ismp/pallets/testsuite/src/tests/xcm_integration_test.rs b/modules/ismp/pallets/testsuite/src/tests/xcm_integration_test.rs index 8635624c6..31a02cc5b 100644 --- a/modules/ismp/pallets/testsuite/src/tests/xcm_integration_test.rs +++ b/modules/ismp/pallets/testsuite/src/tests/xcm_integration_test.rs @@ -2,175 +2,26 @@ use std::collections::HashMap; -use anyhow::{anyhow, Context}; +use anyhow::anyhow; use codec::Encode; use futures::StreamExt; use ismp::host::StateMachine; use pallet_ismp_rpc::BlockNumberOrHash; -use sp_runtime::MultiAddress; use staging_xcm::{ v3::{Junction, Junctions, MultiLocation, NetworkId, WeightLimit}, VersionedMultiAssets, VersionedMultiLocation, }; use subxt::{ - config::{ - extrinsic_params::BaseExtrinsicParamsBuilder, - polkadot::{PlainTip, PolkadotExtrinsicParams}, - substrate::SubstrateHeader, - ExtrinsicParams, Hasher, Header, - }, - ext::{ - sp_core::{self, bytes::from_hex, crypto::AccountId32, keccak_256, sr25519, Pair, H256}, - sp_runtime::{traits::IdentifyAccount, MultiSignature, MultiSigner}, - }, + config::Header, + ext::sp_core::{bytes::from_hex, sr25519, Pair, H256}, rpc_params, - tx::{Signer, TxPayload}, - Error, Metadata, OnlineClient, PolkadotConfig, + tx::TxPayload, + OnlineClient, PolkadotConfig, }; - -/// Implements [`TxPayload`] for extrinsic encoding -pub struct Extrinsic { - /// The pallet name, used to query the metadata - pallet_name: String, - /// The call name - call_name: String, - /// The encoded pallet call. Note that this should be the pallet call. Not runtime call - encoded: Vec, -} - -impl Extrinsic { - /// Creates a new extrinsic ready to be sent with subxt. - pub fn new( - pallet_name: impl Into, - call_name: impl Into, - encoded_call: Vec, - ) -> Self { - Extrinsic { - pallet_name: pallet_name.into(), - call_name: call_name.into(), - encoded: encoded_call, - } - } -} - -impl TxPayload for Extrinsic { - fn encode_call_data_to(&self, metadata: &Metadata, out: &mut Vec) -> Result<(), Error> { - // encode the pallet index - let pallet = metadata.pallet_by_name_err(&self.pallet_name)?; - let call_index = pallet - .call_variant_by_name(&self.call_name) - .ok_or_else(|| { - Error::Other(format!( - "Can't find {} in pallet {} metadata", - self.call_name, self.pallet_name - )) - })? - .index; - let pallet_index = pallet.index(); - pallet_index.encode_to(out); - call_index.encode_to(out); - - // copy the encoded call to out - out.extend_from_slice(&self.encoded); - - Ok(()) - } -} - -#[derive(Clone)] -pub struct InMemorySigner { - pub account_id: T::AccountId, - pub signer: sr25519::Pair, -} - -impl InMemorySigner -where - T::Signature: From + Send + Sync, - T::AccountId: - From + Into + Clone + 'static + Send + Sync, -{ - pub fn new(pair: sr25519::Pair) -> Self { - InMemorySigner { - account_id: MultiSigner::Sr25519(pair.public()).into_account().into(), - signer: pair, - } - } -} - -impl Signer for InMemorySigner -where - T::AccountId: Into + Clone + 'static, - T::Signature: From + Send + Sync, -{ - fn account_id(&self) -> T::AccountId { - self.account_id.clone() - } - - fn address(&self) -> T::Address { - self.account_id.clone().into() - } - - fn sign(&self, payload: &[u8]) -> T::Signature { - MultiSignature::Sr25519(self.signer.sign(&payload)).into() - } -} - -/// Send a transaction -pub async fn send_extrinsic( - client: &OnlineClient, - signer: InMemorySigner, - payload: Tx, -) -> Result<(), anyhow::Error> -where - >::OtherParams: - Default + Send + Sync + From>, - T::Signature: From + Send + Sync, -{ - let other_params = BaseExtrinsicParamsBuilder::new(); - let ext = client.tx().create_signed(&payload, &signer, other_params.into()).await?; - let progress = ext.submit_and_watch().await.context("Failed to submit signed extrinsic")?; - let ext_hash = progress.extrinsic_hash(); - - let extrinsic = match progress.wait_for_in_block().await { - Ok(p) => p, - Err(err) => Err(err).context(format!( - "Error waiting for signed extrinsic in block with hash {ext_hash:?}" - ))?, - }; - - match extrinsic.wait_for_success().await { - Ok(p) => p, - Err(err) => Err(err).context(format!("Error executing signed extrinsic {ext_hash:?}"))?, - }; - Ok(()) -} +use subxt_utils::{send_extrinsic, Extrinsic, Hyperbridge, InMemorySigner}; const SEND_AMOUNT: u128 = 2_000_000_000_000; -#[derive(Clone)] -pub struct Hyperbridge; - -/// A type that can hash values using the keccak_256 algorithm. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode)] -pub struct RuntimeHasher; - -impl Hasher for RuntimeHasher { - type Output = H256; - fn hash(s: &[u8]) -> Self::Output { - keccak_256(s).into() - } -} - -impl subxt::Config for Hyperbridge { - type Hash = H256; - type AccountId = AccountId32; - type Address = MultiAddress; - type Signature = MultiSignature; - type Hasher = RuntimeHasher; - type Header = SubstrateHeader; - type ExtrinsicParams = PolkadotExtrinsicParams; -} - #[ignore] #[tokio::test] async fn should_dispatch_ismp_request_when_xcm_is_received() -> anyhow::Result<()> { @@ -231,11 +82,11 @@ async fn should_dispatch_ismp_request_when_xcm_is_received() -> anyhow::Result<( send_extrinsic(&client, signer, tx).await?; } - let ext = Extrinsic { - pallet_name: "XcmPallet".to_string(), - call_name: "limited_reserve_transfer_assets".to_string(), - encoded: call.encode(), - }; + let ext = Extrinsic::new( + "XcmPallet".to_string(), + "limited_reserve_transfer_assets".to_string(), + call.encode(), + ); send_extrinsic(&client, signer, ext).await?; diff --git a/modules/subxt/utils/Cargo.toml b/modules/subxt/utils/Cargo.toml index 4601eb090..c2b4477e7 100644 --- a/modules/subxt/utils/Cargo.toml +++ b/modules/subxt/utils/Cargo.toml @@ -32,4 +32,5 @@ std = [ "reconnecting-jsonrpsee-ws-client/native", "pallet-hyperbridge/std", "pallet-ismp-host-executive/std", + "subxt/substrate-compat" ] diff --git a/modules/subxt/utils/src/lib.rs b/modules/subxt/utils/src/lib.rs index ac52e3bb8..799f5e932 100644 --- a/modules/subxt/utils/src/lib.rs +++ b/modules/subxt/utils/src/lib.rs @@ -1,13 +1,23 @@ +use anyhow::anyhow; use codec::Encode; use sp_core_hashing::keccak_256; use subxt::{ - config::{polkadot::PolkadotExtrinsicParams, substrate::SubstrateHeader, Hasher}, - utils::{AccountId32, MultiAddress, MultiSignature, H256}, + config::{ + polkadot::PolkadotExtrinsicParams, + substrate::{BlakeTwo256, SubstrateExtrinsicParams, SubstrateHeader}, + Hasher, + }, + tx::TxPayload, + utils::{AccountId32, MultiAddress, H256}, + Metadata, }; pub mod client; pub mod gargantua; +#[cfg(feature = "std")] +pub use signer::*; + mod gargantua_conversion { use crate::gargantua::api::runtime_types::pallet_hyperbridge::VersionedHostParams; @@ -192,8 +202,170 @@ impl subxt::Config for Hyperbridge { type Hash = H256; type AccountId = AccountId32; type Address = MultiAddress; - type Signature = MultiSignature; + type Signature = subxt::utils::MultiSignature; type Hasher = RuntimeHasher; type Header = SubstrateHeader; type ExtrinsicParams = PolkadotExtrinsicParams; } + +/// Implements [`subxt::Config`] for substrate chains with blake2b as their hashing algorithm +#[derive(Clone)] +pub struct BlakeSubstrateChain; + +impl subxt::Config for BlakeSubstrateChain { + type Hash = H256; + type AccountId = AccountId32; + type Address = MultiAddress; + type Signature = subxt::utils::MultiSignature; + type Hasher = BlakeTwo256; + type Header = SubstrateHeader; + type ExtrinsicParams = SubstrateExtrinsicParams; +} + +/// Implements [`TxPayload`] for extrinsic encoding +pub struct Extrinsic { + /// The pallet name, used to query the metadata + pallet_name: String, + /// The call name + call_name: String, + /// The encoded pallet call. Note that this should be the pallet call. Not runtime call + encoded: Vec, +} + +impl Extrinsic { + /// Creates a new extrinsic ready to be sent with subxt. + pub fn new( + pallet_name: impl Into, + call_name: impl Into, + encoded_call: Vec, + ) -> Self { + Extrinsic { + pallet_name: pallet_name.into(), + call_name: call_name.into(), + encoded: encoded_call, + } + } +} + +impl TxPayload for Extrinsic { + fn encode_call_data_to( + &self, + metadata: &Metadata, + out: &mut Vec, + ) -> Result<(), subxt::error::Error> { + // encode the pallet index + let pallet = metadata.pallet_by_name_err(&self.pallet_name)?; + let call_index = pallet + .call_variant_by_name(&self.call_name) + .ok_or_else(|| { + subxt::error::Error::Other(format!( + "Can't find {} in pallet {} metadata", + self.call_name, self.pallet_name + )) + })? + .index; + let pallet_index = pallet.index(); + pallet_index.encode_to(out); + call_index.encode_to(out); + + // copy the encoded call to out + out.extend_from_slice(&self.encoded); + + Ok(()) + } +} + +#[cfg(feature = "std")] +pub mod signer { + use super::*; + use anyhow::Context; + use subxt::{ + config::{ + extrinsic_params::BaseExtrinsicParamsBuilder, polkadot::PlainTip, ExtrinsicParams, + }, + ext::{ + sp_core::{crypto, sr25519, Pair}, + sp_runtime::{traits::IdentifyAccount, MultiSignature, MultiSigner}, + }, + tx::Signer, + OnlineClient, + }; + + #[derive(Clone)] + pub struct InMemorySigner { + pub account_id: T::AccountId, + pub signer: sr25519::Pair, + } + + impl InMemorySigner + where + T::Signature: From + Send + Sync, + T::AccountId: From + Into + Clone + 'static + Send + Sync, + { + pub fn new(pair: sr25519::Pair) -> Self { + InMemorySigner { + account_id: MultiSigner::Sr25519(pair.public()).into_account().into(), + signer: pair, + } + } + } + + impl Signer for InMemorySigner + where + T::AccountId: Into + Clone + 'static, + T::Signature: From + Send + Sync, + { + fn account_id(&self) -> T::AccountId { + self.account_id.clone() + } + + fn address(&self) -> T::Address { + self.account_id.clone().into() + } + + fn sign(&self, payload: &[u8]) -> T::Signature { + MultiSignature::Sr25519(self.signer.sign(&payload)).into() + } + } + + /// Send a transaction + pub async fn send_extrinsic( + client: &OnlineClient, + signer: InMemorySigner, + payload: Tx, + ) -> Result<(), anyhow::Error> + where + >::OtherParams: + Default + Send + Sync + From>, + T::Signature: From + Send + Sync, + { + let other_params = BaseExtrinsicParamsBuilder::new(); + let ext = client.tx().create_signed(&payload, &signer, other_params.into()).await?; + let progress = ext.submit_and_watch().await.context("Failed to submit signed extrinsic")?; + let ext_hash = progress.extrinsic_hash(); + + let extrinsic = match progress.wait_for_in_block().await { + Ok(p) => p, + Err(err) => Err(refine_subxt_error(err)).context(format!( + "Error waiting for signed extrinsic in block with hash {ext_hash:?}" + ))?, + }; + + match extrinsic.wait_for_success().await { + Ok(p) => p, + Err(err) => + Err(err).context(format!("Error executing signed extrinsic {ext_hash:?}"))?, + }; + Ok(()) + } +} + +/// This prevents the runtime metadata from being displayed when module errors are encountered +pub fn refine_subxt_error(err: subxt::Error) -> anyhow::Error { + match err { + subxt::Error::Runtime(subxt::error::DispatchError::Module(ref err)) => { + anyhow!(err.to_string()) + }, + _ => anyhow!(err), + } +} diff --git a/tesseract/substrate/src/calls.rs b/tesseract/substrate/src/calls.rs index 85dab871f..147c03d91 100644 --- a/tesseract/substrate/src/calls.rs +++ b/tesseract/substrate/src/calls.rs @@ -1,7 +1,7 @@ //! Functions for updating configuration on pallets use crate::{ - extrinsic::{send_extrinsic, send_unsigned_extrinsic, Extrinsic, InMemorySigner}, + extrinsic::{send_unsigned_extrinsic, Extrinsic, InMemorySigner}, runtime, SubstrateClient, }; use anyhow::anyhow; @@ -35,6 +35,7 @@ use subxt::{ utils::AccountId32, OnlineClient, }; +use subxt_utils::send_extrinsic; use tesseract_primitives::{HyperbridgeClaim, IsmpProvider, WithdrawFundsResult}; #[derive(codec::Encode, codec::Decode)] diff --git a/tesseract/substrate/src/extrinsic.rs b/tesseract/substrate/src/extrinsic.rs index fdcbc15fc..4ff38790f 100644 --- a/tesseract/substrate/src/extrinsic.rs +++ b/tesseract/substrate/src/extrinsic.rs @@ -15,104 +15,17 @@ //! Extrinsic utilities -use anyhow::{anyhow, Context}; -use codec::Encode; +use anyhow::Context; use subxt::{ config::{extrinsic_params::BaseExtrinsicParamsBuilder, polkadot::PlainTip, ExtrinsicParams}, - ext::{ - sp_core::{crypto, sr25519, Pair}, - sp_runtime::{traits::IdentifyAccount, MultiSignature, MultiSigner}, - }, + ext::sp_runtime::MultiSignature, rpc::types::DryRunResult, - tx::{Signer, TxPayload}, - Error, Metadata, OnlineClient, + tx::TxPayload, + OnlineClient, }; -/// Implements [`TxPayload`] for extrinsic encoding -pub struct Extrinsic { - /// The pallet name, used to query the metadata - pallet_name: String, - /// The call name - call_name: String, - /// The encoded pallet call. Note that this should be the pallet call. Not runtime call - encoded: Vec, -} - -impl Extrinsic { - /// Creates a new extrinsic ready to be sent with subxt. - pub fn new( - pallet_name: impl Into, - call_name: impl Into, - encoded_call: Vec, - ) -> Self { - Extrinsic { - pallet_name: pallet_name.into(), - call_name: call_name.into(), - encoded: encoded_call, - } - } -} - -impl TxPayload for Extrinsic { - fn encode_call_data_to(&self, metadata: &Metadata, out: &mut Vec) -> Result<(), Error> { - // encode the pallet index - let pallet = metadata.pallet_by_name_err(&self.pallet_name)?; - let call_index = pallet - .call_variant_by_name(&self.call_name) - .ok_or_else(|| { - Error::Other(format!( - "Can't find {} in pallet {} metadata", - self.call_name, self.pallet_name - )) - })? - .index; - let pallet_index = pallet.index(); - pallet_index.encode_to(out); - call_index.encode_to(out); - - // copy the encoded call to out - out.extend_from_slice(&self.encoded); - - Ok(()) - } -} - -#[derive(Clone)] -pub struct InMemorySigner { - pub account_id: T::AccountId, - pub signer: sr25519::Pair, -} - -impl InMemorySigner -where - T::Signature: From + Send + Sync, - T::AccountId: From + Into + Clone + 'static + Send + Sync, -{ - pub fn new(pair: sr25519::Pair) -> Self { - InMemorySigner { - account_id: MultiSigner::Sr25519(pair.public()).into_account().into(), - signer: pair, - } - } -} - -impl Signer for InMemorySigner -where - T::AccountId: Into + Clone + 'static, - T::Signature: From + Send + Sync, -{ - fn account_id(&self) -> T::AccountId { - self.account_id.clone() - } - - fn address(&self) -> T::Address { - self.account_id.clone().into() - } - - fn sign(&self, payload: &[u8]) -> T::Signature { - MultiSignature::Sr25519(self.signer.sign(&payload)).into() - } -} +use subxt_utils::refine_subxt_error; +pub use subxt_utils::{Extrinsic, InMemorySigner}; /// Send an unsigned extrinsic for ISMP messages. pub async fn send_unsigned_extrinsic( @@ -164,36 +77,6 @@ where Ok(Some(hash)) } -/// Send a transaction -pub async fn send_extrinsic( - client: &OnlineClient, - signer: InMemorySigner, - payload: Tx, -) -> Result<(), anyhow::Error> -where - >::OtherParams: - Default + Send + Sync + From>, - T::Signature: From + Send + Sync, -{ - let other_params = BaseExtrinsicParamsBuilder::new(); - let ext = client.tx().create_signed(&payload, &signer, other_params.into()).await?; - let progress = ext.submit_and_watch().await.context("Failed to submit signed extrinsic")?; - let ext_hash = progress.extrinsic_hash(); - - let extrinsic = match progress.wait_for_in_block().await { - Ok(p) => p, - Err(err) => Err(refine_subxt_error(err)).context(format!( - "Error waiting for signed extrinsic in block with hash {ext_hash:?}" - ))?, - }; - - match extrinsic.wait_for_success().await { - Ok(p) => p, - Err(err) => Err(err).context(format!("Error executing signed extrinsic {ext_hash:?}"))?, - }; - Ok(()) -} - /// Dry run extrinsic pub async fn system_dry_run_unsigned( client: &OnlineClient, @@ -208,13 +91,3 @@ where let result = ext.dry_run(None).await?; Ok(result) } - -/// This prevents the runtime metadata from being displayed when module errors are encountered -fn refine_subxt_error(err: subxt::Error) -> anyhow::Error { - match err { - subxt::Error::Runtime(subxt::error::DispatchError::Module(ref err)) => { - anyhow!(err.to_string()) - }, - _ => anyhow!(err), - } -} diff --git a/tesseract/substrate/src/provider.rs b/tesseract/substrate/src/provider.rs index 1e2a1dc4d..a67584f5a 100644 --- a/tesseract/substrate/src/provider.rs +++ b/tesseract/substrate/src/provider.rs @@ -57,6 +57,7 @@ use subxt::{ tx::TxPayload, }; +use subxt_utils::send_extrinsic; use tesseract_primitives::{ BoxStream, EstimateGasReturnParams, IsmpProvider, Query, StateMachineUpdated, StateProofQueryType, TxReceipt, @@ -64,9 +65,7 @@ use tesseract_primitives::{ use crate::{ calls::RequestMetadata, - extrinsic::{ - send_extrinsic, send_unsigned_extrinsic, system_dry_run_unsigned, Extrinsic, InMemorySigner, - }, + extrinsic::{send_unsigned_extrinsic, system_dry_run_unsigned, Extrinsic, InMemorySigner}, runtime::{self}, SubstrateClient, }; diff --git a/tesseract/substrate/src/testing.rs b/tesseract/substrate/src/testing.rs index bea72ef3f..d18190581 100644 --- a/tesseract/substrate/src/testing.rs +++ b/tesseract/substrate/src/testing.rs @@ -16,7 +16,7 @@ //! Testing utilities use crate::{ - extrinsic::{send_extrinsic, Extrinsic, InMemorySigner}, + extrinsic::{Extrinsic, InMemorySigner}, SubstrateClient, }; use codec::Encode; @@ -31,6 +31,8 @@ use subxt::{ tx::TxPayload, }; +use subxt_utils::send_extrinsic; + impl SubstrateClient where C: subxt::Config + Send + Sync + Clone,