diff --git a/Cargo.lock b/Cargo.lock index 51bcbd1167..f8a194f11a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3281,6 +3281,17 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "lightclient" +version = "0.1.0" +dependencies = [ + "chain-utils", + "hex-literal", + "protos", + "serde", + "unionlabs", +] + [[package]] name = "linux-raw-sys" version = "0.1.4" @@ -4864,9 +4875,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.107" +version = "1.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" dependencies = [ "itoa", "ryu", @@ -6445,6 +6456,7 @@ dependencies = [ "hex", "hex-literal", "hubble", + "lightclient", "num-bigint", "pg-queue", "pin-utils", @@ -6472,6 +6484,38 @@ dependencies = [ "tracing-subscriber 0.3.17", "typenum", "unionlabs", + "voyager-message", +] + +[[package]] +name = "voyager-message" +version = "0.1.0" +dependencies = [ + "beacon-api", + "chain-utils", + "contracts", + "derive_more", + "ethers", + "frame-support-procedural", + "frunk", + "futures", + "hex-literal", + "lightclient", + "num-bigint", + "prost", + "protos", + "serde", + "serde-utils", + "serde_json", + "tendermint", + "tendermint-proto", + "tendermint-rpc", + "thiserror", + "tokio", + "tonic", + "tracing", + "typenum", + "unionlabs", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index cf32c94c8e..863a9afb06 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,6 +54,8 @@ chain-utils = { path = "lib/chain-utils", default-features = false } hubble = { path = "hubble" } token-factory-api = { path = "cosmwasm/token-factory-api", default-features = false } ucs01-relay-api = { path = "cosmwasm/ucs01-relay-api", default-features = false } +voyager-message = { path = "voyager/voyager-message", default-features = false } +lightclient = { path = "lib/lightclient", default-features = false } # external dependencies diff --git a/lib/chain-utils/src/evm.rs b/lib/chain-utils/src/evm.rs index 3a17494e75..87ab75e7d7 100644 --- a/lib/chain-utils/src/evm.rs +++ b/lib/chain-utils/src/evm.rs @@ -1,4 +1,4 @@ -use std::{ops::Div, str::FromStr, sync::Arc}; +use std::{fmt::Debug, ops::Div, str::FromStr, sync::Arc}; use beacon_api::client::BeaconApiClient; use contracts::{ @@ -12,16 +12,17 @@ use contracts::{ shared_types::{IbcCoreChannelV1ChannelData, IbcCoreConnectionV1ConnectionEndData}, }; use ethers::{ - abi::Tokenizable, + abi::{AbiEncode, Tokenizable}, contract::{ContractError, EthCall, EthLogDecode}, core::k256::ecdsa, middleware::{NonceManagerMiddleware, SignerMiddleware}, providers::{Middleware, Provider, ProviderError, Ws, WsClientError}, signers::{LocalWallet, Wallet}, - utils::secret_key_to_address, + utils::{keccak256, secret_key_to_address}, }; use futures::{stream, Future, FutureExt, Stream, StreamExt}; use hubble::hasura::{insert_demo_tx, Datastore, HasuraConfig, HasuraDataStore, InsertDemoTx}; +use prost::Message; use serde::{Deserialize, Serialize}; use typenum::Unsigned; use unionlabs::{ @@ -35,15 +36,20 @@ use unionlabs::{ google::protobuf::any::Any, ibc::{ core::{ - channel::channel::Channel, client::height::Height, + channel::channel::Channel, + client::height::{Height, IsHeight}, connection::connection_end::ConnectionEnd, }, lightclients::{cometbls, ethereum, tendermint::fraction::Fraction, wasm}, }, id::{ChannelId, ClientId, ConnectionId, PortId}, - traits::{Chain, ClientState}, + proof::{ + AcknowledgementPath, ChannelEndPath, ClientConsensusStatePath, ClientStatePath, + CommitmentPath, ConnectionPath, IbcPath, IbcStateRead, + }, + traits::{Chain, ClientState, ClientStateOf, ConsensusStateOf}, validated::ValidateT, - EmptyString, TryFromEthAbiErrorOf, TryFromProto, + EmptyString, TryFromEthAbiErrorOf, TryFromProto, TryFromProtoErrorOf, }; use crate::{private_key::PrivateKey, ChainEvent, EventSource, Pool}; @@ -516,8 +522,11 @@ impl EventSource for Evm { let event = match event { Ok(x) => x, Err(e) => { - tracing::warn!("Failed to decode ibc handler event, we may need to regenerate them: {:?}", e); - return Ok::<_, EvmEventSourceError>(None::>>) + tracing::error!( + error = ?e, + "failed to decode ibc handler event" + ); + return Ok::<_, EvmEventSourceError>(None::>>); } }; @@ -566,14 +575,20 @@ impl EventSource for Evm { packet_timeout_height: packet_ack.packet.timeout_height.into(), packet_timeout_timestamp: packet_ack.packet.timeout_timestamp, packet_sequence: packet_ack.packet.sequence, - packet_src_port: packet_ack.packet.source_port.parse() + packet_src_port: packet_ack + .packet + .source_port + .parse() .map_err(EvmEventSourceError::PortIdParse)?, packet_src_channel: packet_ack .packet .source_channel .parse() .map_err(EvmEventSourceError::ChannelIdParse)?, - packet_dst_port: packet_ack.packet.destination_port.parse() + packet_dst_port: packet_ack + .packet + .destination_port + .parse() .map_err(EvmEventSourceError::PortIdParse)?, packet_dst_channel: packet_ack .packet @@ -592,7 +607,10 @@ impl EventSource for Evm { .await?; Some(IbcEvent::ChannelOpenAck(ChannelOpenAck { - port_id: event.port_id.parse().map_err(EvmEventSourceError::PortIdParse)?, + port_id: event + .port_id + .parse() + .map_err(EvmEventSourceError::PortIdParse)?, channel_id: event .channel_id .parse() @@ -612,7 +630,10 @@ impl EventSource for Evm { .await?; Some(IbcEvent::ChannelOpenConfirm(ChannelOpenConfirm { - port_id: event.port_id.parse().map_err(EvmEventSourceError::PortIdParse)?, + port_id: event + .port_id + .parse() + .map_err(EvmEventSourceError::PortIdParse)?, channel_id: event .channel_id .parse() @@ -632,14 +653,23 @@ impl EventSource for Evm { .await?; Some(IbcEvent::ChannelOpenInit(ChannelOpenInit { - port_id: event.port_id.parse().map_err(EvmEventSourceError::PortIdParse)?, + port_id: event + .port_id + .parse() + .map_err(EvmEventSourceError::PortIdParse)?, channel_id: event .channel_id .parse() .map_err(EvmEventSourceError::ChannelIdParse)?, // TODO: Ensure that event.counterparty_channel_id is `EmptyString` - counterparty_channel_id: "".to_string().validate().expect("empty string is a valid empty string; qed;"), - counterparty_port_id: event.counterparty_port_id.parse().map_err(EvmEventSourceError::PortIdParse)?, + counterparty_channel_id: "" + .to_string() + .validate() + .expect("empty string is a valid empty string; qed;"), + counterparty_port_id: event + .counterparty_port_id + .parse() + .map_err(EvmEventSourceError::PortIdParse)?, connection_id: event .connection_id .parse() @@ -653,12 +683,18 @@ impl EventSource for Evm { .await?; Some(IbcEvent::ChannelOpenTry(ChannelOpenTry { - port_id: event.port_id.parse().map_err(EvmEventSourceError::PortIdParse)?, + port_id: event + .port_id + .parse() + .map_err(EvmEventSourceError::PortIdParse)?, channel_id: event .channel_id .parse() .map_err(EvmEventSourceError::ChannelIdParse)?, - counterparty_port_id: event.counterparty_port_id.parse().map_err(EvmEventSourceError::PortIdParse)?, + counterparty_port_id: event + .counterparty_port_id + .parse() + .map_err(EvmEventSourceError::PortIdParse)?, counterparty_channel_id: channel .counterparty .channel_id @@ -798,14 +834,20 @@ impl EventSource for Evm { packet_timeout_height: event.packet.timeout_height.into(), packet_timeout_timestamp: event.packet.timeout_timestamp, packet_sequence: event.packet.sequence, - packet_src_port: event.packet.source_port.parse() + packet_src_port: event + .packet + .source_port + .parse() .map_err(EvmEventSourceError::PortIdParse)?, packet_src_channel: event .packet .source_channel .parse() .map_err(EvmEventSourceError::ChannelIdParse)?, - packet_dst_port: event.packet.destination_port.parse() + packet_dst_port: event + .packet + .destination_port + .parse() .map_err(EvmEventSourceError::PortIdParse)?, packet_dst_channel: event .packet @@ -828,7 +870,9 @@ impl EventSource for Evm { packet_timeout_height: event.timeout_height.into(), packet_timeout_timestamp: event.timeout_timestamp, packet_sequence: event.sequence, - packet_src_port: event.source_port.parse() + packet_src_port: event + .source_port + .parse() .map_err(EvmEventSourceError::PortIdParse)?, packet_src_channel: event .source_channel @@ -861,28 +905,30 @@ impl EventSource for Evm { })) }) .filter_map(|x| async { x.transpose() }) - .then(|event: Result>, EvmEventSourceError>| async { - if let Ok(ref event) = event { - let current_slot = event.height.revision_height; + .then( + |event: Result>, EvmEventSourceError>| async { + if let Ok(ref event) = event { + let current_slot = event.height.revision_height; - let next_epoch_ts = next_epoch_timestamp::(current_slot, genesis_time); + let next_epoch_ts = + next_epoch_timestamp::(current_slot, genesis_time); if let Some(hc) = &this.hasura_client { - hc - .do_post::(insert_demo_tx::Variables { - data: serde_json::json! {{ - "latest_execution_block_hash": event.block_hash, - "timestamp": next_epoch_ts, - }}, - }) - .await - .unwrap(); + hc.do_post::(insert_demo_tx::Variables { + data: serde_json::json! {{ + "latest_execution_block_hash": event.block_hash, + "timestamp": next_epoch_ts, + }}, + }) + .await + .unwrap(); } - } + } - // pass it back through - event - }); + // pass it back through + event + }, + ); let iter = futures::stream::iter(packets.collect::>().await); @@ -981,15 +1027,287 @@ pub fn next_epoch_timestamp(slot: u64, genesis_timestamp: u64) -> genesis_timestamp + (next_epoch_slot * C::SECONDS_PER_SLOT::U64) } -// #[test] -// fn next_epoch_ts() { -// dbg!(next_epoch_timestamp::(6, 0)); -// dbg!(next_epoch_timestamp::(7, 0)); -// dbg!(next_epoch_timestamp::(8, 0)); -// dbg!(next_epoch_timestamp::(9, 0)); - -// dbg!(next_epoch_timestamp::(6, 0)); -// // dbg!(next_epoch::(48, 0)); -// // dbg!(next_epoch::(49, 0)); -// // dbg!(next_epoch::(47, 0)); -// } +impl IbcStateRead for Evm +where + Counterparty: Chain, + C: ChainSpec, + P: IbcPath, Counterparty> + + EthereumStateRead< + C, + Counterparty, + Encoded = <<

>::EthCall as EthCallExt>::Return as TupleToOption>::Inner, + > + 'static, + ::Return: TupleToOption, +{ + fn proof( + &self, + path: P, + at: Height, + ) -> impl Future> + '_ { + async move { + let execution_height = self.execution_height(at).await; + + let path = path.to_string(); + + let location = keccak256( + keccak256(path.as_bytes()) + .into_iter() + .chain(ethers::types::U256::from(0).encode()) + .collect::>(), + ); + + let proof = self + .provider + .get_proof( + self.readonly_ibc_handler.address(), + vec![location.into()], + Some(execution_height.into()), + ) + .await + .unwrap(); + + tracing::info!(?proof); + + let proof = match <[_; 1]>::try_from(proof.storage_proof) { + Ok([proof]) => proof, + Err(invalid) => { + panic!("received invalid response from eth_getProof, expected length of 1 but got `{invalid:#?}`"); + } + }; + + protos::union::ibc::lightclients::ethereum::v1::StorageProof { + proofs: [protos::union::ibc::lightclients::ethereum::v1::Proof { + key: proof.key.to_fixed_bytes().to_vec(), + // REVIEW(benluelo): Make sure this encoding works + value: proof.value.encode(), + proof: proof + .proof + .into_iter() + .map(|bytes| bytes.to_vec()) + .collect(), + }] + .to_vec(), + } + .encode_to_vec() + } + } + + fn state(&self, path: P, at: Self::Height) + -> impl Future, Counterparty>>::Output> + '_ { + async move { + + let execution_block_number = self.execution_height(at).await; + self.read_ibc_state(path.into_eth_call(), execution_block_number) + .await + .unwrap() + .map(|x| P::decode_ibc_state(x)) + .unwrap() + }} +} + +pub trait EthereumStateRead: IbcPath, Counterparty> +where + Counterparty: Chain, + C: ChainSpec, +{ + /// The type of the encoded state returned from the contract. This may be bytes (see client state) + /// or a type (see connection end) + /// Since solidity doesn't support generics, it emulates generics by using bytes in interfaces and + /// "downcasting" (via parsing) to expected types in implementations. + type Encoded; + + type EthCall: EthCallExt + 'static; + + fn into_eth_call(self) -> Self::EthCall; + + fn decode_ibc_state(encoded: Self::Encoded) -> Self::Output; +} + +impl EthereumStateRead + for ClientStatePath< as Chain>::ClientId> +where + ClientStateOf: TryFromProto, + TryFromProtoErrorOf>: Debug, +{ + type Encoded = Vec; + + type EthCall = GetClientStateCall; + + fn into_eth_call(self) -> Self::EthCall { + Self::EthCall { + client_id: self.client_id.to_string(), + } + } + + fn decode_ibc_state(encoded: Self::Encoded) -> Self::Output { + TryFromProto::try_from_proto_bytes(&encoded).unwrap() + } +} + +impl EthereumStateRead + for ClientConsensusStatePath< as Chain>::ClientId, ::Height> +where + ConsensusStateOf: TryFromProto, + TryFromProtoErrorOf>: Debug, +{ + type Encoded = Vec; + + type EthCall = GetConsensusStateCall; + + fn into_eth_call(self) -> Self::EthCall { + Self::EthCall { + client_id: self.client_id.to_string(), + height: self.height.into_height().into(), + } + } + + fn decode_ibc_state(encoded: Self::Encoded) -> Self::Output { + TryFromProto::try_from_proto_bytes(&encoded).unwrap() + } +} + +impl EthereumStateRead for ConnectionPath { + type Encoded = IbcCoreConnectionV1ConnectionEndData; + + type EthCall = GetConnectionCall; + + fn into_eth_call(self) -> Self::EthCall { + Self::EthCall { + connection_id: self.connection_id.to_string(), + } + } + + fn decode_ibc_state(encoded: Self::Encoded) -> Self::Output { + encoded.try_into().unwrap() + } +} + +impl EthereumStateRead for ChannelEndPath { + type Encoded = IbcCoreChannelV1ChannelData; + + type EthCall = GetChannelCall; + + fn into_eth_call(self) -> Self::EthCall { + Self::EthCall { + port_id: self.port_id.to_string(), + channel_id: self.channel_id.to_string(), + } + } + + fn decode_ibc_state(encoded: Self::Encoded) -> Self::Output { + encoded.try_into().unwrap() + } +} + +impl EthereumStateRead for CommitmentPath { + type Encoded = [u8; 32]; + + type EthCall = GetHashedPacketCommitmentCall; + + fn into_eth_call(self) -> Self::EthCall { + Self::EthCall { + port_id: self.port_id.to_string(), + channel_id: self.channel_id.to_string(), + sequence: self.sequence, + } + } + + fn decode_ibc_state(encoded: Self::Encoded) -> Self::Output { + encoded.into() + } +} + +impl EthereumStateRead for AcknowledgementPath { + type Encoded = [u8; 32]; + + type EthCall = GetHashedPacketAcknowledgementCommitmentCall; + + fn into_eth_call(self) -> Self::EthCall { + Self::EthCall { + port_id: self.port_id.to_string(), + channel_id: self.channel_id.to_string(), + sequence: self.sequence, + } + } + + fn decode_ibc_state(encoded: Self::Encoded) -> Self::Output { + encoded.into() + } +} + +pub async fn bind_port(this: &Evm, module_address: Address, port_id: String) { + // HACK: This will pop the top item out of the queue, but binding the port requires the contract owner; + // this will work as long as the first signer in the list is the owner. + this.ibc_handlers + .with(|ibc_handler| async move { + let bind_port_result = ibc_handler.bind_port(port_id, module_address.into()); + + match bind_port_result.send().await { + Ok(ok) => { + ok.await.unwrap().unwrap(); + } + Err(why) => eprintln!("{:?}", why.decode_revert::()), + }; + }) + .await +} + +#[allow(unused_variables)] +pub async fn setup_initial_channel( + this: &Evm, + module_address: Address, + channel_id: String, + port_id: String, + counterparty_port_id: String, +) { + // let signer_middleware = Arc::new(SignerMiddleware::new( + // this.provider.clone(), + // this.wallet.clone(), + // )); + + // let ibc_handler = devnet_ownable_ibc_handler::DevnetOwnableIBCHandler::new( + // this.ibc_handler.address(), + // signer_middleware, + // ); + + // ibc_handler + // .setup_initial_channel( + // "connection-0".into(), + // IbcCoreConnectionV1ConnectionEndData { + // client_id: "cometbls-new-0".into(), + // versions: vec![IbcCoreConnectionV1VersionData { + // identifier: "1".into(), + // features: vec!["ORDER_ORDERED".into(), "ORDER_UNORDERED".into()], + // }], + // state: 3, + // counterparty: IbcCoreConnectionV1CounterpartyData { + // client_id: "08-wasm-0".into(), + // connection_id: "connection-0".into(), + // prefix: IbcCoreCommitmentV1MerklePrefixData { + // key_prefix: b"ibc".to_vec().into(), + // }, + // }, + // delay_period: 6, + // }, + // port_id, + // channel_id.clone(), + // IbcCoreChannelV1ChannelData { + // state: 3, + // ordering: 1, + // counterparty: IbcCoreChannelV1CounterpartyData { + // port_id: counterparty_port_id, + // channel_id, + // }, + // connection_hops: vec!["connection-0".into()], + // version: "ics20-1".into(), + // }, + // module_address.into(), + // ) + // .send() + // .await + // .unwrap() + // .await + // .unwrap() + // .unwrap(); + todo!() +} diff --git a/lib/chain-utils/src/lib.rs b/lib/chain-utils/src/lib.rs index 33e39479b2..5bb74e413f 100644 --- a/lib/chain-utils/src/lib.rs +++ b/lib/chain-utils/src/lib.rs @@ -117,9 +117,3 @@ impl Pool { r } } - -pub trait MaybeRecoverableError: Error { - fn is_recoverable(&self) -> bool; -} - -fn _is_object_safe(_: &dyn MaybeRecoverableError) {} diff --git a/lib/chain-utils/src/union.rs b/lib/chain-utils/src/union.rs index 3fde262b3e..82ebb11258 100644 --- a/lib/chain-utils/src/union.rs +++ b/lib/chain-utils/src/union.rs @@ -1,4 +1,4 @@ -use std::num::ParseIntError; +use std::{fmt::Debug, num::ParseIntError}; use ethers::prelude::k256::ecdsa; use futures::{stream, Future, FutureExt, Stream, StreamExt}; @@ -15,15 +15,19 @@ use unionlabs::{ lightclients::{cometbls, wasm}, }, id::ClientId, + proof::{ + AcknowledgementPath, ChannelEndPath, ClientConsensusStatePath, ClientStatePath, + CommitmentPath, ConnectionPath, IbcPath, IbcStateRead, + }, tendermint::abci::{event::Event, event_attribute::EventAttribute}, - traits::{Chain, ClientState}, - CosmosAccountId, + traits::{Chain, ClientState, ClientStateOf, ConsensusStateOf}, + CosmosAccountId, MaybeRecoverableError, TryFromProto, TryFromProtoErrorOf, }; use crate::{ private_key::PrivateKey, union::tm_types::{CosmosSdkError, SdkError}, - ChainEvent, EventSource, MaybeRecoverableError, Pool, + ChainEvent, EventSource, Pool, }; #[derive(Debug, Clone)] @@ -979,3 +983,149 @@ pub mod tm_types { ) } } + +pub trait AbciStateRead: IbcPath +where + Counterparty: Chain, +{ + fn from_abci_bytes(bytes: Vec) -> Self::Output; +} + +impl AbciStateRead for ClientStatePath<::ClientId> +where + Counterparty: Chain, + ClientStateOf: TryFromProto, + TryFromProtoErrorOf>: Debug, +{ + fn from_abci_bytes(bytes: Vec) -> Self::Output { + Self::Output::try_from_proto_bytes(&bytes).unwrap() + } +} + +impl AbciStateRead + for ClientConsensusStatePath<::ClientId, ::Height> +where + Counterparty: Chain, + ConsensusStateOf: TryFromProto, + TryFromProtoErrorOf>: Debug, +{ + fn from_abci_bytes(bytes: Vec) -> Self::Output { + Self::Output::try_from_proto_bytes(&bytes).unwrap() + } +} + +impl AbciStateRead for ConnectionPath +where + Counterparty: Chain, + // ::ClientId: ClientId, + // Self::Output: Proto + TryFrom, +{ + fn from_abci_bytes(bytes: Vec) -> Self::Output { + Self::Output::try_from_proto_bytes(&bytes).unwrap() + } +} + +impl AbciStateRead for ChannelEndPath +where + Counterparty: Chain, +{ + fn from_abci_bytes(bytes: Vec) -> Self::Output { + Self::Output::try_from_proto_bytes(&bytes).unwrap() + } +} + +impl AbciStateRead for CommitmentPath +where + Counterparty: Chain, +{ + fn from_abci_bytes(bytes: Vec) -> Self::Output { + bytes.try_into().unwrap() + } +} + +impl AbciStateRead for AcknowledgementPath +where + Counterparty: Chain, +{ + fn from_abci_bytes(bytes: Vec) -> Self::Output { + bytes.try_into().unwrap() + } +} + +impl IbcStateRead for Union +where + Counterparty: Chain, + ClientStateOf: TryFromProto, + ConsensusStateOf: TryFromProto, + P: IbcPath + AbciStateRead + 'static, +{ + fn proof(&self, path: P, at: Height) -> impl Future> + '_ { + async move { + tracing::info!(%path, %at, "fetching state proof"); + + let mut client = + protos::cosmos::base::tendermint::v1beta1::service_client::ServiceClient::connect( + self.grpc_url.clone(), + ) + .await + .unwrap(); + + let query_result = client + .abci_query( + protos::cosmos::base::tendermint::v1beta1::AbciQueryRequest { + data: path.to_string().into_bytes(), + path: "store/ibc/key".to_string(), + height: i64::try_from(at.revision_height).unwrap(), + prove: true, + }, + ) + .await + .unwrap() + .into_inner(); + + protos::ibc::core::commitment::v1::MerkleProof { + proofs: query_result + .proof_ops + .unwrap() + .ops + .into_iter() + .map(|op| { + protos::cosmos::ics23::v1::CommitmentProof::decode(op.data.as_slice()) + .unwrap() + }) + .collect::>(), + } + .encode_to_vec() + } + } + + fn state( + &self, + path: P, + at: Self::Height, + ) -> impl Future>::Output> + '_ { + async move { + let mut client = + protos::cosmos::base::tendermint::v1beta1::service_client::ServiceClient::connect( + self.grpc_url.clone(), + ) + .await + .unwrap(); + + let query_result = client + .abci_query( + protos::cosmos::base::tendermint::v1beta1::AbciQueryRequest { + data: path.to_string().into_bytes(), + path: "store/ibc/key".to_string(), + height: i64::try_from(at.revision_height).unwrap(), + prove: false, + }, + ) + .await + .unwrap() + .into_inner(); + + P::from_abci_bytes(query_result.value) + } + } +} diff --git a/lib/lightclient/Cargo.toml b/lib/lightclient/Cargo.toml new file mode 100644 index 0000000000..621119bd0c --- /dev/null +++ b/lib/lightclient/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "lightclient" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +chain-utils.workspace = true +unionlabs = { workspace = true, features = ["ethabi"] } +serde = { version = "1.0.173", features = ["derive"] } +protos = { workspace = true, features = ["proto_full", "client"] } + +[dev-dependencies] +hex-literal = "0.4.1" diff --git a/lib/lightclient/src/cometbls.rs b/lib/lightclient/src/cometbls.rs new file mode 100644 index 0000000000..25a16d0fae --- /dev/null +++ b/lib/lightclient/src/cometbls.rs @@ -0,0 +1,201 @@ +use std::future::Future; + +use chain_utils::evm::{EthCallExt, EthereumStateRead, Evm, TupleToOption}; +use serde::{Deserialize, Serialize}; +use unionlabs::{ + ethereum::Address, + ethereum_consts_traits::{ChainSpec, Mainnet, Minimal}, + google::protobuf::any::Any, + ibc::{ + core::{client::height::Height, connection::connection_end::ConnectionEnd}, + lightclients::{cometbls, wasm}, + }, + id::ClientId, + proof::{ChannelEndPath, ConnectionPath, IbcPath}, + traits::{Chain, ChainOf, ClientStateOf, HeightOf, LightClientBase}, + TryFromProto, +}; + +use crate::ethereum::{EthereumMainnet, EthereumMinimal}; + +/// The solidity light client, tracking the state of the 08-wasm light client on union. +pub struct CometblsMinimal { + chain: Evm, +} + +/// The solidity light client, tracking the state of the 08-wasm light client on union. +pub struct CometblsMainnet { + chain: Evm, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct CometblsConfig { + pub client_type: String, + pub cometbls_client_address: Address, +} + +impl LightClientBase for CometblsMainnet { + type HostChain = Evm; + type Counterparty = EthereumMainnet; + + type ClientId = ClientId; + type ClientType = String; + + type Config = CometblsConfig; + + fn chain(&self) -> &Self::HostChain { + &self.chain + } + + fn from_chain(chain: Self::HostChain) -> Self { + Self { chain } + } + + fn channel( + &self, + channel_id: unionlabs::id::ChannelId, + port_id: unionlabs::id::PortId, + at: HeightOf, + ) -> impl Future + '_ { + read_ibc_state::, _, _>( + &self.chain, + ChannelEndPath { + port_id, + channel_id, + }, + at, + ) + } + + fn connection( + &self, + connection_id: unionlabs::id::ConnectionId, + at: HeightOf, + ) -> impl Future< + Output = ConnectionEnd< + Self::ClientId, + ::ClientId, + String, + >, + > + '_ { + read_ibc_state::, _, _>( + &self.chain, + ConnectionPath { connection_id }, + at, + ) + } + + fn query_client_state( + &self, + client_id: ::ClientId, + height: HeightOf, + ) -> impl Future::HostChain>> + '_ + { + query_client_state(&self.chain, client_id, height) + } +} + +impl LightClientBase for CometblsMinimal { + type HostChain = Evm; + type Counterparty = EthereumMinimal; + + type ClientId = ClientId; + type ClientType = String; + + type Config = CometblsConfig; + + fn chain(&self) -> &Self::HostChain { + &self.chain + } + + fn from_chain(chain: Self::HostChain) -> Self { + Self { chain } + } + + fn channel( + &self, + channel_id: unionlabs::id::ChannelId, + port_id: unionlabs::id::PortId, + at: HeightOf, + ) -> impl Future + '_ { + read_ibc_state::, _, _>( + &self.chain, + ChannelEndPath { + port_id, + channel_id, + }, + at, + ) + } + + fn connection( + &self, + connection_id: unionlabs::id::ConnectionId, + at: HeightOf, + ) -> impl Future< + Output = ConnectionEnd< + Self::ClientId, + ::ClientId, + String, + >, + > + '_ { + read_ibc_state::, _, _>( + &self.chain, + ConnectionPath { connection_id }, + at, + ) + } + + fn query_client_state( + &self, + client_id: ::ClientId, + height: HeightOf, + ) -> impl Future::HostChain>> + '_ + { + query_client_state(&self.chain, client_id, height) + } +} + +async fn query_client_state( + evm: &Evm, + client_id: chain_utils::evm::EvmClientId, + height: Height, +) -> Any> { + let execution_height = evm.execution_height(height).await; + + let (client_state_bytes, is_found) = evm + .readonly_ibc_handler + .get_client_state(client_id.to_string()) + .block(execution_height) + .await + .unwrap(); + + assert!(is_found); + + Any::try_from_proto_bytes(&client_state_bytes).unwrap() +} + +async fn read_ibc_state( +evm: &Evm, +p: P, +at: HeightOf>, +) -> P::Output +where +Counterparty: Chain, +C: ChainSpec, +P: IbcPath, Counterparty> + + EthereumStateRead< + C, + Counterparty, + Encoded = <<

>::EthCall as EthCallExt>::Return as TupleToOption>::Inner, + > + 'static, +::Return: TupleToOption, +{ + let execution_block_number = evm.execution_height(at).await; + + evm.read_ibc_state(p.into_eth_call(), execution_block_number) + .await + .unwrap() + .map(|x| P::decode_ibc_state(x)) + .unwrap() +} diff --git a/lib/lightclient/src/ethereum.rs b/lib/lightclient/src/ethereum.rs new file mode 100644 index 0000000000..7e1acdfc25 --- /dev/null +++ b/lib/lightclient/src/ethereum.rs @@ -0,0 +1,223 @@ +use core::future::Future; +use std::fmt::Debug; + +use chain_utils::union::{AbciStateRead, Union}; +use serde::{Deserialize, Serialize}; +use unionlabs::{ + ethereum::H256, + ibc::core::{client::height::Height, connection::connection_end::ConnectionEnd}, + id::ClientId, + proof::{ChannelEndPath, ClientStatePath, ConnectionPath, IbcPath}, + traits::{Chain, ChainOf, ClientStateOf, ConsensusStateOf, HeightOf, LightClientBase}, + Proto, TryFromProto, TryFromProtoErrorOf, +}; + +use crate::cometbls::{CometblsMainnet, CometblsMinimal}; + +/// The 08-wasm light client tracking ethereum, running on the union chain. +pub struct EthereumMinimal { + chain: ::HostChain, +} + +/// The 08-wasm light client tracking ethereum, running on the union chain. +pub struct EthereumMainnet { + chain: ::HostChain, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct EthereumConfig { + pub code_id: H256, +} + +impl LightClientBase for EthereumMinimal { + type HostChain = Union; + type Counterparty = CometblsMinimal; + + type ClientId = ClientId; + type ClientType = String; + + type Config = EthereumConfig; + + fn chain(&self) -> &Self::HostChain { + &self.chain + } + + fn from_chain(chain: Self::HostChain) -> Self { + Self { chain } + } + + fn query_client_state( + &self, + client_id: ::ClientId, + height: HeightOf, + ) -> impl Future::HostChain>> + '_ + { + query_client_state::(&self.chain, client_id, height) + } + + fn channel( + &self, + channel_id: unionlabs::id::ChannelId, + port_id: unionlabs::id::PortId, + at: HeightOf, + ) -> impl Future + '_ { + read_ibc_state::, _>( + &self.chain, + ChannelEndPath { + port_id, + channel_id, + }, + at, + ) + } + + fn connection( + &self, + connection_id: unionlabs::id::ConnectionId, + at: HeightOf, + ) -> impl Future< + Output = ConnectionEnd< + Self::ClientId, + ::ClientId, + String, + >, + > + '_ { + read_ibc_state::, _>( + &self.chain, + ConnectionPath { connection_id }, + at, + ) + } +} + +impl LightClientBase for EthereumMainnet { + type HostChain = Union; + type Counterparty = CometblsMainnet; + + type ClientId = ClientId; + type ClientType = String; + + type Config = EthereumConfig; + + fn chain(&self) -> &Self::HostChain { + &self.chain + } + + fn from_chain(chain: Self::HostChain) -> Self { + Self { chain } + } + + fn query_client_state( + &self, + client_id: ::ClientId, + height: HeightOf, + ) -> impl Future::HostChain>> + '_ + { + query_client_state::(&self.chain, client_id, height) + } + + fn channel( + &self, + channel_id: unionlabs::id::ChannelId, + port_id: unionlabs::id::PortId, + at: HeightOf, + ) -> impl Future + '_ { + read_ibc_state::, _>( + &self.chain, + ChannelEndPath { + port_id, + channel_id, + }, + at, + ) + } + + fn connection( + &self, + connection_id: unionlabs::id::ConnectionId, + at: HeightOf, + ) -> impl Future< + Output = ConnectionEnd< + Self::ClientId, + ::ClientId, + String, + >, + > + '_ { + read_ibc_state::, _>( + &self.chain, + ConnectionPath { connection_id }, + at, + ) + } +} + +async fn read_ibc_state(union: &Union, path: P, at: HeightOf) -> P::Output +where + Counterparty: Chain, + ClientStateOf: TryFromProto, + ConsensusStateOf: TryFromProto, + P: IbcPath + AbciStateRead + 'static, +{ + let mut client = + protos::cosmos::base::tendermint::v1beta1::service_client::ServiceClient::connect( + union.grpc_url.clone(), + ) + .await + .unwrap(); + + let query_result = client + .abci_query( + protos::cosmos::base::tendermint::v1beta1::AbciQueryRequest { + data: path.to_string().into_bytes(), + path: "store/ibc/key".to_string(), + height: i64::try_from(at.revision_height).unwrap(), + prove: false, + }, + ) + .await + .unwrap() + .into_inner(); + + P::from_abci_bytes(query_result.value) +} + +async fn query_client_state( + union: &Union, + client_id: chain_utils::union::UnionClientId, + height: Height, +) -> ClientStateOf<::HostChain> +where + L: LightClientBase, + ClientStateOf<::HostChain>: Proto + + TryFrom + + TryFromProto, + // NOTE: This bound can be removed once we don't unwrap anymore + TryFromProtoErrorOf::HostChain>>: Debug, + <::HostChain as Chain>::SelfClientState: Proto + + TryFrom + + TryFromProto, +{ + let mut client = + protos::cosmos::base::tendermint::v1beta1::service_client::ServiceClient::connect( + union.grpc_url.clone(), + ) + .await + .unwrap(); + + ::HostChain>>::try_from_proto_bytes( + &client + .abci_query( + protos::cosmos::base::tendermint::v1beta1::AbciQueryRequest { + data: ClientStatePath { client_id }.to_string().into_bytes(), + path: "store/ibc/key".to_string(), + height: height.revision_height.try_into().unwrap(), + prove: false, + }, + ) + .await + .unwrap() + .into_inner() + .value, + ) + .unwrap() +} diff --git a/lib/lightclient/src/lib.rs b/lib/lightclient/src/lib.rs new file mode 100644 index 0000000000..e8e25568e3 --- /dev/null +++ b/lib/lightclient/src/lib.rs @@ -0,0 +1,5 @@ +#![feature(return_position_impl_trait_in_trait)] + +// TODO: Come up with a better naming convention for lightclients +pub mod cometbls; +pub mod ethereum; diff --git a/lib/unionlabs/src/lib.rs b/lib/unionlabs/src/lib.rs index e4052348e8..8b62934dda 100644 --- a/lib/unionlabs/src/lib.rs +++ b/lib/unionlabs/src/lib.rs @@ -2,7 +2,10 @@ #![allow(clippy::missing_errors_doc, clippy::module_name_repetitions)] #![feature(return_position_impl_trait_in_trait)] -use std::fmt::{Debug, Display}; +use std::{ + fmt::{Debug, Display}, + str::FromStr, +}; use bip32::{ secp256k1::{ @@ -12,9 +15,18 @@ use bip32::{ PrivateKey, PublicKey, }; use prost::Message; +use serde::{Deserialize, Serialize}; use sha2::Digest; -use crate::{errors::TryFromBranchError, ethereum::H256, id::Bounded, validated::Validated}; +use crate::{ + errors::TryFromBranchError, + ethereum::H256, + ibc::core::client::height::{HeightFromStrError, IsHeight}, + id::Bounded, + validated::Validated, +}; + +pub const DELAY_PERIOD: u64 = 0; /// Wrapper types around protos defined in , matching the proto module structure. pub mod google; @@ -46,6 +58,8 @@ pub mod bounded; pub mod proof; +pub mod validated; + pub(crate) mod macros; pub mod errors { @@ -443,10 +457,15 @@ pub mod traits { ethereum_consts_traits::ChainSpec, google::protobuf::any::Any, ibc::{ - core::client::height::{Height, IsHeight}, + core::{ + channel::channel::Channel, + client::height::{Height, IsHeight}, + connection::connection_end::ConnectionEnd, + }, lightclients::{cometbls, ethereum, wasm}, }, - id::{ChannelId, PortId}, + id::{ChannelId, ConnectionId, PortId}, + proof::IbcStateReadPaths, validated::{Validate, Validated}, }; @@ -619,6 +638,72 @@ pub mod traits { .unwrap() } } + /// The IBC interface on a [`Chain`] that knows how to connect to a counterparty. + pub trait LightClientBase: Send + Sync + Sized { + /// The chain that this light client is on. + type HostChain: Chain + + IbcStateReadPaths<::HostChain>; + type Counterparty: LightClientBase; + + type ClientId: Id + + TryFrom<::ClientId> + + Into<::ClientId>; + type ClientType: Display + + FromStr + + Debug + + Clone + + PartialEq + + Serialize + + for<'de> Deserialize<'de> + + Send + + Sync + + TryFrom<::ClientType> + + Into<::ClientType> + + 'static; + + /// The config required to construct this light client. + type Config: Debug + Clone + PartialEq + Serialize + for<'de> Deserialize<'de>; + + /// Get the underlying [`Self::HostChain`] that this client is on. + fn chain(&self) -> &Self::HostChain; + + fn from_chain(chain: Self::HostChain) -> Self; + + fn channel( + &self, + channel_id: ChannelId, + port_id: PortId, + at: HeightOf, + ) -> impl Future + '_; + + fn connection( + &self, + connection_id: ConnectionId, + at: HeightOf, + ) -> impl Future< + Output = ConnectionEnd< + Self::ClientId, + ::ClientId, + String, + >, + > + '_; + + // TODO: Use state_proof instead + fn query_client_state( + &self, + // TODO: Make this Into<_> + client_id: ::ClientId, + height: HeightOf, + ) -> impl Future::HostChain>> + '_; + } + + pub type ClientStateOf = ::SelfClientState; + pub type ConsensusStateOf = ::SelfConsensusState; + pub type HeaderOf = ::Header; + pub type HeightOf = ::Height; + pub type ChainOf = ::HostChain; + pub type ChainIdOf = + <<::HostChain as Chain>::SelfClientState as ClientState>::ChainId; } /// An empty string. Will only parse/serialize to/from `""`. @@ -680,203 +765,53 @@ impl TryFrom<&[u8]> for WasmClientType { } } -pub mod validated { - use std::{ - fmt::{Debug, Display}, - marker::PhantomData, - str::FromStr, - }; - - use either::Either; - use serde::{Deserialize, Serialize}; - - #[derive(Serialize, Deserialize)] - #[serde( - transparent, - bound(serialize = "T: Serialize", deserialize = "T: for<'d> Deserialize<'d>") - )] - pub struct Validated>(T, #[serde(skip)] PhantomData V>); - - pub trait ValidateT: Sized { - fn validate>(self) -> Result, V::Error> { - Validated::new(self) - } - } - - impl ValidateT for T {} - - impl> Debug for Validated { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("Validated").field(&self.0).finish() - } - } - - impl> Display for Validated { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) - } - } - - impl> FromStr for Validated { - type Err = Either; - - fn from_str(s: &str) -> Result { - Validated::new(s.parse().map_err(Either::Left)?).map_err(Either::Right) - } - } +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde( + try_from = "&str", + into = "String", + bound(serialize = "", deserialize = "") +)] +pub enum QueryHeight { + Latest, + Specific(H), +} - impl> Clone for Validated { - fn clone(&self) -> Self { - Self(self.0.clone(), PhantomData) +impl Display for QueryHeight { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + QueryHeight::Latest => f.write_str("latest"), + QueryHeight::Specific(height) => f.write_fmt(format_args!("{height}")), } } +} - impl> PartialEq for Validated { - fn eq(&self, other: &Self) -> bool { - self.0.eq(&other.0) - } +impl From> for String { + fn from(val: QueryHeight) -> Self { + val.to_string() } +} - impl> Validated { - pub fn new(t: T) -> Result { - V::validate(t).map(|ok| Validated(ok, PhantomData)) - } +impl FromStr for QueryHeight { + type Err = HeightFromStrError; - pub fn value(self) -> T { - self.0 - } - - pub fn mutate( - self, - f: impl FnOnce(T) -> U, - ) -> Result, >::Error> - where - V: Validate, - { - Validated::new(f(self.0)) + fn from_str(s: &str) -> Result { + match s { + "latest" => Ok(Self::Latest), + _ => s.parse().map(Self::Specific), } } +} - pub trait Validate: Sized { - type Error; - - fn validate(t: T) -> Result; - } - - impl, V2: Validate> Validate for (V1, V2) { - type Error = Either; +impl TryFrom<&'_ str> for QueryHeight { + type Error = HeightFromStrError; - fn validate(t: T) -> Result { - match V1::validate(t).map(|t| V2::validate(t)) { - Ok(Ok(t)) => Ok(t), - Ok(Err(e)) => Err(Either::Right(e)), - Err(e) => Err(Either::Left(e)), - } - } + fn try_from(value: &'_ str) -> Result { + value.parse() } +} - impl Validate for () { - type Error = (); - - fn validate(t: T) -> Result { - Ok(t) - } - } - - #[cfg(test)] - mod tests { - use super::*; - - #[test] - fn validate() { - #[derive(Debug, PartialEq)] - struct NonZero; - #[derive(Debug, PartialEq)] - struct NonMax; - #[derive(Debug, PartialEq)] - struct NotEight; - - impl Validate for NonZero { - type Error = Self; - - fn validate(t: u8) -> Result { - if t == 0 { - Err(NonZero) - } else { - Ok(t) - } - } - } - - impl Validate for NonMax { - type Error = Self; - - fn validate(t: u8) -> Result { - if t == u8::MAX { - Err(NonMax) - } else { - Ok(t) - } - } - } - - impl Validate for NotEight { - type Error = Self; - - fn validate(t: u8) -> Result { - if t == 8 { - Err(NotEight) - } else { - Ok(t) - } - } - } - - assert_eq!(Validated::<_, NonZero>::new(0), Err(NonZero)); - - assert_eq!( - Validated::<_, (NonZero, ())>::new(0), - Err(Either::Left(NonZero)) - ); - - assert_eq!( - Validated::<_, (NonZero, NonMax)>::new(0), - Err(Either::Left(NonZero)) - ); - - assert_eq!( - Validated::<_, (NonZero, NonMax)>::new(u8::MAX), - Err(Either::Right(NonMax)) - ); - - assert_eq!( - Validated::<_, (NonZero, NonMax)>::new(8), - Ok(Validated(8, PhantomData)) - ); - - assert_eq!( - Validated::<_, (NonZero, (NonMax, NotEight))>::new(8), - Err(Either::Right(Either::Right(NotEight))) - ); - - assert_eq!( - Validated::<_, (NotEight, (NonMax, NonZero))>::new(8), - Err(Either::Left(NotEight)) - ); - - assert_eq!( - Validated::<_, (NotEight, (NonMax, NonZero))>::new(7) - .unwrap() - .mutate(|t| t + 1), - Err(Either::Left(NotEight)) - ); - - assert_eq!( - Validated::<_, (NotEight, (NonMax, NonZero))>::new(7) - .unwrap() - .mutate(|t| t + 2), - Ok(Validated(9, PhantomData)) - ); - } - } +pub trait MaybeRecoverableError: std::error::Error { + fn is_recoverable(&self) -> bool; } + +fn _is_object_safe(_: &dyn MaybeRecoverableError) {} diff --git a/lib/unionlabs/src/proof.rs b/lib/unionlabs/src/proof.rs index f301728af6..391a4c5f97 100644 --- a/lib/unionlabs/src/proof.rs +++ b/lib/unionlabs/src/proof.rs @@ -1,4 +1,7 @@ -use std::fmt::{Debug, Display}; +use std::{ + fmt::{Debug, Display}, + future::Future, +}; use clap::builder::{StringValueParser, TypedValueParser}; use serde::{Deserialize, Serialize}; @@ -132,3 +135,31 @@ pub enum Path { #[display(fmt = "{_0}")] AcknowledgementPath(AcknowledgementPath), } + +pub trait IbcStateRead>: Chain + Sized { + fn proof(&self, path: P, at: Self::Height) -> impl Future> + '_; + fn state(&self, path: P, at: Self::Height) -> impl Future + '_; +} + +pub trait IbcStateReadPaths: + Chain + + IbcStateRead::ClientId>> + + IbcStateRead< + Counterparty, + ClientConsensusStatePath<::ClientId, Counterparty::Height>, + > + IbcStateRead + + IbcStateRead + + IbcStateRead + + IbcStateRead +{ +} + +impl IbcStateReadPaths for T where + T: IbcStateRead> + + IbcStateRead> + + IbcStateRead + + IbcStateRead + + IbcStateRead + + IbcStateRead +{ +} diff --git a/lib/unionlabs/src/validated.rs b/lib/unionlabs/src/validated.rs new file mode 100644 index 0000000000..8e074427dc --- /dev/null +++ b/lib/unionlabs/src/validated.rs @@ -0,0 +1,198 @@ +use std::{ + fmt::{Debug, Display}, + marker::PhantomData, + str::FromStr, +}; + +use either::Either; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize)] +#[serde( + transparent, + bound(serialize = "T: Serialize", deserialize = "T: for<'d> Deserialize<'d>") +)] +pub struct Validated>(T, #[serde(skip)] PhantomData V>); + +pub trait ValidateT: Sized { + fn validate>(self) -> Result, V::Error> { + Validated::new(self) + } +} + +impl ValidateT for T {} + +impl> Debug for Validated { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("Validated").field(&self.0).finish() + } +} + +impl> Display for Validated { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +impl> FromStr for Validated { + type Err = Either; + + fn from_str(s: &str) -> Result { + Validated::new(s.parse().map_err(Either::Left)?).map_err(Either::Right) + } +} + +impl> Clone for Validated { + fn clone(&self) -> Self { + Self(self.0.clone(), PhantomData) + } +} + +impl> PartialEq for Validated { + fn eq(&self, other: &Self) -> bool { + self.0.eq(&other.0) + } +} + +impl> Validated { + pub fn new(t: T) -> Result { + V::validate(t).map(|ok| Validated(ok, PhantomData)) + } + + pub fn value(self) -> T { + self.0 + } + + pub fn mutate( + self, + f: impl FnOnce(T) -> U, + ) -> Result, >::Error> + where + V: Validate, + { + Validated::new(f(self.0)) + } +} + +pub trait Validate: Sized { + type Error; + + fn validate(t: T) -> Result; +} + +impl, V2: Validate> Validate for (V1, V2) { + type Error = Either; + + fn validate(t: T) -> Result { + match V1::validate(t).map(|t| V2::validate(t)) { + Ok(Ok(t)) => Ok(t), + Ok(Err(e)) => Err(Either::Right(e)), + Err(e) => Err(Either::Left(e)), + } + } +} + +impl Validate for () { + type Error = (); + + fn validate(t: T) -> Result { + Ok(t) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn validate() { + #[derive(Debug, PartialEq)] + struct NonZero; + #[derive(Debug, PartialEq)] + struct NonMax; + #[derive(Debug, PartialEq)] + struct NotEight; + + impl Validate for NonZero { + type Error = Self; + + fn validate(t: u8) -> Result { + if t == 0 { + Err(NonZero) + } else { + Ok(t) + } + } + } + + impl Validate for NonMax { + type Error = Self; + + fn validate(t: u8) -> Result { + if t == u8::MAX { + Err(NonMax) + } else { + Ok(t) + } + } + } + + impl Validate for NotEight { + type Error = Self; + + fn validate(t: u8) -> Result { + if t == 8 { + Err(NotEight) + } else { + Ok(t) + } + } + } + + assert_eq!(Validated::<_, NonZero>::new(0), Err(NonZero)); + + assert_eq!( + Validated::<_, (NonZero, ())>::new(0), + Err(Either::Left(NonZero)) + ); + + assert_eq!( + Validated::<_, (NonZero, NonMax)>::new(0), + Err(Either::Left(NonZero)) + ); + + assert_eq!( + Validated::<_, (NonZero, NonMax)>::new(u8::MAX), + Err(Either::Right(NonMax)) + ); + + assert_eq!( + Validated::<_, (NonZero, NonMax)>::new(8), + Ok(Validated(8, PhantomData)) + ); + + assert_eq!( + Validated::<_, (NonZero, (NonMax, NotEight))>::new(8), + Err(Either::Right(Either::Right(NotEight))) + ); + + assert_eq!( + Validated::<_, (NotEight, (NonMax, NonZero))>::new(8), + Err(Either::Left(NotEight)) + ); + + assert_eq!( + Validated::<_, (NotEight, (NonMax, NonZero))>::new(7) + .unwrap() + .mutate(|t| t + 1), + Err(Either::Left(NotEight)) + ); + + assert_eq!( + Validated::<_, (NotEight, (NonMax, NonZero))>::new(7) + .unwrap() + .mutate(|t| t + 2), + Ok(Validated(9, PhantomData)) + ); + } +} diff --git a/voyager/Cargo.toml b/voyager/Cargo.toml index e2c3baf23d..3fdb8494a4 100644 --- a/voyager/Cargo.toml +++ b/voyager/Cargo.toml @@ -54,5 +54,8 @@ derive_more = "0.99.17" tikv-jemallocator = "0.5" tokio-util = "0.7.9" +voyager-message.workspace = true +lightclient.workspace = true + [features] eth-mainnet = [ "unionlabs/eth-mainnet" ] diff --git a/voyager/src/chain.rs b/voyager/src/chain.rs index 89fbdfc500..6b708ae5ac 100644 --- a/voyager/src/chain.rs +++ b/voyager/src/chain.rs @@ -1,44 +1,14 @@ -use std::{ - fmt::{Debug, Display}, - str::FromStr, -}; - use chain_utils::{ evm::{Evm, EvmInitError}, union::{Union, UnionInitError}, - MaybeRecoverableError, -}; -use futures::Future; -use serde::{Deserialize, Serialize}; -use unionlabs::{ - ethereum_consts_traits::{Mainnet, Minimal}, - ibc::core::{ - channel::channel::Channel, - client::height::{HeightFromStrError, IsHeight}, - connection::connection_end::ConnectionEnd, - }, - id::{ChannelId, ConnectionId, PortId}, - traits::{self, Chain}, }; +use unionlabs::ethereum_consts_traits::{Mainnet, Minimal}; use crate::{ - chain::proof::{IbcStateRead, IbcStateReadPaths}, config::{self, ChainConfig, EvmChainConfig}, - msg::{ - aggregate::LightClientSpecificAggregate, - data::LightClientSpecificData, - fetch::{FetchStateProof, FetchUpdateHeaders, LightClientSpecificFetch}, - msg::Msg, - DoAggregate, RelayerMsg, - }, queue::Queue, }; -pub mod evm; -pub mod union; - -pub mod proof; - pub enum AnyChain { Union(Union), EvmMainnet(Evm), @@ -92,254 +62,3 @@ impl AnyChain { }) } } - -/// The IBC interface on a [`Chain`] that knows how to connect to a counterparty. -pub trait LightClientBase: Send + Sync + Sized { - /// The chain that this light client is on. - type HostChain: Chain + IbcStateReadPaths<::HostChain>; - type Counterparty: LightClientBase; - - type ClientId: traits::Id - + TryFrom<::ClientId> - + Into<::ClientId>; - type ClientType: Display - + FromStr - + Debug - + Clone - + PartialEq - + Serialize - + for<'de> Deserialize<'de> - + Send - + Sync - + TryFrom<::ClientType> - + Into<::ClientType> - + 'static; - - /// The config required to construct this light client. - type Config: Debug + Clone + PartialEq + Serialize + for<'de> Deserialize<'de>; - - /// Get the underlying [`Self::HostChain`] that this client is on. - fn chain(&self) -> &Self::HostChain; - - fn from_chain(chain: Self::HostChain) -> Self; - - fn channel( - &self, - channel_id: ChannelId, - port_id: PortId, - at: HeightOf, - ) -> impl Future + '_; - - fn connection( - &self, - connection_id: ConnectionId, - at: HeightOf, - ) -> impl Future< - Output = ConnectionEnd< - Self::ClientId, - ::ClientId, - String, - >, - > + '_; - - // TODO: Use state_proof instead - fn query_client_state( - &self, - // TODO: Make this Into<_> - client_id: ::ClientId, - height: HeightOf, - ) -> impl Future::HostChain>> + '_; -} - -pub trait LightClient: LightClientBase { - // https://github.com/rust-lang/rust/issues/20671 - type BaseCounterparty: LightClient; - - type Data: Debug - + Display - + Clone - + PartialEq - + Serialize - + for<'de> Deserialize<'de> - + Into>; - type Fetch: Debug - + Display - + Clone - + PartialEq - + Serialize - + for<'de> Deserialize<'de> - + Into>; - type Aggregate: Debug - + Display - + Clone - + PartialEq - + Serialize - + for<'de> Deserialize<'de> - + Into> - + DoAggregate; - - /// Error type for [`Self::msg`]. - type MsgError: MaybeRecoverableError; - - fn proof(&self, msg: FetchStateProof) -> RelayerMsg; - - fn msg(&self, msg: Msg) -> impl Future> + '_; - - fn do_fetch(&self, msg: Self::Fetch) -> impl Future> + '_; - - // Should (eventually) resolve to UpdateClientData - fn generate_counterparty_updates( - &self, - update_info: FetchUpdateHeaders, - ) -> Vec; -} - -trait LightClientExt = LightClient where ::Counterparty: LightClient; - -pub type ClientStateOf = ::SelfClientState; -pub type ConsensusStateOf = ::SelfConsensusState; -pub type HeaderOf = ::Header; -pub type HeightOf = ::Height; -pub type ChainOf = ::HostChain; - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde( - try_from = "&str", - into = "String", - bound(serialize = "", deserialize = "") -)] -pub enum QueryHeight { - Latest, - Specific(H), -} - -impl Display for QueryHeight { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - QueryHeight::Latest => f.write_str("latest"), - QueryHeight::Specific(height) => f.write_fmt(format_args!("{height}")), - } - } -} - -impl From> for String { - fn from(val: QueryHeight) -> Self { - val.to_string() - } -} - -impl FromStr for QueryHeight { - type Err = HeightFromStrError; - - fn from_str(s: &str) -> Result { - match s { - "latest" => Ok(Self::Latest), - _ => s.parse().map(Self::Specific), - } - } -} - -impl TryFrom<&'_ str> for QueryHeight { - type Error = HeightFromStrError; - - fn try_from(value: &'_ str) -> Result { - value.parse() - } -} - -macro_rules! try_from_relayer_msg { - ( - #[ - $Lc:ident($( - lc_msg( - msg = $LcMsg:ident($Specific:ident), - ty = $Msg:ident, - variants($( $Var:ident($Ty:ty), )+), - ), - )+) - ] - ) => { - $( - $( - impl TryFrom for Identified<$Lc, $Ty> { - type Error = RelayerMsg; - fn try_from(value: RelayerMsg) -> Result, RelayerMsg> { - match value { - RelayerMsg::Lc(crate::msg::AnyLightClientIdentified::$Lc(Identified { - chain_id, - data: LcMsg::$LcMsg( - $LcMsg::LightClientSpecific($Specific($Msg::$Var( - data, - ))), - ), - })) => Ok(Identified { chain_id, data }), - _ => Err(value), - } - } - } - )+ - - crate::chain::this_is_a_hack_look_away! { - $Lc( - lc_msg( - msg = $LcMsg($Specific), - ty = $Msg, - variants($( $Var($Ty), )+), - ), - ) - } - - impl From<<$Lc as LightClient>::$LcMsg> for $Specific<$Lc> { - fn from(msg: <$Lc as LightClient>::$LcMsg) -> Self { - Self(msg) - } - } - )+ - }; -} - -macro_rules! this_is_a_hack_look_away { - ( - $Lc:ident( - lc_msg( - msg = Data(LightClientSpecificData), - ty = $Msg:ident, - variants($( $Var:ident($Ty:ty), )+), - ), - ) - ) => { - $( - impl From> for crate::msg::AggregateData { - fn from(Identified { chain_id, data }: Identified<$Lc, $Ty>) -> crate::msg::AggregateData { - crate::msg::AggregateData::$Lc(Identified { - chain_id, - data: Data::LightClientSpecific(LightClientSpecificData($Msg::$Var( - data, - ))), - }) - } - } - - impl TryFrom for Identified<$Lc, $Ty> { - type Error = crate::msg::AggregateData; - - fn try_from(value: crate::msg::AggregateData) -> Result, crate::msg::AggregateData> { - match value { - crate::msg::AnyLightClientIdentified::$Lc(Identified { - chain_id, - data: Data::LightClientSpecific(LightClientSpecificData($Msg::$Var( - data, - ))), - }) => Ok(Identified { chain_id, data }), - _ => Err(value), - } - } - } - )+ - }; - - ($($_:tt)*) => {}; -} - -pub(crate) use this_is_a_hack_look_away; -pub(crate) use try_from_relayer_msg; diff --git a/voyager/src/chain/proof.rs b/voyager/src/chain/proof.rs deleted file mode 100644 index febd658aed..0000000000 --- a/voyager/src/chain/proof.rs +++ /dev/null @@ -1,35 +0,0 @@ -use futures::Future; -use unionlabs::{ - proof::{ - AcknowledgementPath, ChannelEndPath, ClientConsensusStatePath, ClientStatePath, - CommitmentPath, ConnectionPath, IbcPath, - }, - traits::Chain, -}; - -pub trait IbcStateRead>: Chain + Sized { - fn state_proof(&self, path: P, at: Self::Height) -> impl Future> + '_; -} - -pub trait IbcStateReadPaths: - Chain - + IbcStateRead::ClientId>> - + IbcStateRead< - Counterparty, - ClientConsensusStatePath<::ClientId, Counterparty::Height>, - > + IbcStateRead - + IbcStateRead - + IbcStateRead - + IbcStateRead -{ -} - -impl IbcStateReadPaths for T where - T: IbcStateRead> - + IbcStateRead> - + IbcStateRead - + IbcStateRead - + IbcStateRead - + IbcStateRead -{ -} diff --git a/voyager/src/cli.rs b/voyager/src/cli.rs index 43d8f3ed4d..dfbb64b2be 100644 --- a/voyager/src/cli.rs +++ b/voyager/src/cli.rs @@ -11,12 +11,13 @@ use ethers::{ use reqwest::Url; use unionlabs::{ ibc::core::client::height::Height, - proof::{self, ClientConsensusStatePath, ClientStatePath}, - traits::Chain, + proof::{ + self, ClientConsensusStatePath, ClientStatePath, IbcPath, IbcStateRead, IbcStateReadPaths, + }, + traits::{Chain, HeightOf}, + QueryHeight, }; -use crate::chain::{proof::IbcStateReadPaths, HeightOf, QueryHeight}; - #[derive(Debug, Parser)] #[command(arg_required_else_help = true)] pub struct AppArgs { @@ -68,30 +69,61 @@ pub async fn any_state_proof_to_json height, }; + async fn state_proof< + Counterparty: Chain, + This: IbcStateRead, + P: IbcPath, + >( + path: P, + c: This, + height: HeightOf, + ) -> StateProof { + StateProof { + state: c.state(path.clone(), height).await, + proof: c.proof(path, height).await, + height, + } + } + + #[derive(Debug, serde::Serialize)] + #[serde(bound(serialize = ""))] + struct StateProof< + Counterparty: Chain, + This: IbcStateRead, + P: IbcPath, + > { + state: P::Output, + #[serde(with = "::serde_utils::hex_string")] + proof: Vec, + height: HeightOf, + } + match path { proof::Path::ClientStatePath(path) => json( - &c.state_proof( + &state_proof( ClientStatePath { client_id: path.client_id.parse().unwrap(), }, + c, height, ) .await, ), proof::Path::ClientConsensusStatePath(path) => json( - &c.state_proof( + &state_proof( ClientConsensusStatePath { client_id: path.client_id.parse().unwrap(), height: path.height.into(), }, + c, height, ) .await, ), - proof::Path::ConnectionPath(path) => json(&c.state_proof(path, height).await), - proof::Path::ChannelEndPath(path) => json(&c.state_proof(path, height).await), - proof::Path::CommitmentPath(path) => json(&c.state_proof(path, height).await), - proof::Path::AcknowledgementPath(path) => json(&c.state_proof(path, height).await), + proof::Path::ConnectionPath(path) => json(&state_proof(path, c, height).await), + proof::Path::ChannelEndPath(path) => json(&state_proof(path, c, height).await), + proof::Path::CommitmentPath(path) => json(&state_proof(path, c, height).await), + proof::Path::AcknowledgementPath(path) => json(&state_proof(path, c, height).await), } .unwrap() } diff --git a/voyager/src/main.rs b/voyager/src/main.rs index fae9345e95..90f4b32190 100644 --- a/voyager/src/main.rs +++ b/voyager/src/main.rs @@ -27,13 +27,9 @@ use crate::{ queue::{AnyQueue, AnyQueueConfig, PgQueueConfig, RunError, Voyager, VoyagerInitError}, }; -pub const DELAY_PERIOD: u64 = 0; - pub mod cli; pub mod config; -pub mod msg; - pub mod queue; pub mod chain; @@ -157,10 +153,10 @@ async fn do_main(args: cli::AppArgs) -> Result<(), VoyagerError> { match chain { AnyChain::EvmMinimal(evm) => { - chain::evm::bind_port(&evm, module_address.into(), port_id).await + chain_utils::evm::bind_port(&evm, module_address.into(), port_id).await } AnyChain::EvmMainnet(evm) => { - chain::evm::bind_port(&evm, module_address.into(), port_id).await + chain_utils::evm::bind_port(&evm, module_address.into(), port_id).await } _ => panic!("Not supported"), }; @@ -176,7 +172,7 @@ async fn do_main(args: cli::AppArgs) -> Result<(), VoyagerError> { match chain { AnyChain::EvmMinimal(evm) => { - chain::evm::setup_initial_channel( + chain_utils::evm::setup_initial_channel( &evm, module_address.into(), channel_id, diff --git a/voyager/src/msg/aggregate.rs b/voyager/src/msg/aggregate.rs deleted file mode 100644 index 4f67d66452..0000000000 --- a/voyager/src/msg/aggregate.rs +++ /dev/null @@ -1,332 +0,0 @@ -use std::fmt::Display; - -use frame_support_procedural::{CloneNoBound, DebugNoBound, PartialEqNoBound}; -use serde::{Deserialize, Serialize}; -use unionlabs::{ - ethereum::H256, - events::{ - ChannelOpenAck, ChannelOpenInit, ChannelOpenTry, ConnectionOpenAck, ConnectionOpenInit, - ConnectionOpenTry, RecvPacket, SendPacket, - }, -}; - -use super::ChainIdOf; -use crate::{ - chain::{ChainOf, HeightOf, LightClient, LightClientBase}, - msg::{any_enum, fetch::FetchStateProof}, -}; - -any_enum! { - /// Aggregate data, using data from [`AggregateData`] - #[any = AnyAggregate] - pub enum Aggregate { - ConnectionOpenTry(AggregateConnectionOpenTry), - ConnectionOpenAck(AggregateConnectionOpenAck), - ConnectionOpenConfirm(AggregateConnectionOpenConfirm), - - ChannelOpenTry(AggregateChannelOpenTry), - ChannelOpenAck(AggregateChannelOpenAck), - ChannelOpenConfirm(AggregateChannelOpenConfirm), - - RecvPacket(AggregateRecvPacket), - AckPacket(AggregateAckPacket), - - ConnectionFetchFromChannelEnd(AggregateConnectionFetchFromChannelEnd), - - // Aggregate that fetches the connection info from the channel - ChannelHandshakeUpdateClient(AggregateChannelHandshakeUpdateClient), - - PacketUpdateClient(AggregatePacketUpdateClient), - - WaitForTrustedHeight(AggregateWaitForTrustedHeight), - - FetchCounterpartyStateproof(AggregateFetchCounterpartyStateProof), - - UpdateClientFromClientId(AggregateUpdateClientFromClientId), - - UpdateClient(AggregateUpdateClient), - UpdateClientWithCounterpartyChainIdData(AggregateUpdateClientWithCounterpartyChainId), - - CreateClient(AggregateCreateClient), - - AggregateMsgAfterUpdate(AggregateMsgAfterUpdate), - - LightClientSpecific(LightClientSpecificAggregate), - } -} - -impl Display for Aggregate { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Aggregate::ConnectionOpenTry(_) => write!(f, "ConnectionOpenTry"), - Aggregate::ConnectionOpenAck(_) => write!(f, "ConnectionOpenAck"), - Aggregate::ConnectionOpenConfirm(_) => write!(f, "ConnectionOpenConfirm"), - Aggregate::ChannelOpenTry(_) => write!(f, "ChannelOpenTry"), - Aggregate::ChannelOpenAck(_) => write!(f, "ChannelOpenAck"), - Aggregate::ChannelOpenConfirm(_) => write!(f, "ChannelOpenConfirm"), - Aggregate::RecvPacket(_) => write!(f, "RecvPacket"), - Aggregate::AckPacket(_) => write!(f, "AckPacket"), - Aggregate::ConnectionFetchFromChannelEnd(_) => { - write!(f, "ConnectionFetchFromChannelEnd") - } - Aggregate::ChannelHandshakeUpdateClient(_) => { - write!(f, "ChannelHandshakeUpdateClient") - } - Aggregate::PacketUpdateClient(msg) => { - write!( - f, - "PacketUpdateClient::{}", - match msg.packet_event { - PacketEvent::Send(_) => "Send", - PacketEvent::Recv(_) => "Recv", - } - ) - } - Aggregate::WaitForTrustedHeight(_) => write!(f, "WaitForTrustedHeight"), - Aggregate::FetchCounterpartyStateproof(_) => { - write!(f, "FetchCounterpartyStateproof") - } - Aggregate::UpdateClientFromClientId(_) => write!(f, "UpdateClientFromClientId"), - Aggregate::UpdateClient(_) => write!(f, "UpdateClient"), - Aggregate::UpdateClientWithCounterpartyChainIdData(_) => { - write!(f, "UpdateClientWithCounterpartyChainIdData") - } - Aggregate::CreateClient(_) => write!(f, "CreateClient"), - Aggregate::AggregateMsgAfterUpdate(msg) => { - write!(f, "AggregateMsgAfterUpdate::")?; - match msg { - AggregateMsgAfterUpdate::ConnectionOpenTry(_) => { - write!(f, "ConnectionOpenTry") - } - AggregateMsgAfterUpdate::ConnectionOpenAck(_) => { - write!(f, "ConnectionOpenAck") - } - AggregateMsgAfterUpdate::ConnectionOpenConfirm(_) => { - write!(f, "ConnectionOpenConfirm") - } - AggregateMsgAfterUpdate::ChannelOpenTry(_) => write!(f, "ChannelOpenTry"), - AggregateMsgAfterUpdate::ChannelOpenAck(_) => write!(f, "ChannelOpenAck"), - AggregateMsgAfterUpdate::ChannelOpenConfirm(_) => { - write!(f, "ChannelOpenConfirm") - } - AggregateMsgAfterUpdate::RecvPacket(_) => write!(f, "RecvPacket"), - AggregateMsgAfterUpdate::AckPacket(_) => write!(f, "AckPacket"), - } - } - Aggregate::LightClientSpecific(agg) => write!(f, "LightClientSpecific({})", agg.0), - } - } -} - -#[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] -#[serde(bound(serialize = "", deserialize = ""))] -pub struct AggregateConnectionOpenTry { - pub event_height: HeightOf, - pub event: ConnectionOpenInit::ClientId>, -} - -#[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] -#[serde(bound(serialize = "", deserialize = ""))] -pub struct AggregateConnectionOpenAck { - pub event_height: HeightOf, - pub event: ConnectionOpenTry::ClientId>, -} - -#[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] -#[serde(bound(serialize = "", deserialize = ""))] -pub struct AggregateConnectionOpenConfirm { - pub event_height: HeightOf, - pub event: ConnectionOpenAck::ClientId>, -} - -#[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] -#[serde(bound(serialize = "", deserialize = ""))] -pub struct AggregateChannelOpenTry { - pub event_height: HeightOf, - pub event: ChannelOpenInit, -} - -#[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] -#[serde(bound(serialize = "", deserialize = ""))] -pub struct AggregateChannelOpenAck { - pub event_height: HeightOf, - pub event: ChannelOpenTry, -} - -#[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] -#[serde(bound(serialize = "", deserialize = ""))] -pub struct AggregateChannelOpenConfirm { - pub event_height: HeightOf, - pub event: ChannelOpenAck, -} - -#[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] -#[serde(bound(serialize = "", deserialize = ""))] -pub struct AggregateRecvPacket { - pub event_height: HeightOf, - pub event: SendPacket, -} - -#[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] -#[serde(bound(serialize = "", deserialize = ""))] -pub struct AggregateAckPacket { - pub event_height: HeightOf, - pub event: RecvPacket, - // HACK: Need to pass the block hash through, figure out a better/cleaner way to do this - pub block_hash: H256, - pub counterparty_client_id: ::ClientId, -} - -#[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] -#[serde(bound(serialize = "", deserialize = ""))] -pub struct AggregateConnectionFetchFromChannelEnd { - pub at: HeightOf>, -} - -#[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] -#[serde(bound(serialize = "", deserialize = ""))] -pub struct AggregateChannelHandshakeUpdateClient { - // Will be threaded through to the update msg - pub update_to: HeightOf, - pub event_height: HeightOf, - pub channel_handshake_event: ChannelHandshakeEvent, -} - -#[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] -#[serde(bound(serialize = "", deserialize = ""))] -pub enum ChannelHandshakeEvent { - Init(ChannelOpenInit), - Try(ChannelOpenTry), - Ack(ChannelOpenAck), -} - -#[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] -#[serde(bound(serialize = "", deserialize = ""))] -pub struct AggregatePacketUpdateClient { - // Will be threaded through to the update msg - pub update_to: HeightOf, - pub event_height: HeightOf, - pub block_hash: H256, - pub packet_event: PacketEvent, -} - -#[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] -#[serde(bound(serialize = "", deserialize = ""))] -pub enum PacketEvent { - Send(SendPacket), - Recv(RecvPacket), -} - -#[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] -#[serde(bound(serialize = "", deserialize = ""))] -pub struct AggregateFetchCounterpartyStateProof { - pub counterparty_client_id: ::ClientId, - pub fetch: FetchStateProof, -} - -#[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] -#[serde(bound(serialize = "", deserialize = ""))] -pub struct AggregateUpdateClientFromClientId { - pub client_id: L::ClientId, - pub counterparty_client_id: ::ClientId, -} - -#[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] -#[serde(bound(serialize = "", deserialize = ""))] -pub struct AggregateUpdateClient { - pub update_to: HeightOf, - pub client_id: L::ClientId, - pub counterparty_client_id: ::ClientId, -} - -#[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] -#[serde(bound(serialize = "", deserialize = ""))] -pub struct AggregateWaitForTrustedHeight { - pub wait_for: HeightOf, - pub client_id: L::ClientId, - pub counterparty_client_id: ::ClientId, -} - -#[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] -#[serde(bound(serialize = "", deserialize = ""))] -pub struct AggregateUpdateClientWithCounterpartyChainId { - pub update_to: HeightOf, - pub client_id: L::ClientId, - pub counterparty_client_id: ::ClientId, - pub counterparty_chain_id: ChainIdOf, -} - -#[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] -#[serde(bound(serialize = "", deserialize = ""))] -pub struct AggregateMsgUpdateClient { - pub update_to: HeightOf, - pub client_id: L::ClientId, - pub counterparty_client_id: ::ClientId, - pub counterparty_chain_id: ChainIdOf, -} - -#[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] -#[serde(bound(serialize = "", deserialize = ""))] -pub struct AggregateCreateClient { - pub config: L::Config, -} - -#[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] -#[serde(bound(serialize = "", deserialize = ""))] -pub struct LightClientSpecificAggregate(pub L::Aggregate); - -/// Messages that will be re-queued after an update. -#[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] -#[serde(bound(serialize = "", deserialize = ""))] -pub enum AggregateMsgAfterUpdate { - ConnectionOpenTry(AggregateConnectionOpenTry), - ConnectionOpenAck(AggregateConnectionOpenAck), - ConnectionOpenConfirm(AggregateConnectionOpenConfirm), - - ChannelOpenTry(AggregateChannelOpenTry), - ChannelOpenAck(AggregateChannelOpenAck), - ChannelOpenConfirm(AggregateChannelOpenConfirm), - - RecvPacket(AggregateRecvPacket), - AckPacket(AggregateAckPacket), -} - -// #[derive( -// DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize, derive_more::Display, -// )] -// #[serde(bound(serialize = "", deserialize = ""))] -// pub enum AggregateAnyStateProof { -// #[display(fmt = "{_0}")] -// ClientState( -// AggregateStateProof< -// L, -// ClientStatePath<::ClientId>, -// >, -// ), -// #[display(fmt = "{_0}")] -// ClientConsensusState( -// AggregateStateProof< -// L, -// ClientConsensusStatePath< -// as unionlabs::traits::Chain>::ClientId, -// HeightOf>, -// >, -// >, -// ), -// #[display(fmt = "{_0}")] -// Connection(AggregateStateProof), -// #[display(fmt = "{_0}")] -// ChannelEnd(AggregateStateProof), -// #[display(fmt = "{_0}")] -// Commitment(AggregateStateProof), -// #[display(fmt = "{_0}")] -// Acknowledgement(AggregateStateProof), -// } - -// #[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] -// #[serde(bound(serialize = "", deserialize = ""))] -// pub struct AggregateStateProof> { -// height: HeightOf>, -// #[serde(skip)] -// pub __marker: PhantomData

, -// } diff --git a/voyager/src/msg/event.rs b/voyager/src/msg/event.rs deleted file mode 100644 index 8788ab066c..0000000000 --- a/voyager/src/msg/event.rs +++ /dev/null @@ -1,79 +0,0 @@ -use std::fmt::Display; - -use frame_support_procedural::{CloneNoBound, DebugNoBound, PartialEqNoBound}; -use serde::{Deserialize, Serialize}; -use unionlabs::ethereum::H256; - -use crate::{ - chain::{ChainOf, HeightOf, LightClient, LightClientBase}, - msg::any_enum, -}; - -any_enum! { - #[any = AnyEvent] - pub enum Event { - Ibc(IbcEvent), - Command(Command), - } -} - -impl Display for Event { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Event::Ibc(_) => write!(f, "Ibc"), - Event::Command(cmd) => write!(f, "{cmd}"), - } - } -} - -#[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] -#[serde(bound(serialize = "", deserialize = ""))] -pub struct IbcEvent { - pub block_hash: H256, - pub height: HeightOf>, - pub event: unionlabs::events::IbcEvent< - L::ClientId, - L::ClientType, - ::ClientId, - >, -} - -impl Display for IbcEvent { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - use unionlabs::events::IbcEvent::*; - - match self.event { - CreateClient(_) => write!(f, "Ibc::CreateClient"), - UpdateClient(_) => write!(f, "Ibc::UpdateClient"), - ClientMisbehaviour(_) => write!(f, "Ibc::ClientMisbehaviour"), - SubmitEvidence(_) => write!(f, "Ibc::SubmitEvidence"), - ConnectionOpenInit(_) => write!(f, "Ibc::ConnectionOpenInit"), - ConnectionOpenTry(_) => write!(f, "Ibc::ConnectionOpenTry"), - ConnectionOpenAck(_) => write!(f, "Ibc::ConnectionOpenAck"), - ConnectionOpenConfirm(_) => write!(f, "Ibc::ConnectionOpenConfirm"), - ChannelOpenInit(_) => write!(f, "Ibc::ChannelOpenInit"), - ChannelOpenTry(_) => write!(f, "Ibc::ChannelOpenTry"), - ChannelOpenAck(_) => write!(f, "Ibc::ChannelOpenAck"), - ChannelOpenConfirm(_) => write!(f, "Ibc::ChannelOpenConfirm"), - WriteAcknowledgement(_) => write!(f, "Ibc::WriteAcknowledgement"), - RecvPacket(_) => write!(f, "Ibc::RecvPacket"), - SendPacket(_) => write!(f, "Ibc::SendPacket"), - AcknowledgePacket(_) => write!(f, "Ibc::AcknowledgePacket"), - TimeoutPacket(_) => write!(f, "Ibc::TimeoutPacket"), - } - } -} - -#[allow(non_camel_case_types, non_upper_case_globals)] -#[derive( - DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize, derive_more::Display, -)] -#[serde(bound(serialize = "", deserialize = ""))] -#[display(fmt = "Command::{}")] -pub enum Command { - #[display(fmt = "UpdateClient({client_id}, {counterparty_client_id})")] - UpdateClient { - client_id: L::ClientId, - counterparty_client_id: ::ClientId, - }, -} diff --git a/voyager/src/msg/wait.rs b/voyager/src/msg/wait.rs deleted file mode 100644 index 3f6e0bf342..0000000000 --- a/voyager/src/msg/wait.rs +++ /dev/null @@ -1,50 +0,0 @@ -use std::{fmt::Display, marker::PhantomData}; - -use frame_support_procedural::{CloneNoBound, DebugNoBound, PartialEqNoBound}; -use serde::{Deserialize, Serialize}; - -use crate::{ - chain::{ChainOf, HeightOf, LightClient, LightClientBase}, - msg::{any_enum, ChainIdOf}, -}; - -any_enum! { - /// Defines messages that are sent *to* the lightclient `L`. - #[any = AnyWait] - pub enum Wait { - Block(WaitForBlock), - Timestamp(WaitForTimestamp), - TrustedHeight(WaitForTrustedHeight), - } -} - -impl Display for Wait { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Wait::Block(block) => write!(f, "Block({})", block.0), - Wait::Timestamp(ts) => write!(f, "Timestamp({})", ts.timestamp), - Wait::TrustedHeight(th) => write!(f, "TrustedHeight({})", th.height), - } - } -} - -#[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] -#[serde(bound(serialize = "", deserialize = ""))] -pub struct WaitForBlock(pub HeightOf>); - -#[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] -#[serde(bound(serialize = "", deserialize = ""))] -pub struct WaitForTimestamp { - pub timestamp: i64, - #[serde(skip)] - pub __marker: PhantomData, -} - -#[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] -#[serde(bound(serialize = "", deserialize = ""))] -pub struct WaitForTrustedHeight { - pub client_id: L::ClientId, - pub counterparty_client_id: ::ClientId, - pub counterparty_chain_id: ChainIdOf, - pub height: HeightOf>, -} diff --git a/voyager/src/queue.rs b/voyager/src/queue.rs index fc309c6382..7243feb5da 100644 --- a/voyager/src/queue.rs +++ b/voyager/src/queue.rs @@ -2,17 +2,17 @@ use std::{ collections::{HashMap, VecDeque}, error::Error, fmt::{Debug, Display}, - marker::PhantomData, str::FromStr, sync::{Arc, Mutex}, - time::{Duration, SystemTime, UNIX_EPOCH}, + time::Duration, }; use chain_utils::{evm::Evm, union::Union, EventSource}; -use frame_support_procedural::DebugNoBound; -use frunk::{hlist_pat, HList}; -use futures::{future::BoxFuture, stream, Future, FutureExt, StreamExt, TryStreamExt}; -use hubble::hasura::{Datastore, HasuraDataStore, InsertDemoTx}; +use futures::{stream, Future, StreamExt, TryStreamExt}; +use lightclient::{ + cometbls::{CometblsMainnet, CometblsMinimal}, + ethereum::{EthereumMainnet, EthereumMinimal}, +}; use pg_queue::ProcessFlow; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use sqlx::postgres::PgPoolOptions; @@ -26,88 +26,21 @@ use unionlabs::{ ConnectionOpenTry, CreateClient, IbcEvent, RecvPacket, SendPacket, SubmitEvidence, TimeoutPacket, UpdateClient, WriteAcknowledgement, }, - ibc::core::{ - channel::{ - self, channel::Channel, msg_acknowledgement::MsgAcknowledgement, - msg_channel_open_ack::MsgChannelOpenAck, - msg_channel_open_confirm::MsgChannelOpenConfirm, - msg_channel_open_try::MsgChannelOpenTry, msg_recv_packet::MsgRecvPacket, - packet::Packet, - }, - client::{ - height::{Height, IsHeight}, - msg_create_client::MsgCreateClient, - }, - commitment::merkle_prefix::MerklePrefix, - connection::{ - self, msg_connection_open_ack::MsgConnectionOpenAck, - msg_connection_open_confirm::MsgConnectionOpenConfirm, - msg_connection_open_try::MsgConnectionOpenTry, - }, - }, - proof::{ - self, AcknowledgementPath, ChannelEndPath, ClientConsensusStatePath, ClientStatePath, - CommitmentPath, ConnectionPath, - }, - traits::{Chain, ClientState}, + traits::{Chain, ChainIdOf, ClientState, LightClientBase}, }; +use voyager_message::{event, GetLc, LightClient, RelayerMsg}; use crate::{ - chain::{ - evm::{CometblsMainnet, CometblsMinimal}, - union::{EthereumMainnet, EthereumMinimal}, - AnyChain, AnyChainTryFromConfigError, HeightOf, LightClient, LightClientBase, QueryHeight, - }, + chain::{AnyChain, AnyChainTryFromConfigError}, config::Config, - msg::{ - aggregate::{ - Aggregate, AggregateAckPacket, AggregateChannelHandshakeUpdateClient, - AggregateChannelOpenAck, AggregateChannelOpenConfirm, AggregateChannelOpenTry, - AggregateConnectionFetchFromChannelEnd, AggregateConnectionOpenAck, - AggregateConnectionOpenConfirm, AggregateConnectionOpenTry, AggregateCreateClient, - AggregateFetchCounterpartyStateProof, AggregateMsgAfterUpdate, - AggregatePacketUpdateClient, AggregateRecvPacket, AggregateUpdateClient, - AggregateUpdateClientFromClientId, AggregateUpdateClientWithCounterpartyChainId, - AggregateWaitForTrustedHeight, ChannelHandshakeEvent, LightClientSpecificAggregate, - PacketEvent, - }, - data, - data::{ - AcknowledgementProof, AnyData, ChannelEnd, ChannelEndProof, ClientConsensusStateProof, - ClientStateProof, CommitmentProof, ConnectionEnd, ConnectionProof, Data, - PacketAcknowledgement, SelfClientState, SelfConsensusState, TrustedClientState, - }, - defer, enum_variants_conversions, event, - event::Event, - fetch, - fetch::{ - Fetch, FetchChannelEnd, FetchConnectionEnd, FetchPacketAcknowledgement, - FetchSelfClientState, FetchSelfConsensusState, FetchStateProof, - FetchTrustedClientState, FetchUpdateHeaders, LightClientSpecificFetch, - }, - identified, msg, - msg::{ - Msg, MsgAckPacketData, MsgChannelOpenAckData, MsgChannelOpenConfirmData, - MsgChannelOpenTryData, MsgConnectionOpenAckData, MsgConnectionOpenConfirmData, - MsgConnectionOpenTryData, MsgCreateClientData, MsgRecvPacketData, - }, - retry, seq, wait, - wait::{Wait, WaitForBlock, WaitForTimestamp, WaitForTrustedHeight}, - AggregateData, AggregateReceiver, AnyLcMsg, AnyLightClientIdentified, ChainIdOf, - DeferPoint, DoAggregate, Identified, LcMsg, RelayerMsg, - }, - queue::aggregate_data::{IsAggregateData, UseAggregate}, - DELAY_PERIOD, }; pub mod msg_server; -pub mod aggregate_data; - #[derive(Debug, Clone)] pub struct Voyager { chains: Arc, - hasura_client: Option>, + // hasura_client: Option>, num_workers: u16, msg_server: msg_server::MsgServer, queue: Q, @@ -117,7 +50,6 @@ pub struct Voyager { pub struct Worker { pub id: u16, pub chains: Arc, - pub hasura_client: Option>, } #[derive(Debug, Clone)] @@ -338,7 +270,6 @@ impl Voyager { Worker { id, chains: self.chains.clone(), - hasura_client: self.hasura_client.clone(), } } @@ -414,13 +345,6 @@ impl Voyager { }), msg_server: msg_server::MsgServer, num_workers: config.voyager.num_workers, - hasura_client: config.voyager.hasura.map(|hc| { - Arc::new(HasuraDataStore::new( - reqwest::Client::new(), - hc.url, - hc.secret, - )) - }), queue, }) } @@ -441,7 +365,7 @@ impl Voyager { event::( chain_event.chain_id, - crate::msg::event::IbcEvent { + voyager_message::event::IbcEvent { block_hash: chain_event.block_hash, height: chain_event.height, event: chain_event_to_lc_event::( @@ -468,7 +392,7 @@ impl Voyager { event::( chain_event.chain_id, - crate::msg::event::IbcEvent { + voyager_message::event::IbcEvent { block_hash: chain_event.block_hash, height: chain_event.height, event: chain_event_to_lc_event::( @@ -584,7 +508,7 @@ impl Worker { } else { let worker = self.clone(); q.process(move |msg| async move { - let new_msgs = worker.handle_msg(msg, 0).await; + let new_msgs = msg.handle(&worker, 0).await; match new_msgs { Ok(ok) => ProcessFlow::Success(ok), @@ -602,267 +526,12 @@ impl Worker { } } } - - // NOTE: Box is required bc recursion - fn handle_msg( - &self, - msg: RelayerMsg, - depth: usize, - ) -> BoxFuture<'_, Result, HandleMsgError>> { - tracing::info!( - worker = self.id, - depth, - msg = %msg, - "handling message", - ); - - async move { - match msg { - RelayerMsg::Lc(any_lc_msg) => { - if let Some(hasura) = &self.hasura_client { - hasura - .do_post::(hubble::hasura::insert_demo_tx::Variables { - data: serde_json::to_value(&any_lc_msg).unwrap(), - }) - .await - .unwrap(); - } - - let res = match any_lc_msg { - AnyLightClientIdentified::EthereumMainnet(msg) => { - let vec: Vec = self.handle_msg_generic::(msg).await.map_err(AnyLcError::EthereumMainnet)?; - vec - } - AnyLightClientIdentified::EthereumMinimal(msg) => { - self.handle_msg_generic::(msg).await.map_err(AnyLcError::EthereumMinimal)? - } - AnyLightClientIdentified::CometblsMainnet(msg) => { - self.handle_msg_generic::(msg).await.map_err(AnyLcError::CometblsMainnet)? - } - AnyLightClientIdentified::CometblsMinimal(msg) => { - self.handle_msg_generic::(msg).await.map_err(AnyLcError::CometblsMinimal)? - } - }; - - Ok(res) - } - - RelayerMsg::DeferUntil { point: DeferPoint::Relative, seconds } => - Ok([RelayerMsg::DeferUntil { point: DeferPoint::Absolute, seconds: now() + seconds }].into()), - - RelayerMsg::DeferUntil { seconds, .. } => { - // if we haven't hit the time yet, requeue the defer msg - if now() < seconds { - // TODO: Make the time configurable? - tokio::time::sleep(Duration::from_secs(1)).await; - - Ok([defer(seconds)].into()) - } else { - Ok(vec![]) - } - } - - RelayerMsg::Timeout { - timeout_timestamp, - msg, - } => { - // if we haven't hit the timeout yet, handle the msg - if now() > timeout_timestamp { - tracing::warn!(json = %serde_json::to_string(&msg).unwrap(), "message expired"); - - Ok([].into()) - } else { - self.handle_msg(*msg, depth + 1).await - } - } - - RelayerMsg::Sequence(mut s) => { - let msgs = match s.pop_front() { - Some(msg) => self.handle_msg(msg, depth + 1).await?, - None => return Ok(vec![]), - }; - - for msg in msgs.into_iter().rev() { - s.push_front(msg); - } - - Ok([flatten_seq(seq(s))].into()) - } - - RelayerMsg::Retry(count, msg) => { - const RETRY_DELAY_SECONDS: u64 = 3; - - match self.handle_msg(*msg.clone(), depth + 1).await { - Ok(ok) => Ok(ok), - Err(err) => if count > 0 { - let retries_left = count - 1; - tracing::warn!( - %msg, - retries_left, - "msg failed, retrying in {RETRY_DELAY_SECONDS} seconds" - ); - Ok([seq([defer(now() + RETRY_DELAY_SECONDS), retry(retries_left, *msg)])].into()) - } else { - tracing::error!(%msg, "msg failed after all retries"); - Err(err) - }, - } - }, - - RelayerMsg::Aggregate { - mut queue, - mut data, - receiver, - } => { - if let Some(msg) = queue.pop_front() { - let msgs = self.handle_msg(msg, depth + 1).await?; - - for m in msgs { - match >::try_from(m) { - Ok(d) => { - data.push_back(d); - } - Err(m) => { - queue.push_back(m); - } - } - } - - let res = [RelayerMsg::Aggregate { - queue, - data, - receiver, - }] - .into(); - - Ok(res) - } else { - // queue is empty, handle msg - - let res = match receiver { - AggregateReceiver::EthereumMainnet(msg) => { - do_create::(msg, data) - } - AggregateReceiver::EthereumMinimal(msg) => { - do_create::(msg, data) - } - AggregateReceiver::CometblsMainnet(msg) => { - do_create::(msg, data) - } - AggregateReceiver::CometblsMinimal(msg) => { - do_create::(msg, data) - } - }; - - Ok(res) - } - } - RelayerMsg::Repeat { times: 0, .. } => Ok([].into()), - RelayerMsg::Repeat { times, msg } => { - Ok([flatten_seq(seq([*msg.clone(), RelayerMsg::Repeat { times: times - 1, msg}]))].into()) - }, - } - } - .boxed() - } - - async fn handle_msg_generic( - &self, - msg: identified!(LcMsg), - ) -> Result, LcError> - where - L: LightClient, - Self: GetLc, - AnyLightClientIdentified: From)>, - AnyLightClientIdentified: From)>, - AggregateReceiver: From)>, - AnyLightClientIdentified: From)>, - // TODO: Remove once we no longer unwrap in handle_fetch - <::ClientId as TryFrom< - <::HostChain as Chain>::ClientId, - >>::Error: Debug, - <::ClientId as TryFrom< - <::HostChain as Chain>::ClientId, - >>::Error: Debug, - { - let l = self.get_lc(&msg.chain_id); - - match msg.data { - LcMsg::Event(event) => Ok(handle_event(l, event)), - LcMsg::Data(data) => { - // TODO: Figure out a way to bubble it up to the top level - - let data = - AnyLightClientIdentified::::from(Identified::new(msg.chain_id, data)); - - tracing::error!( - data = %serde_json::to_string(&data).unwrap(), - "received data outside of an aggregation" - ); - - Ok([].into()) - } - LcMsg::Fetch(fetch) => Ok(handle_fetch(l, fetch).await), - LcMsg::Msg(m) => { - // NOTE: `Msg`s don't requeue any `RelayerMsg`s; they are side-effect only. - l.msg(m).await.map_err(LcError::Msg)?; - - Ok([].into()) - } - LcMsg::Wait(wait) => Ok(handle_wait(l, wait).await), - LcMsg::Aggregate(_) => { - todo!() - } - } - } -} - -/// Returns the current unix timestamp in seconds. -fn now() -> u64 { - SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_secs() -} - -#[derive(Debug, thiserror::Error)] -pub enum HandleMsgError { - #[error(transparent)] - Lc(#[from] AnyLcError), -} - -enum_variants_conversions! { - #[derive(Debug, thiserror::Error)] - pub enum AnyLcError { - // The 08-wasm client tracking the state of Evm. - #[error(transparent)] - EthereumMainnet(LcError), - // The 08-wasm client tracking the state of Evm. - #[error(transparent)] - EthereumMinimal(LcError), - // The solidity client on Evm tracking the state of Union. - #[error(transparent)] - CometblsMainnet(LcError), - // The solidity client on Evm tracking the state of Union. - #[error(transparent)] - CometblsMinimal(LcError), - } -} - -#[derive(DebugNoBound, thiserror::Error)] -pub enum LcError { - #[error(transparent)] - Msg(L::MsgError), } // pub enum AnyLcError_ {} // impl AnyLightClient for AnyLcError_ {} -trait GetLc { - fn get_lc(&self, chain_id: &ChainIdOf) -> L; -} - // TODO: Implement this on Chains, not Worker impl GetLc for Worker { fn get_lc(&self, chain_id: &ChainIdOf) -> CometblsMinimal { @@ -890,380 +559,6 @@ impl GetLc for Worker { } } -fn handle_event(l: L, event: crate::msg::event::Event) -> Vec -where - AnyLightClientIdentified: From)>, - AggregateReceiver: From)>, -{ - match event { - Event::Ibc(ibc_event) => match ibc_event.event { - IbcEvent::CreateClient(e) => { - println!("client created: {e:#?}"); - - vec![] - } - IbcEvent::UpdateClient(e) => { - println!( - "client updated: {:#?} to {:#?}", - e.client_id, e.consensus_heights - ); - - vec![] - } - - IbcEvent::ClientMisbehaviour(_) => unimplemented!(), - IbcEvent::SubmitEvidence(_) => unimplemented!(), - - IbcEvent::ConnectionOpenInit(init) => [seq([ - wait::(l.chain().chain_id(), WaitForBlock(ibc_event.height)), - RelayerMsg::Aggregate { - data: [].into(), - queue: [ - // mk_aggregate_update( - // l.chain().chain_id(), - // init.client_id.clone(), - // init.counterparty_client_id.clone(), - // ibc_event.height, - // ) - mk_aggregate_wait_for_update( - l.chain().chain_id(), - init.client_id.clone(), - init.counterparty_client_id.clone(), - ibc_event.height, - ), - ] - .into(), - receiver: AggregateReceiver::from(Identified::new( - l.chain().chain_id(), - Aggregate::AggregateMsgAfterUpdate( - AggregateMsgAfterUpdate::ConnectionOpenTry( - AggregateConnectionOpenTry { - event_height: ibc_event.height, - event: init, - }, - ), - ), - )), - }, - ])] - .into(), - IbcEvent::ConnectionOpenTry(try_) => [seq([ - // wait::( - // l.chain().chain_id(), - // WaitForBlock(ibc_event.height.increment()), - // ), - RelayerMsg::Aggregate { - data: [].into(), - queue: [mk_aggregate_wait_for_update( - l.chain().chain_id(), - try_.client_id.clone(), - try_.counterparty_client_id.clone(), - ibc_event.height, - )] - .into(), - receiver: AggregateReceiver::from(Identified::new( - l.chain().chain_id(), - Aggregate::AggregateMsgAfterUpdate( - AggregateMsgAfterUpdate::ConnectionOpenAck( - AggregateConnectionOpenAck { - event_height: ibc_event.height, - event: try_, - }, - ), - ), - )), - }, - ])] - .into(), - IbcEvent::ConnectionOpenAck(ack) => [seq([ - // wait::( - // l.chain().chain_id(), - // WaitForBlock(ibc_event.height.increment()), - // ), - RelayerMsg::Aggregate { - data: [].into(), - queue: [mk_aggregate_wait_for_update( - l.chain().chain_id(), - ack.client_id.clone(), - ack.counterparty_client_id.clone(), - ibc_event.height, - )] - .into(), - receiver: AggregateReceiver::from(Identified::new( - l.chain().chain_id(), - Aggregate::AggregateMsgAfterUpdate( - AggregateMsgAfterUpdate::ConnectionOpenConfirm( - AggregateConnectionOpenConfirm { - event_height: ibc_event.height, - event: ack, - }, - ), - ), - )), - }, - ])] - .into(), - IbcEvent::ConnectionOpenConfirm(confirm) => { - println!("connection opened: {confirm:#?}"); - - vec![] - } - - IbcEvent::ChannelOpenInit(init) => [seq([ - // wait::( - // l.chain().chain_id(), - // WaitForBlock(ibc_event.height.increment()), - // ), - RelayerMsg::Aggregate { - data: [].into(), - queue: [RelayerMsg::Aggregate { - data: [].into(), - queue: [fetch( - l.chain().chain_id(), - FetchChannelEnd { - at: ibc_event.height, - port_id: init.port_id.clone(), - channel_id: init.channel_id.clone(), - }, - )] - .into(), - receiver: AggregateReceiver::from(Identified::new( - l.chain().chain_id(), - Aggregate::ConnectionFetchFromChannelEnd( - AggregateConnectionFetchFromChannelEnd { - at: ibc_event.height, - }, - ), - )), - }] - .into(), - receiver: AggregateReceiver::from(Identified::new( - l.chain().chain_id(), - Aggregate::ChannelHandshakeUpdateClient( - AggregateChannelHandshakeUpdateClient { - update_to: ibc_event.height, - event_height: ibc_event.height, - channel_handshake_event: ChannelHandshakeEvent::Init(init), - }, - ), - )), - }, - ])] - .into(), - IbcEvent::ChannelOpenTry(try_) => [seq([ - // wait::( - // l.chain().chain_id(), - // WaitForBlock(ibc_event.height.increment()), - // ), - RelayerMsg::Aggregate { - data: [].into(), - queue: [RelayerMsg::Aggregate { - data: [].into(), - queue: [fetch( - l.chain().chain_id(), - FetchChannelEnd { - at: ibc_event.height, - port_id: try_.port_id.clone(), - channel_id: try_.channel_id.clone(), - }, - )] - .into(), - receiver: AggregateReceiver::from(Identified::new( - l.chain().chain_id(), - Aggregate::ConnectionFetchFromChannelEnd( - AggregateConnectionFetchFromChannelEnd { - at: ibc_event.height, - }, - ), - )), - }] - .into(), - receiver: AggregateReceiver::from(Identified::new( - l.chain().chain_id(), - Aggregate::ChannelHandshakeUpdateClient( - AggregateChannelHandshakeUpdateClient { - update_to: ibc_event.height, - event_height: ibc_event.height, - channel_handshake_event: ChannelHandshakeEvent::Try(try_), - }, - ), - )), - }, - ])] - .into(), - IbcEvent::ChannelOpenAck(ack) => [seq([ - // wait::( - // l.chain().chain_id(), - // WaitForBlock(ibc_event.height.increment()), - // ), - RelayerMsg::Aggregate { - data: [].into(), - queue: [RelayerMsg::Aggregate { - data: [].into(), - queue: [fetch( - l.chain().chain_id(), - FetchChannelEnd { - at: ibc_event.height, - port_id: ack.port_id.clone(), - channel_id: ack.channel_id.clone(), - }, - )] - .into(), - receiver: AggregateReceiver::from(Identified::new( - l.chain().chain_id(), - Aggregate::ConnectionFetchFromChannelEnd( - AggregateConnectionFetchFromChannelEnd { - at: ibc_event.height, - }, - ), - )), - }] - .into(), - receiver: AggregateReceiver::from(Identified::new( - l.chain().chain_id(), - Aggregate::ChannelHandshakeUpdateClient( - AggregateChannelHandshakeUpdateClient { - update_to: ibc_event.height, - event_height: ibc_event.height, - channel_handshake_event: ChannelHandshakeEvent::Ack(ack), - }, - ), - )), - }, - ])] - .into(), - - IbcEvent::ChannelOpenConfirm(confirm) => { - println!("channel opened: {confirm:#?}"); - - vec![] - } - - IbcEvent::RecvPacket(packet) => [seq([ - // wait::( - // l.chain().chain_id(), - // WaitForBlock(ibc_event.height.increment()), - // ), - RelayerMsg::Aggregate { - data: [].into(), - queue: [fetch( - l.chain().chain_id(), - FetchConnectionEnd { - at: ibc_event.height, - connection_id: packet.connection_id.clone(), - }, - )] - .into(), - receiver: AggregateReceiver::from(Identified::new( - l.chain().chain_id(), - Aggregate::PacketUpdateClient(AggregatePacketUpdateClient { - update_to: ibc_event.height, - event_height: ibc_event.height, - block_hash: ibc_event.block_hash, - packet_event: PacketEvent::Recv(packet), - }), - )), - }, - ])] - .into(), - IbcEvent::SendPacket(packet) => [seq([ - // wait::( - // l.chain().chain_id(), - // WaitForBlock(ibc_event.height.increment()), - // ), - RelayerMsg::Aggregate { - data: [].into(), - queue: [fetch( - l.chain().chain_id(), - FetchConnectionEnd { - at: ibc_event.height, - connection_id: packet.connection_id.clone(), - }, - )] - .into(), - receiver: AggregateReceiver::from(Identified::new( - l.chain().chain_id(), - Aggregate::PacketUpdateClient(AggregatePacketUpdateClient { - update_to: ibc_event.height, - event_height: ibc_event.height, - block_hash: ibc_event.block_hash, - packet_event: PacketEvent::Send(packet), - }), - )), - }, - ])] - .into(), - IbcEvent::AcknowledgePacket(ack) => { - tracing::info!(?ack, "packet acknowledged"); - [].into() - } - IbcEvent::TimeoutPacket(timeout) => { - tracing::error!(?timeout, "packet timed out"); - [].into() - } - IbcEvent::WriteAcknowledgement(write_ack) => { - tracing::info!(?write_ack, "packet acknowledgement written"); - [].into() - } - }, - Event::Command(command) => match command { - crate::msg::event::Command::UpdateClient { - client_id, - counterparty_client_id, - } => [RelayerMsg::Aggregate { - queue: [fetch::( - l.chain().chain_id(), - FetchTrustedClientState { - at: QueryHeight::Latest, - client_id: client_id.clone(), - }, - )] - .into(), - data: [].into(), - receiver: AggregateReceiver::from(Identified::new( - l.chain().chain_id(), - Aggregate::::UpdateClientFromClientId(AggregateUpdateClientFromClientId { - client_id, - counterparty_client_id, - }), - )), - }] - .into(), - }, - } -} - -fn mk_aggregate_wait_for_update( - chain_id: ChainIdOf, - client_id: L::ClientId, - counterparty_client_id: ::ClientId, - wait_for: HeightOf, -) -> RelayerMsg -where - AnyLightClientIdentified: From)>, - AggregateReceiver: From)>, -{ - RelayerMsg::Aggregate { - queue: [fetch::( - chain_id.clone(), - FetchTrustedClientState { - at: QueryHeight::Latest, - client_id: client_id.clone().clone(), - }, - )] - .into(), - data: [].into(), - receiver: AggregateReceiver::from(Identified::new( - chain_id, - Aggregate::::WaitForTrustedHeight(AggregateWaitForTrustedHeight { - wait_for, - client_id, - counterparty_client_id, - }), - )), - } -} - // /// For updating a client, the information we have originally is: // /// // /// - `chain_id`: the id of the chain that the client to be updated is on @@ -1320,2049 +615,6 @@ where // } // } -async fn handle_fetch(l: L, fetch: Fetch) -> Vec -where - AnyLightClientIdentified: From)>, - // TODO: Remove once we no longer unwrap - <::ClientId as TryFrom< - <::HostChain as Chain>::ClientId, - >>::Error: Debug, - <::ClientId as TryFrom< - <::HostChain as Chain>::ClientId, - >>::Error: Debug, -{ - let relayer_msg = match fetch { - Fetch::TrustedClientState(FetchTrustedClientState { at, client_id }) => { - // TODO: Split this into a separate query and aggregate - let height = match at { - QueryHeight::Latest => l.chain().query_latest_height().await, - QueryHeight::Specific(h) => h, - }; - - [data( - l.chain().chain_id(), - TrustedClientState { - fetched_at: height, - client_id: client_id.clone(), - trusted_client_state: l.query_client_state(client_id.into(), height).await, - }, - )] - .into() - } - Fetch::StateProof(msg) => [l.proof(msg)].into(), - Fetch::SelfClientState(FetchSelfClientState { at: height }) => { - // TODO: Split this into a separate query and aggregate - let height = match height { - QueryHeight::Latest => l.chain().query_latest_height().await, - QueryHeight::Specific(h) => h, - }; - - [data( - l.chain().chain_id(), - SelfClientState(l.chain().self_client_state(height).await), - )] - .into() - } - Fetch::SelfConsensusState(FetchSelfConsensusState { at: height }) => { - // TODO: Split this into a separate query and aggregate - let height = match height { - QueryHeight::Latest => l.chain().query_latest_height().await, - QueryHeight::Specific(h) => h, - }; - - [data( - l.chain().chain_id(), - SelfConsensusState(l.chain().self_consensus_state(height).await), - )] - .into() - } - Fetch::PacketAcknowledgement(FetchPacketAcknowledgement { - block_hash, - destination_port_id, - destination_channel_id, - sequence, - __marker, - }) => { - let ack = l - .chain() - .read_ack( - block_hash.clone(), - destination_channel_id.clone(), - destination_port_id.clone(), - sequence, - ) - .await; - - [data( - l.chain().chain_id(), - PacketAcknowledgement { - fetched_by: FetchPacketAcknowledgement { - block_hash, - destination_port_id, - destination_channel_id, - sequence, - __marker, - }, - ack, - }, - )] - .into() - } - Fetch::UpdateHeaders(fetch_update_headers) => { - l.generate_counterparty_updates(fetch_update_headers) - } - Fetch::LightClientSpecific(LightClientSpecificFetch(fetch)) => l.do_fetch(fetch).await, - Fetch::ChannelEnd(FetchChannelEnd { - at, - port_id, - channel_id, - }) => [data( - l.chain().chain_id(), - ChannelEnd { - channel: l.channel(channel_id, port_id, at).await, - __marker: PhantomData, - }, - )] - .into(), - Fetch::ConnectionEnd(FetchConnectionEnd { at, connection_id }) => [data( - l.chain().chain_id(), - ConnectionEnd(l.connection(connection_id, at).await), - )] - .into(), - }; - - relayer_msg -} - -async fn handle_wait(l: L, wait_msg: Wait) -> Vec -where - AnyLightClientIdentified: From)>, - AnyLightClientIdentified: From)>, -{ - match wait_msg { - Wait::Block(WaitForBlock(height)) => { - let chain_height = l.chain().query_latest_height().await; - - assert_eq!( - chain_height.revision_number(), - height.revision_number(), - "chain_height: {chain_height}, height: {height}", - height = Into::::into(height) - ); - - if chain_height.revision_height() >= height.revision_height() { - [].into() - } else { - [seq([ - // REVIEW: Defer until `now + chain.block_time()`? Would require a new method on chain - defer(now() + 1), - wait::(l.chain().chain_id(), WaitForBlock(height)), - ])] - .into() - } - } - Wait::Timestamp(WaitForTimestamp { - timestamp, - __marker, - }) => { - let chain_ts = l.chain().query_latest_timestamp().await; - - if chain_ts >= timestamp { - [].into() - } else { - [seq([ - // REVIEW: Defer until `now + chain.block_time()`? Would require a new method on chain - defer(now() + 1), - wait::( - l.chain().chain_id(), - WaitForTimestamp { - timestamp, - __marker, - }, - ), - ])] - .into() - } - } - Wait::TrustedHeight(WaitForTrustedHeight { - client_id, - height, - counterparty_client_id, - counterparty_chain_id, - }) => { - let latest_height = l.chain().query_latest_height_as_destination().await; - let trusted_client_state = l - .query_client_state(client_id.clone().into(), latest_height) - .await; - - if trusted_client_state.height().revision_height() >= height.revision_height() { - tracing::debug!( - "client height reached ({} >= {})", - trusted_client_state.height(), - height - ); - - [fetch::( - counterparty_chain_id, - FetchTrustedClientState { - at: QueryHeight::Specific(trusted_client_state.height()), - client_id: counterparty_client_id.clone(), - }, - )] - .into() - } else { - [seq([ - // REVIEW: Defer until `now + counterparty_chain.block_time()`? Would require a new method on chain - defer(now() + 1), - wait::( - l.chain().chain_id(), - Wait::TrustedHeight(WaitForTrustedHeight { - client_id, - height, - counterparty_client_id, - counterparty_chain_id, - }), - ), - ])] - .into() - } - } - } -} - -fn do_create( - Identified { - chain_id, - data: msg, - }: identified!(Aggregate), - data: VecDeque, -) -> Vec -where - identified!(TrustedClientState): IsAggregateData, - identified!(TrustedClientState): IsAggregateData, - - identified!(ClientStateProof): IsAggregateData, - identified!(ClientConsensusStateProof): IsAggregateData, - identified!(ConnectionProof): IsAggregateData, - identified!(ChannelEndProof): IsAggregateData, - identified!(CommitmentProof): IsAggregateData, - identified!(AcknowledgementProof): IsAggregateData, - - identified!(SelfClientState): IsAggregateData, - identified!(SelfConsensusState): IsAggregateData, - - identified!(ChannelEnd): IsAggregateData, - identified!(ConnectionEnd): IsAggregateData, - identified!(PacketAcknowledgement): IsAggregateData, - - AnyLightClientIdentified: From)>, - AnyLightClientIdentified: From)>, - AggregateData: From)>, - AggregateReceiver: From)>, -{ - match msg { - Aggregate::ConnectionOpenTry(init) => [aggregate_data::do_aggregate::( - Identified { - chain_id, - data: init, - }, - data, - )] - .into(), - Aggregate::ConnectionOpenAck(ack) => [aggregate_data::do_aggregate::( - Identified { - chain_id, - data: ack, - }, - data, - )] - .into(), - Aggregate::ConnectionOpenConfirm(confirm) => [aggregate_data::do_aggregate::( - Identified { - chain_id, - data: confirm, - }, - data, - )] - .into(), - Aggregate::ChannelOpenTry(try_) => [aggregate_data::do_aggregate::( - Identified { - chain_id, - data: try_, - }, - data, - )] - .into(), - Aggregate::ChannelOpenAck(ack) => [aggregate_data::do_aggregate::( - Identified { - chain_id, - data: ack, - }, - data, - )] - .into(), - Aggregate::ChannelOpenConfirm(confirm) => [aggregate_data::do_aggregate::( - Identified { - chain_id, - data: confirm, - }, - data, - )] - .into(), - Aggregate::UpdateClientFromClientId(update_client) => { - [aggregate_data::do_aggregate::( - Identified { - chain_id, - data: update_client, - }, - data, - )] - .into() - } - Aggregate::UpdateClient(update_client) => [aggregate_data::do_aggregate::( - Identified { - chain_id, - data: update_client, - }, - data, - )] - .into(), - Aggregate::UpdateClientWithCounterpartyChainIdData(aggregate) => { - [aggregate_data::do_aggregate::( - Identified { - chain_id, - data: aggregate, - }, - data, - )] - .into() - } - Aggregate::CreateClient(create_client) => [aggregate_data::do_aggregate::( - Identified { - chain_id, - data: create_client, - }, - data, - )] - .into(), - Aggregate::AggregateMsgAfterUpdate(aggregate) => [aggregate_data::do_aggregate::( - Identified { - chain_id, - data: aggregate, - }, - data, - )] - .into(), - Aggregate::LightClientSpecific(LightClientSpecificAggregate(aggregate)) => { - L::Aggregate::do_aggregate( - Identified { - chain_id, - data: aggregate, - }, - data, - ) - } - Aggregate::ConnectionFetchFromChannelEnd(aggregate) => { - [aggregate_data::do_aggregate::( - Identified { - chain_id, - data: aggregate, - }, - data, - )] - .into() - } - Aggregate::ChannelHandshakeUpdateClient(channel_handshake_update_client) => { - [aggregate_data::do_aggregate::( - Identified { - chain_id, - data: channel_handshake_update_client, - }, - data, - )] - .into() - } - Aggregate::PacketUpdateClient(packet_update_client) => { - [aggregate_data::do_aggregate::( - Identified { - chain_id, - data: packet_update_client, - }, - data, - )] - .into() - } - Aggregate::RecvPacket(recv_packet) => [aggregate_data::do_aggregate::( - Identified { - chain_id, - data: recv_packet, - }, - data, - )] - .into(), - Aggregate::AckPacket(ack_packet) => [aggregate_data::do_aggregate::( - Identified { - chain_id, - data: ack_packet, - }, - data, - )] - .into(), - Aggregate::WaitForTrustedHeight(agg) => [aggregate_data::do_aggregate::( - Identified { - chain_id, - data: agg, - }, - data, - )] - .into(), - Aggregate::FetchCounterpartyStateproof(agg) => [aggregate_data::do_aggregate::( - Identified { - chain_id, - data: agg, - }, - data, - )] - .into(), - } -} - -impl UseAggregate for identified!(AggregateChannelHandshakeUpdateClient) -where - identified!(ConnectionEnd): IsAggregateData, - AnyLightClientIdentified: From)>, - AggregateReceiver: From)>, -{ - type AggregatedData = HList![identified!(ConnectionEnd)]; - - fn aggregate( - Identified { - chain_id: this_chain_id, - data: - AggregateChannelHandshakeUpdateClient { - update_to, - channel_handshake_event, - event_height, - }, - }: Self, - hlist_pat![Identified { - chain_id: self_chain_id, - data: ConnectionEnd(connection), - }]: Self::AggregatedData, - ) -> RelayerMsg { - assert_eq!(this_chain_id, self_chain_id); - - let event_msg = match channel_handshake_event { - ChannelHandshakeEvent::Init(init) => { - AggregateMsgAfterUpdate::ChannelOpenTry(AggregateChannelOpenTry { - event_height, - event: init, - }) - } - ChannelHandshakeEvent::Try(try_) => { - AggregateMsgAfterUpdate::ChannelOpenAck(AggregateChannelOpenAck { - event_height, - event: try_, - }) - } - ChannelHandshakeEvent::Ack(ack) => { - AggregateMsgAfterUpdate::ChannelOpenConfirm(AggregateChannelOpenConfirm { - event_height, - event: ack, - }) - } - }; - - RelayerMsg::Aggregate { - data: [].into(), - queue: [mk_aggregate_wait_for_update( - this_chain_id.clone(), - connection.client_id, - connection.counterparty.client_id, - update_to, - )] - .into(), - receiver: AggregateReceiver::from(Identified::new( - this_chain_id, - Aggregate::AggregateMsgAfterUpdate(event_msg), - )), - } - } -} - -impl UseAggregate for identified!(AggregatePacketUpdateClient) -where - identified!(ConnectionEnd): IsAggregateData, - AnyLightClientIdentified: From)>, - AggregateReceiver: From)>, -{ - type AggregatedData = HList![identified!(ConnectionEnd)]; - - fn aggregate( - Identified { - chain_id: this_chain_id, - data: - AggregatePacketUpdateClient { - update_to, - event_height, - block_hash, - packet_event, - }, - }: Self, - hlist_pat![Identified { - chain_id: self_chain_id, - data: ConnectionEnd(connection), - }]: Self::AggregatedData, - ) -> RelayerMsg { - assert_eq!(this_chain_id, self_chain_id); - - let event = match packet_event { - PacketEvent::Send(send) => Aggregate::AggregateMsgAfterUpdate( - AggregateMsgAfterUpdate::RecvPacket(AggregateRecvPacket { - event_height, - event: send, - }), - ), - PacketEvent::Recv(recv) => Aggregate::AggregateMsgAfterUpdate( - AggregateMsgAfterUpdate::AckPacket(AggregateAckPacket { - event_height, - event: recv, - block_hash, - counterparty_client_id: connection.counterparty.client_id.clone(), - }), - ), - }; - - let agg = RelayerMsg::Aggregate { - queue: [fetch::( - this_chain_id.clone().clone(), - FetchTrustedClientState { - at: QueryHeight::Latest, - client_id: connection.client_id.clone().clone(), - }, - )] - .into(), - data: [].into(), - receiver: AggregateReceiver::from(Identified::new( - this_chain_id.clone(), - Aggregate::::WaitForTrustedHeight(AggregateWaitForTrustedHeight { - wait_for: update_to, - client_id: connection.client_id.clone().clone(), - counterparty_client_id: connection.counterparty.client_id.clone(), - }), - )), - }; - - RelayerMsg::Aggregate { - data: [].into(), - queue: [agg].into(), - receiver: AggregateReceiver::from(Identified::new(this_chain_id, event)), - } - } -} - -impl UseAggregate for identified!(AggregateConnectionFetchFromChannelEnd) -where - identified!(ChannelEnd): IsAggregateData, - AnyLightClientIdentified: From)>, -{ - type AggregatedData = HList![identified!(ChannelEnd)]; - - fn aggregate( - Identified { - chain_id: this_chain_id, - data: AggregateConnectionFetchFromChannelEnd { at }, - }: Self, - hlist_pat![Identified { - chain_id: self_chain_id, - data: ChannelEnd { - channel, - __marker: _ - }, - }]: Self::AggregatedData, - ) -> RelayerMsg { - assert_eq!(this_chain_id, self_chain_id); - - fetch( - this_chain_id, - FetchConnectionEnd { - at, - connection_id: channel.connection_hops[0].clone(), - }, - ) - } -} - -impl UseAggregate for identified!(AggregateUpdateClientFromClientId) -where - identified!(TrustedClientState): IsAggregateData, - // AnyLightClientIdentified: From)>, - AnyLightClientIdentified: From)>, - AggregateReceiver: From)>, -{ - type AggregatedData = HList![identified!(TrustedClientState)]; - - fn aggregate( - Identified { - chain_id: this_chain_id, - data: - AggregateUpdateClientFromClientId { - client_id, - counterparty_client_id, - }, - }: Self, - hlist_pat![Identified { - chain_id: self_chain_id, - data: TrustedClientState { - fetched_at, - client_id: trusted_client_state_client_id, - trusted_client_state, - }, - }]: Self::AggregatedData, - ) -> RelayerMsg { - assert_eq!(this_chain_id, self_chain_id); - assert_eq!(trusted_client_state_client_id, client_id); - - let counterparty_chain_id = trusted_client_state.chain_id(); - - RelayerMsg::Aggregate { - queue: [fetch::( - counterparty_chain_id.clone(), - FetchTrustedClientState { - at: QueryHeight::Specific(trusted_client_state.height()), - client_id: counterparty_client_id.clone(), - }, - )] - .into(), - data: [].into(), - receiver: AggregateReceiver::from(Identified::new( - this_chain_id, - Aggregate::UpdateClientWithCounterpartyChainIdData( - AggregateUpdateClientWithCounterpartyChainId { - update_to: fetched_at, - client_id, - counterparty_client_id, - counterparty_chain_id, - }, - ), - )), - } - } -} - -impl UseAggregate for identified!(AggregateUpdateClient) -where - identified!(TrustedClientState): IsAggregateData, - // AnyLightClientIdentified: From)>, - AnyLightClientIdentified: From)>, - AggregateReceiver: From)>, -{ - type AggregatedData = HList![identified!(TrustedClientState)]; - - fn aggregate( - Identified { - chain_id: this_chain_id, - data: - AggregateUpdateClient { - update_to, - client_id: update_client_id, - counterparty_client_id: update_counterparty_client_id, - }, - }: Self, - hlist_pat![Identified { - chain_id: self_chain_id, - data: TrustedClientState { - fetched_at: _, - client_id: trusted_client_state_client_id, - trusted_client_state, - }, - }]: Self::AggregatedData, - ) -> RelayerMsg { - assert_eq!(this_chain_id, self_chain_id); - assert_eq!(update_client_id, trusted_client_state_client_id); - - let counterparty_chain_id: ChainIdOf = trusted_client_state.chain_id(); - - RelayerMsg::Aggregate { - queue: [fetch::( - counterparty_chain_id.clone(), - FetchTrustedClientState { - at: QueryHeight::Latest, - client_id: update_counterparty_client_id.clone(), - }, - )] - .into(), - data: [].into(), - receiver: AggregateReceiver::from(Identified::new( - this_chain_id, - Aggregate::UpdateClientWithCounterpartyChainIdData( - AggregateUpdateClientWithCounterpartyChainId { - update_to, - client_id: update_client_id, - counterparty_client_id: update_counterparty_client_id, - counterparty_chain_id, - }, - ), - )), - } - } -} - -impl UseAggregate - for identified!(AggregateUpdateClientWithCounterpartyChainId) -where - identified!(TrustedClientState): IsAggregateData, - AnyLightClientIdentified: From)>, - AggregateReceiver: From)>, -{ - type AggregatedData = HList![identified!(TrustedClientState)]; - - fn aggregate( - Identified { - chain_id: this_chain_id, - data: - AggregateUpdateClientWithCounterpartyChainId { - update_to, - client_id: update_client_id, - counterparty_client_id: update_counterparty_client_id, - counterparty_chain_id: update_counterparty_chain_id, - }, - }: Self, - hlist_pat![Identified { - chain_id: counterparty_chain_id, - data: TrustedClientState { - fetched_at: _, - client_id: latest_trusted_client_state_client_id, - trusted_client_state - }, - }]: Self::AggregatedData, - ) -> RelayerMsg { - let self_chain_id: ChainIdOf = trusted_client_state.chain_id(); - - assert_eq!(this_chain_id, self_chain_id); - assert_eq!( - latest_trusted_client_state_client_id, - update_counterparty_client_id - ); - assert_eq!(counterparty_chain_id, update_counterparty_chain_id); - - fetch::( - this_chain_id, - FetchUpdateHeaders { - client_id: update_client_id, - counterparty_client_id: update_counterparty_client_id, - counterparty_chain_id, - update_from: trusted_client_state.height(), - update_to, - }, - ) - } -} - -impl UseAggregate for identified!(AggregateWaitForTrustedHeight) -where - identified!(TrustedClientState): IsAggregateData, - AnyLightClientIdentified: From)>, - AggregateReceiver: From)>, -{ - type AggregatedData = HList![identified!(TrustedClientState)]; - - fn aggregate( - Identified { - chain_id: this_chain_id, - data: - AggregateWaitForTrustedHeight { - wait_for, - client_id, - counterparty_client_id, - }, - }: Self, - hlist_pat![Identified { - chain_id: trusted_client_state_chain_id, - data: TrustedClientState { - fetched_at: _, - client_id: trusted_client_state_client_id, - trusted_client_state - }, - }]: Self::AggregatedData, - ) -> RelayerMsg { - assert_eq!(trusted_client_state_client_id, client_id); - assert_eq!(trusted_client_state_chain_id, this_chain_id); - - let counterparty_chain_id: ChainIdOf = trusted_client_state.chain_id(); - - tracing::debug!("building WaitForTrustedHeight"); - - wait::( - counterparty_chain_id, - WaitForTrustedHeight { - height: wait_for, - client_id: counterparty_client_id, - counterparty_client_id: client_id, - counterparty_chain_id: this_chain_id, - }, - ) - } -} - -impl UseAggregate for identified!(AggregateMsgAfterUpdate) -where - identified!(TrustedClientState): IsAggregateData, - AnyLightClientIdentified: From)>, - AnyLightClientIdentified: From)>, - AggregateData: From)>, - AggregateReceiver: From)>, -{ - type AggregatedData = HList![identified!(TrustedClientState)]; - - fn aggregate( - Identified { - chain_id: this_chain_id, - data: msg_to_aggregate, - }: Self, - hlist_pat![Identified { - chain_id: self_chain_id, - data: TrustedClientState { - fetched_at: trusted_client_state_fetched_at_height, - client_id: trusted_client_state_client_id, - trusted_client_state - }, - }]: Self::AggregatedData, - ) -> RelayerMsg { - assert_eq!(this_chain_id, self_chain_id); - // assert_eq!(client_id, trusted_client_state_client_id); - - match msg_to_aggregate { - AggregateMsgAfterUpdate::ConnectionOpenTry(AggregateConnectionOpenTry { - event_height, - event, - }) => { - let consensus_state_height = trusted_client_state_fetched_at_height; - - assert_eq!( - consensus_state_height.revision_number(), - event_height.revision_number(), - "{consensus_state_height}, {event_height}", - ); - - assert!( - consensus_state_height.revision_height() >= event_height.revision_height(), - "{} < {}", - consensus_state_height.revision_height(), - event_height.revision_height() - ); - - let trusted_client_state_height = trusted_client_state.height(); - - RelayerMsg::Aggregate { - data: [AggregateData::from(Identified::new( - this_chain_id.clone(), - Data::TrustedClientState(TrustedClientState { - fetched_at: trusted_client_state_fetched_at_height, - client_id: trusted_client_state_client_id, - trusted_client_state, - }), - ))] - .into(), - queue: [ - fetch::( - this_chain_id.clone(), - FetchStateProof { - at: trusted_client_state_fetched_at_height, - path: proof::Path::ClientStatePath(ClientStatePath { - client_id: event.client_id.clone().into(), - }), - }, - ), - fetch::( - this_chain_id.clone(), - FetchStateProof { - at: trusted_client_state_fetched_at_height, - path: proof::Path::ClientConsensusStatePath( - ClientConsensusStatePath { - client_id: event.client_id.clone().into(), - height: trusted_client_state_height, - }, - ), - }, - ), - fetch::( - this_chain_id.clone(), - FetchStateProof { - at: trusted_client_state_fetched_at_height, - path: proof::Path::ConnectionPath(ConnectionPath { - connection_id: event.connection_id.clone(), - }), - }, - ), - fetch::( - this_chain_id.clone(), - FetchConnectionEnd { - at: trusted_client_state_fetched_at_height, - connection_id: event.connection_id.clone(), - }, - ), - ] - .into(), - receiver: AggregateReceiver::from(Identified::new( - this_chain_id, - Aggregate::ConnectionOpenTry(AggregateConnectionOpenTry { - event_height, - event, - }), - )), - } - } - AggregateMsgAfterUpdate::ConnectionOpenAck(AggregateConnectionOpenAck { - event_height, - event, - }) => { - let consensus_state_height = trusted_client_state_fetched_at_height; - - assert_eq!( - consensus_state_height.revision_number(), - event_height.revision_number(), - "{consensus_state_height}, {event_height}", - ); - - assert!( - consensus_state_height.revision_height() >= event_height.revision_height(), - "{} < {}", - consensus_state_height.revision_height(), - event_height.revision_height() - ); - - let trusted_client_state_height = trusted_client_state.height(); - - RelayerMsg::Aggregate { - data: [AggregateData::from(Identified::new( - this_chain_id.clone(), - Data::TrustedClientState(TrustedClientState { - fetched_at: trusted_client_state_fetched_at_height, - client_id: trusted_client_state_client_id, - trusted_client_state, - }), - ))] - .into(), - queue: [ - fetch::( - this_chain_id.clone(), - FetchStateProof { - at: trusted_client_state_fetched_at_height, - path: proof::Path::ClientStatePath(ClientStatePath { - client_id: event.client_id.clone().into(), - }), - }, - ), - fetch::( - this_chain_id.clone(), - FetchStateProof { - at: trusted_client_state_fetched_at_height, - path: proof::Path::ClientConsensusStatePath( - ClientConsensusStatePath { - client_id: event.client_id.clone().into(), - height: trusted_client_state_height, - }, - ), - }, - ), - fetch::( - this_chain_id.clone(), - FetchStateProof { - at: trusted_client_state_fetched_at_height, - path: proof::Path::ConnectionPath(ConnectionPath { - connection_id: event.connection_id.clone(), - }), - }, - ), - fetch::( - this_chain_id.clone(), - FetchConnectionEnd { - at: trusted_client_state_fetched_at_height, - connection_id: event.connection_id.clone(), - }, - ), - ] - .into(), - receiver: AggregateReceiver::from(Identified::new( - this_chain_id, - Aggregate::ConnectionOpenAck(AggregateConnectionOpenAck { - event_height, - event, - }), - )), - } - } - AggregateMsgAfterUpdate::ConnectionOpenConfirm(AggregateConnectionOpenConfirm { - event_height, - event, - }) => { - let consensus_state_height = trusted_client_state_fetched_at_height; - - assert_eq!( - consensus_state_height.revision_number(), - event_height.revision_number(), - "{consensus_state_height}, {event_height}", - ); - - assert!( - consensus_state_height.revision_height() >= event_height.revision_height(), - "{} < {}", - consensus_state_height.revision_height(), - event_height.revision_height() - ); - - RelayerMsg::Aggregate { - data: [AggregateData::from(Identified::new( - this_chain_id.clone(), - Data::TrustedClientState(TrustedClientState { - fetched_at: trusted_client_state_fetched_at_height, - client_id: trusted_client_state_client_id, - trusted_client_state, - }), - ))] - .into(), - queue: [fetch::( - this_chain_id.clone(), - Fetch::StateProof(FetchStateProof { - at: trusted_client_state_fetched_at_height, - path: proof::Path::ConnectionPath(ConnectionPath { - connection_id: event.connection_id.clone(), - }), - }), - )] - .into(), - receiver: AggregateReceiver::from(Identified::new( - this_chain_id, - Aggregate::ConnectionOpenConfirm(AggregateConnectionOpenConfirm { - event_height, - event, - }), - )), - } - } - AggregateMsgAfterUpdate::ChannelOpenTry(AggregateChannelOpenTry { - event_height, - event, - }) => { - let consensus_state_height = trusted_client_state_fetched_at_height; - - assert_eq!( - consensus_state_height.revision_number(), - event_height.revision_number(), - "{consensus_state_height}, {event_height}", - ); - - assert!( - consensus_state_height.revision_height() >= event_height.revision_height(), - "{} < {}", - consensus_state_height.revision_height(), - event_height.revision_height() - ); - - RelayerMsg::Aggregate { - data: [AggregateData::from(Identified::new( - this_chain_id.clone(), - Data::TrustedClientState(TrustedClientState { - fetched_at: trusted_client_state_fetched_at_height, - client_id: trusted_client_state_client_id, - trusted_client_state, - }), - ))] - .into(), - queue: [ - RelayerMsg::Aggregate { - data: [].into(), - queue: [fetch::( - this_chain_id.clone(), - FetchChannelEnd { - at: trusted_client_state_fetched_at_height, - port_id: event.port_id.clone(), - channel_id: event.channel_id.clone(), - }, - )] - .into(), - receiver: AggregateReceiver::from(Identified::new( - this_chain_id.clone(), - Aggregate::ConnectionFetchFromChannelEnd( - AggregateConnectionFetchFromChannelEnd { - at: trusted_client_state_fetched_at_height, - }, - ), - )), - }, - fetch::( - this_chain_id.clone(), - FetchStateProof { - at: trusted_client_state_fetched_at_height, - path: proof::Path::ChannelEndPath(ChannelEndPath { - port_id: event.port_id.clone(), - channel_id: event.channel_id.clone(), - }), - }, - ), - fetch::( - this_chain_id.clone(), - FetchChannelEnd { - at: trusted_client_state_fetched_at_height, - port_id: event.port_id.clone(), - channel_id: event.channel_id.clone(), - }, - ), - ] - .into(), - receiver: AggregateReceiver::from(Identified::new( - this_chain_id, - Aggregate::ChannelOpenTry(AggregateChannelOpenTry { - event_height, - event, - }), - )), - } - } - AggregateMsgAfterUpdate::ChannelOpenAck(AggregateChannelOpenAck { - event_height, - event, - }) => { - let consensus_state_height = trusted_client_state_fetched_at_height; - - assert_eq!( - consensus_state_height.revision_number(), - event_height.revision_number(), - "{consensus_state_height}, {event_height}", - ); - - assert!( - consensus_state_height.revision_height() >= event_height.revision_height(), - "{} < {}", - consensus_state_height.revision_height(), - event_height.revision_height() - ); - - // RelayerMsg::Sequence([].into()); - RelayerMsg::Aggregate { - data: [AggregateData::from(Identified::new( - this_chain_id.clone(), - Data::TrustedClientState(TrustedClientState { - fetched_at: trusted_client_state_fetched_at_height, - client_id: trusted_client_state_client_id, - trusted_client_state, - }), - ))] - .into(), - queue: [ - fetch::( - this_chain_id.clone(), - FetchStateProof { - at: trusted_client_state_fetched_at_height, - path: proof::Path::ChannelEndPath(ChannelEndPath { - port_id: event.port_id.clone(), - channel_id: event.channel_id.clone(), - }), - }, - ), - fetch::( - this_chain_id.clone(), - FetchChannelEnd { - at: trusted_client_state_fetched_at_height, - port_id: event.port_id.clone(), - channel_id: event.channel_id.clone(), - }, - ), - ] - .into(), - receiver: AggregateReceiver::from(Identified::new( - this_chain_id, - Aggregate::ChannelOpenAck(AggregateChannelOpenAck { - event_height, - event, - }), - )), - } - } - AggregateMsgAfterUpdate::ChannelOpenConfirm(AggregateChannelOpenConfirm { - event_height, - event, - }) => { - let consensus_state_height = trusted_client_state_fetched_at_height; - - assert_eq!( - consensus_state_height.revision_number(), - event_height.revision_number(), - "{consensus_state_height}, {event_height}", - ); - - assert!( - consensus_state_height.revision_height() >= event_height.revision_height(), - "{} < {}", - consensus_state_height.revision_height(), - event_height.revision_height() - ); - - RelayerMsg::Aggregate { - data: [AggregateData::from(Identified::new( - this_chain_id.clone(), - Data::TrustedClientState(TrustedClientState { - fetched_at: trusted_client_state_fetched_at_height, - client_id: trusted_client_state_client_id, - trusted_client_state, - }), - ))] - .into(), - queue: [ - fetch::( - this_chain_id.clone(), - FetchStateProof { - at: trusted_client_state_fetched_at_height, - path: proof::Path::ChannelEndPath(ChannelEndPath { - port_id: event.port_id.clone(), - channel_id: event.channel_id.clone(), - }), - }, - ), - fetch::( - this_chain_id.clone(), - FetchChannelEnd { - at: trusted_client_state_fetched_at_height, - port_id: event.port_id.clone(), - channel_id: event.channel_id.clone(), - }, - ), - ] - .into(), - receiver: AggregateReceiver::from(Identified::new( - this_chain_id, - Aggregate::ChannelOpenConfirm(AggregateChannelOpenConfirm { - event_height, - event, - }), - )), - } - } - AggregateMsgAfterUpdate::RecvPacket(AggregateRecvPacket { - event_height, - event, - }) => { - // - tracing::debug!("building aggregate for RecvPacket"); - - RelayerMsg::Aggregate { - data: [AggregateData::from(Identified::new( - this_chain_id.clone(), - Data::TrustedClientState(TrustedClientState { - fetched_at: trusted_client_state_fetched_at_height, - client_id: trusted_client_state_client_id, - trusted_client_state, - }), - ))] - .into(), - queue: [fetch::( - this_chain_id.clone(), - FetchStateProof { - at: trusted_client_state_fetched_at_height, - path: proof::Path::CommitmentPath(CommitmentPath { - port_id: event.packet_src_port.clone(), - channel_id: event.packet_src_channel.clone(), - sequence: event.packet_sequence, - }), - }, - )] - .into(), - receiver: AggregateReceiver::from(Identified::new( - this_chain_id, - Aggregate::RecvPacket(AggregateRecvPacket { - event_height, - event, - }), - )), - } - } - AggregateMsgAfterUpdate::AckPacket(AggregateAckPacket { - event_height, - event, - block_hash, - counterparty_client_id, - }) => RelayerMsg::Aggregate { - data: [AggregateData::from(Identified::new( - this_chain_id.clone(), - Data::TrustedClientState(TrustedClientState { - fetched_at: trusted_client_state_fetched_at_height, - client_id: trusted_client_state_client_id, - trusted_client_state: trusted_client_state.clone(), - }), - ))] - .into(), - queue: [ - fetch::( - this_chain_id.clone(), - FetchPacketAcknowledgement { - block_hash: block_hash.clone(), - destination_port_id: event.packet_dst_port.clone(), - destination_channel_id: event.packet_dst_channel.clone(), - sequence: event.packet_sequence, - __marker: PhantomData, - }, - ), - fetch::( - this_chain_id.clone(), - FetchStateProof { - at: trusted_client_state_fetched_at_height, - path: proof::Path::AcknowledgementPath(AcknowledgementPath { - port_id: event.packet_dst_port.clone(), - channel_id: event.packet_dst_channel.clone(), - sequence: event.packet_sequence, - }), - }, - ), - ] - .into(), - receiver: AggregateReceiver::from(Identified::new( - this_chain_id, - Aggregate::AckPacket(AggregateAckPacket { - event_height, - event, - block_hash, - counterparty_client_id, - }), - )), - }, - } - } -} - -impl UseAggregate for identified!(AggregateConnectionOpenTry) -where - identified!(TrustedClientState): IsAggregateData, - identified!(ClientStateProof): IsAggregateData, - identified!(ClientConsensusStateProof): IsAggregateData, - identified!(ConnectionProof): IsAggregateData, - identified!(ConnectionEnd): IsAggregateData, - AnyLightClientIdentified: From)>, -{ - type AggregatedData = HList![ - identified!(TrustedClientState), - identified!(ClientStateProof), - identified!(ClientConsensusStateProof), - identified!(ConnectionProof), - identified!(ConnectionEnd), - ]; - - fn aggregate( - Identified { - chain_id: this_chain_id, - data: - AggregateConnectionOpenTry { - event_height: trusted_height, - event, - }, - }: Self, - hlist_pat![ - Identified { - chain_id: trusted_client_state_chain_id, - data: TrustedClientState { - fetched_at: _, - client_id: _, - trusted_client_state - } - }, - Identified { - chain_id: client_state_proof_chain_id, - data: ClientStateProof { - height: client_state_proof_height, - proof: client_state_proof, - } - }, - Identified { - chain_id: consensus_state_proof_chain_id, - data: ClientConsensusStateProof { - height: consensus_state_proof_height, - proof: consensus_state_proof, - } - }, - Identified { - chain_id: connection_proof_chain_id, - data: ConnectionProof { - height: connection_proof_height, - proof: connection_proof, - } - }, - Identified { - chain_id: connection_end_chain_id, - data: ConnectionEnd(connection_end), - }, - ]: Self::AggregatedData, - ) -> RelayerMsg { - assert!(consensus_state_proof_height.revision_height() >= trusted_height.revision_height()); - assert!(client_state_proof_height.revision_height() >= trusted_height.revision_height()); - - let counterparty_chain_id: ChainIdOf = trusted_client_state.chain_id(); - - assert_eq!(trusted_client_state_chain_id, this_chain_id); - - assert_eq!(client_state_proof_chain_id, this_chain_id); - assert_eq!(consensus_state_proof_chain_id, this_chain_id); - assert_eq!(connection_proof_chain_id, this_chain_id); - assert_eq!(connection_end_chain_id, this_chain_id); - - let consensus_height = trusted_client_state.height(); - - msg::( - counterparty_chain_id, - Msg::ConnectionOpenTry(MsgConnectionOpenTryData { - msg: MsgConnectionOpenTry { - client_id: event.counterparty_client_id, - client_state: trusted_client_state, - counterparty: connection::counterparty::Counterparty { - client_id: event.client_id, - connection_id: event.connection_id, - prefix: MerklePrefix { - key_prefix: b"ibc".to_vec(), - }, - }, - delay_period: DELAY_PERIOD, - counterparty_versions: connection_end.versions, - proof_height: connection_proof_height, - proof_init: connection_proof, - proof_client: client_state_proof, - proof_consensus: consensus_state_proof, - consensus_height, - }, - }), - ) - } -} - -impl UseAggregate for identified!(AggregateConnectionOpenAck) -where - identified!(TrustedClientState): IsAggregateData, - identified!(ClientStateProof): IsAggregateData, - identified!(ClientConsensusStateProof): IsAggregateData, - identified!(ConnectionProof): IsAggregateData, - identified!(ConnectionEnd): IsAggregateData, - AnyLightClientIdentified: From)>, -{ - type AggregatedData = HList![ - identified!(TrustedClientState), - identified!(ClientStateProof), - identified!(ClientConsensusStateProof), - identified!(ConnectionProof), - identified!(ConnectionEnd), - ]; - - fn aggregate( - Identified { - chain_id: this_chain_id, - data: - AggregateConnectionOpenAck { - event_height: trusted_height, - event, - }, - }: Self, - hlist_pat![ - Identified { - chain_id: trusted_client_state_chain_id, - data: TrustedClientState { - fetched_at: _, - client_id: _, - trusted_client_state - } - }, - Identified { - chain_id: client_state_proof_chain_id, - data: ClientStateProof { - height: client_state_proof_height, - proof: client_state_proof, - } - }, - Identified { - chain_id: consensus_state_proof_chain_id, - data: ClientConsensusStateProof { - height: consensus_state_proof_height, - proof: consensus_state_proof, - } - }, - Identified { - chain_id: connection_proof_chain_id, - data: ConnectionProof { - height: connection_proof_height, - proof: connection_proof, - } - }, - Identified { - chain_id: connection_end_chain_id, - data: ConnectionEnd(connection_end), - }, - ]: Self::AggregatedData, - ) -> RelayerMsg { - assert!(consensus_state_proof_height.revision_height() >= trusted_height.revision_height()); - assert!(client_state_proof_height.revision_height() >= trusted_height.revision_height()); - - let counterparty_chain_id: ChainIdOf = trusted_client_state.chain_id(); - - assert_eq!(trusted_client_state_chain_id, this_chain_id); - assert_eq!(client_state_proof_chain_id, this_chain_id); - assert_eq!(consensus_state_proof_chain_id, this_chain_id); - assert_eq!(connection_proof_chain_id, this_chain_id); - assert_eq!(connection_end_chain_id, this_chain_id); - - let consensus_height = trusted_client_state.height(); - - msg::( - counterparty_chain_id, - Msg::ConnectionOpenAck(MsgConnectionOpenAckData { - msg: MsgConnectionOpenAck { - connection_id: event.counterparty_connection_id, - counterparty_connection_id: event.connection_id, - // TODO: Figure out a way to not panic here, likely by encoding this invariant into the type somehow - version: connection_end.versions[0].clone(), - client_state: trusted_client_state, - proof_height: connection_proof_height.into(), - proof_try: connection_proof, - proof_client: client_state_proof, - proof_consensus: consensus_state_proof, - consensus_height: consensus_height.into(), - }, - }), - ) - } -} - -impl UseAggregate for identified!(AggregateConnectionOpenConfirm) -where - identified!(TrustedClientState): IsAggregateData, - identified!(ClientStateProof): IsAggregateData, - identified!(ClientConsensusStateProof): IsAggregateData, - identified!(ConnectionProof): IsAggregateData, - AnyLightClientIdentified: From)>, -{ - type AggregatedData = HList![ - identified!(TrustedClientState), - identified!(ConnectionProof), - ]; - - fn aggregate( - Identified { - chain_id: this_chain_id, - data: - AggregateConnectionOpenConfirm { - event_height: _, - event, - }, - }: Self, - hlist_pat![ - Identified { - chain_id: trusted_client_state_chain_id, - data: TrustedClientState { - fetched_at: _, - client_id: _, - trusted_client_state - } - }, - Identified { - chain_id: connection_proof_chain_id, - data: ConnectionProof { - height: connection_proof_height, - proof: connection_proof - } - }, - ]: Self::AggregatedData, - ) -> RelayerMsg { - let counterparty_chain_id: ChainIdOf = trusted_client_state.chain_id(); - - assert_eq!(trusted_client_state_chain_id, this_chain_id); - assert_eq!(connection_proof_chain_id, this_chain_id); - - msg::( - counterparty_chain_id, - Msg::ConnectionOpenConfirm(MsgConnectionOpenConfirmData(MsgConnectionOpenConfirm { - connection_id: event.counterparty_connection_id, - proof_height: connection_proof_height, - proof_ack: connection_proof, - })), - ) - } -} - -impl UseAggregate for identified!(AggregateChannelOpenTry) -where - identified!(TrustedClientState): IsAggregateData, - identified!(ChannelEndProof): IsAggregateData, - identified!(ConnectionEnd): IsAggregateData, - identified!(ChannelEnd): IsAggregateData, - AnyLightClientIdentified: From)>, -{ - type AggregatedData = HList![ - identified!(TrustedClientState), - identified!(ChannelEndProof), - identified!(ConnectionEnd), - identified!(ChannelEnd), - ]; - - fn aggregate( - Identified { - chain_id: this_chain_id, - data: - AggregateChannelOpenTry { - event_height: _, - event, - }, - }: Self, - hlist_pat![ - Identified { - chain_id: trusted_client_state_chain_id, - data: TrustedClientState { - fetched_at: _, - client_id: _, - trusted_client_state - } - }, - Identified { - chain_id: channel_proof_chain_id, - data: ChannelEndProof { - proof: channel_proof, - height: channel_proof_height - } - }, - Identified { - chain_id: _connection_end_chain_id, - data: ConnectionEnd(connection) - }, - Identified { - chain_id: _channel_end_chain_id, - data: ChannelEnd { - channel, - __marker: _ - }, - }, - ]: Self::AggregatedData, - ) -> RelayerMsg { - assert_eq!(this_chain_id, trusted_client_state_chain_id); - - let counterparty_chain_id: ChainIdOf = trusted_client_state.chain_id(); - - assert_eq!(channel_proof_chain_id, this_chain_id); - - msg::( - counterparty_chain_id, - MsgChannelOpenTryData { - msg: MsgChannelOpenTry { - port_id: channel.counterparty.port_id.clone(), - channel: Channel { - state: channel::state::State::Tryopen, - ordering: channel.ordering, - counterparty: channel::counterparty::Counterparty { - port_id: event.port_id.clone(), - channel_id: event.channel_id.clone().to_string(), - }, - connection_hops: vec![connection - .counterparty - .connection_id - .parse() - .unwrap()], - version: event.version.clone(), - }, - // NOTE: Review behaviour here - counterparty_version: event.version, - proof_init: channel_proof, - proof_height: channel_proof_height.into(), - }, - __marker: PhantomData, - }, - ) - } -} - -impl UseAggregate for identified!(AggregateChannelOpenAck) -where - identified!(TrustedClientState): IsAggregateData, - identified!(ChannelEndProof): IsAggregateData, - identified!(ChannelEnd): IsAggregateData, - AnyLightClientIdentified: From)>, -{ - type AggregatedData = HList![ - identified!(TrustedClientState), - identified!(ChannelEndProof), - identified!(ChannelEnd), - ]; - - fn aggregate( - Identified { - chain_id: this_chain_id, - data: - AggregateChannelOpenAck { - event_height: _, - event, - }, - }: Self, - hlist_pat![ - Identified { - chain_id: trusted_client_state_chain_id, - data: TrustedClientState { - fetched_at: _, - client_id: _, - trusted_client_state - } - }, - Identified { - chain_id: channel_proof_chain_id, - data: ChannelEndProof { - height: channel_proof_height, - proof: channel_proof, - } - }, - Identified { - chain_id: channel_end_chain_id, - data: ChannelEnd { - channel, - __marker: _ - }, - }, - ]: Self::AggregatedData, - ) -> RelayerMsg { - let counterparty_chain_id: ChainIdOf = trusted_client_state.chain_id(); - - assert_eq!(trusted_client_state_chain_id, this_chain_id); - assert_eq!(channel_proof_chain_id, this_chain_id); - assert_eq!(channel_end_chain_id, this_chain_id); - - msg::( - counterparty_chain_id, - MsgChannelOpenAckData { - msg: MsgChannelOpenAck { - port_id: channel.counterparty.port_id.clone(), - channel_id: event.counterparty_channel_id, - counterparty_channel_id: event.channel_id, - counterparty_version: event.version, - proof_try: channel_proof, - proof_height: channel_proof_height.into(), - }, - __marker: PhantomData, - }, - ) - } -} - -impl UseAggregate for identified!(AggregateChannelOpenConfirm) -where - identified!(TrustedClientState): IsAggregateData, - identified!(ChannelEndProof): IsAggregateData, - identified!(ChannelEnd): IsAggregateData, - AnyLightClientIdentified: From)>, -{ - type AggregatedData = HList![ - identified!(TrustedClientState), - identified!(ChannelEndProof), - identified!(ChannelEnd), - ]; - - fn aggregate( - Identified { - chain_id: this_chain_id, - data: - AggregateChannelOpenConfirm { - event_height: _, - event, - }, - }: Self, - hlist_pat![ - Identified { - chain_id: trusted_client_state_chain_id, - data: TrustedClientState { - fetched_at: _, - client_id: _, - trusted_client_state - } - }, - Identified { - chain_id: channel_proof_chain_id, - data: ChannelEndProof { - height: channel_proof_height, - proof: channel_proof, - } - }, - Identified { - chain_id: channel_end_chain_id, - data: ChannelEnd { - channel, - __marker: _ - }, - }, - ]: Self::AggregatedData, - ) -> RelayerMsg { - assert_eq!(this_chain_id, trusted_client_state_chain_id); - assert_eq!(this_chain_id, channel_proof_chain_id); - assert_eq!(channel_end_chain_id, this_chain_id); - - let counterparty_chain_id: ChainIdOf = trusted_client_state.chain_id(); - - msg::( - counterparty_chain_id, - MsgChannelOpenConfirmData { - msg: MsgChannelOpenConfirm { - port_id: channel.counterparty.port_id, - channel_id: event.counterparty_channel_id, - proof_ack: channel_proof, - proof_height: channel_proof_height.into(), - }, - __marker: PhantomData, - }, - ) - } -} - -impl UseAggregate for identified!(AggregateRecvPacket) -where - identified!(TrustedClientState): IsAggregateData, - identified!(CommitmentProof): IsAggregateData, - AnyLightClientIdentified: From)>, -{ - type AggregatedData = HList![ - identified!(TrustedClientState), - identified!(CommitmentProof), - ]; - - fn aggregate( - Identified { - chain_id: this_chain_id, - data: - AggregateRecvPacket { - event_height: _, - event, - }, - }: Self, - hlist_pat![ - Identified { - chain_id: trusted_client_state_chain_id, - data: TrustedClientState { - fetched_at: _, - client_id: _, - trusted_client_state - } - }, - Identified { - chain_id: commitment_proof_chain_id, - data: CommitmentProof { - height: commitment_proof_height, - proof: commitment_proof - } - }, - ]: Self::AggregatedData, - ) -> RelayerMsg { - assert_eq!(this_chain_id, trusted_client_state_chain_id); - assert_eq!(this_chain_id, commitment_proof_chain_id); - - let counterparty_chain_id: ChainIdOf = trusted_client_state.chain_id(); - - msg::( - counterparty_chain_id, - MsgRecvPacketData { - msg: MsgRecvPacket { - packet: Packet { - sequence: event.packet_sequence, - source_port: event.packet_src_port, - source_channel: event.packet_src_channel, - destination_port: event.packet_dst_port, - destination_channel: event.packet_dst_channel, - data: event.packet_data_hex, - timeout_height: event.packet_timeout_height, - timeout_timestamp: event.packet_timeout_timestamp, - }, - proof_commitment: commitment_proof, - proof_height: commitment_proof_height.into(), - }, - __marker: PhantomData, - }, - ) - } -} - -impl UseAggregate for identified!(AggregateAckPacket) -where - identified!(TrustedClientState): IsAggregateData, - identified!(PacketAcknowledgement): IsAggregateData, - identified!(AcknowledgementProof): IsAggregateData, - AnyLightClientIdentified: From)>, -{ - type AggregatedData = HList![ - identified!(TrustedClientState), - identified!(PacketAcknowledgement), - identified!(AcknowledgementProof), - ]; - - fn aggregate( - Identified { - chain_id: this_chain_id, - data: - AggregateAckPacket { - event_height: _, - event, - block_hash: _, - counterparty_client_id: _, - }, - }: Self, - hlist_pat![ - Identified { - chain_id: trusted_client_state_chain_id, - data: TrustedClientState { - fetched_at: _, - client_id: _, - trusted_client_state - } - }, - Identified { - chain_id: packet_acknowledgement_chain_id, - data: PacketAcknowledgement { fetched_by: _, ack } - }, - Identified { - chain_id: commitment_proof_chain_id, - data: AcknowledgementProof { - proof: acknowledgement_proof, - height: acknowledgement_proof_height - }, - }, - ]: Self::AggregatedData, - ) -> RelayerMsg { - assert_eq!(this_chain_id, trusted_client_state_chain_id); - assert_eq!(this_chain_id, packet_acknowledgement_chain_id); - assert_eq!(commitment_proof_chain_id, this_chain_id); - - let counterparty_chain_id: ChainIdOf = trusted_client_state.chain_id(); - - msg::( - counterparty_chain_id, - Msg::AckPacket(MsgAckPacketData { - msg: MsgAcknowledgement { - proof_height: acknowledgement_proof_height.into(), - packet: Packet { - sequence: event.packet_sequence, - source_port: event.packet_src_port, - source_channel: event.packet_src_channel, - destination_port: event.packet_dst_port, - destination_channel: event.packet_dst_channel, - data: event.packet_data_hex, - timeout_height: event.packet_timeout_height, - timeout_timestamp: event.packet_timeout_timestamp, - }, - acknowledgement: ack, - proof_acked: acknowledgement_proof, - }, - __marker: PhantomData, - }), - ) - } -} - -impl UseAggregate for identified!(AggregateFetchCounterpartyStateProof) -where - identified!(TrustedClientState): IsAggregateData, - AnyLightClientIdentified: From)>, -{ - type AggregatedData = HList![identified!(TrustedClientState),]; - - fn aggregate( - Identified { - chain_id: this_chain_id, - data: - AggregateFetchCounterpartyStateProof { - counterparty_client_id: _, - fetch: fetch_, - }, - }: Self, - hlist_pat![Identified { - chain_id: trusted_client_state_chain_id, - data: TrustedClientState { - fetched_at: _, - client_id: _, - trusted_client_state - } - }]: Self::AggregatedData, - ) -> RelayerMsg { - assert_eq!(this_chain_id, trusted_client_state_chain_id); - - let counterparty_chain_id: ChainIdOf = trusted_client_state.chain_id(); - - fetch::(counterparty_chain_id, fetch_) - } -} - -impl UseAggregate for identified!(AggregateCreateClient) -where - identified!(SelfClientState): IsAggregateData, - identified!(SelfConsensusState): IsAggregateData, - AnyLightClientIdentified: From)>, -{ - type AggregatedData = HList![ - identified!(SelfClientState), - identified!(SelfConsensusState), - ]; - - fn aggregate( - Identified { - chain_id: this_chain_id, - data: this, - }: Self, - hlist_pat![ - Identified { - chain_id: self_client_state_chain_id, - data: SelfClientState(self_client_state) - }, - Identified { - chain_id: self_consensus_state_chain_id, - data: SelfConsensusState(self_consensus_state) - }, - ]: Self::AggregatedData, - ) -> RelayerMsg { - assert_eq!(self_client_state_chain_id, self_consensus_state_chain_id); - - // let counterparty_chain_id = self_client_state_chain_id; - - msg::( - this_chain_id, - Msg::CreateClient(MsgCreateClientData { - config: this.config, - msg: MsgCreateClient { - client_state: self_client_state, - consensus_state: self_consensus_state, - }, - }), - ) - } -} - -fn flatten_seq(msg: RelayerMsg) -> RelayerMsg { - fn flatten(msg: RelayerMsg) -> VecDeque { - if let RelayerMsg::Sequence(new_seq) = msg { - new_seq.into_iter().flat_map(flatten).collect() - } else { - [msg].into() - } - } - - let mut msgs = flatten(msg); - - if msgs.len() == 1 { - msgs.pop_front().unwrap() - } else { - seq(msgs) - } -} - -#[test] -fn flatten() { - use crate::msg::{defer, seq}; - - let msg = seq([ - defer(1), - seq([defer(2), defer(3)]), - seq([defer(4)]), - defer(5), - ]); - - let msg = flatten_seq(msg); - - dbg!(msg); -} - fn chain_event_to_lc_event( event: IbcEvent<::ClientId, ::ClientType, String>, ) -> IbcEvent::ClientId> diff --git a/voyager/src/queue/aggregate_data.rs b/voyager/src/queue/aggregate_data.rs index 860f030e61..8b13789179 100644 --- a/voyager/src/queue/aggregate_data.rs +++ b/voyager/src/queue/aggregate_data.rs @@ -1,160 +1 @@ -use std::{collections::VecDeque, fmt::Display, ops::ControlFlow}; -use frunk::{HCons, HNil}; - -use crate::{ - chain::LightClient, - msg::{data::AnyData, AnyLightClientIdentified, RelayerMsg}, -}; - -pub trait IsAggregateData = TryFrom, Error = AnyLightClientIdentified> - + Into>; - -pub fn do_aggregate>( - event: T, - data: VecDeque>, -) -> RelayerMsg { - // let data_json = serde_json::to_string(&data).expect("serialization should not fail"); - - // tracing::info!(%data_json, "aggregating data"); - - let data = match HListTryFromIterator::try_from_iter(data) { - Ok(ok) => ok, - Err(_) => { - panic!( - "could not aggregate data into {}", - std::any::type_name::() - ) - } - }; - T::aggregate(event, data) -} - -pub(crate) fn pluck, U>( - mut vec: VecDeque, -) -> ControlFlow<(VecDeque, T), VecDeque> { - let mut new_vec = VecDeque::new(); - - while let Some(data) = vec.pop_front() { - match T::try_from(data) { - Ok(t) => { - new_vec.extend(vec); - return ControlFlow::Break((new_vec, t)); - } - Err(value) => new_vec.push_back(value), - } - } - - ControlFlow::Continue(new_vec) -} - -// TODO: Figure out how to return the entire source list on error -pub trait HListTryFromIterator: Sized { - fn try_from_iter(vec: VecDeque) -> Result>; -} - -impl HListTryFromIterator for HCons -where - T: TryFrom + Into, - Tail: HListTryFromIterator, - // REVIEW: Should debug be used instead? - U: Display, -{ - fn try_from_iter(vec: VecDeque) -> Result> { - match pluck::(vec) { - ControlFlow::Continue(invalid) => { - let invalid_str = invalid - .iter() - .map(|x| x.to_string()) - .collect::>() - .join(", "); - - tracing::error!( - %invalid_str, - "type didn't match" - ); - - Err(invalid) - } - ControlFlow::Break((vec, u)) => Ok(HCons { - head: u, - tail: Tail::try_from_iter(vec)?, - }), - } - } -} - -impl HListTryFromIterator for HNil { - fn try_from_iter(vec: VecDeque) -> Result> { - if vec.is_empty() { - Ok(Self) - } else { - Err(vec) - } - } -} - -pub trait UseAggregate { - type AggregatedData: HListTryFromIterator>; - - fn aggregate(this: Self, data: Self::AggregatedData) -> RelayerMsg; -} - -#[cfg(test)] -pub(crate) mod tests { - use frunk::HList; - - use super::*; - use crate::msg::enum_variants_conversions; - - #[test] - fn hlist_try_from_iter() { - enum_variants_conversions! { - #[derive(Debug, PartialEq, derive_more::Display)] - pub enum A { - B(B), - C(C), - D(D), - } - } - - #[derive(Debug, PartialEq, derive_more::Display)] - pub struct B; - - #[derive(Debug, PartialEq, derive_more::Display)] - pub struct C; - - #[derive(Debug, PartialEq, derive_more::Display)] - pub struct D; - - // correct items, correct order - assert_eq!( - ::try_from_iter(VecDeque::from_iter([A::B(B), A::C(C)])), - Ok(frunk::hlist![B, C]) - ); - - // correct items, wrong order - assert_eq!( - ::try_from_iter(VecDeque::from_iter([A::C(C), A::B(B)])), - Ok(frunk::hlist![B, C]) - ); - - // extra items - assert_eq!( - ::try_from_iter(VecDeque::from_iter([A::C(C), A::B(B), A::D(D)])), - Err(VecDeque::from_iter([A::D(D)])) - ); - - // missing items - assert_eq!( - ::try_from_iter(VecDeque::from_iter([A::C(C)])), - Err(VecDeque::from_iter([A::C(C)])) - ); - - // missing items, extra items present - assert_eq!( - ::try_from_iter(VecDeque::from_iter([A::C(C), A::D(D)])), - Err(VecDeque::from_iter([A::C(C), A::D(D)])) - ); - } -} diff --git a/voyager/src/queue/msg_server.rs b/voyager/src/queue/msg_server.rs index 067e2dfe98..14156d4140 100644 --- a/voyager/src/queue/msg_server.rs +++ b/voyager/src/queue/msg_server.rs @@ -9,8 +9,7 @@ use futures::StreamExt; use reqwest::StatusCode; use tokio::sync::mpsc::{unbounded_channel, UnboundedSender}; use tokio_stream::{wrappers::UnboundedReceiverStream, Stream}; - -use crate::msg::RelayerMsg; +use voyager_message::RelayerMsg; #[derive(Debug, Clone)] pub struct MsgServer; diff --git a/voyager/voyager-message/Cargo.toml b/voyager/voyager-message/Cargo.toml new file mode 100644 index 0000000000..899df9377d --- /dev/null +++ b/voyager/voyager-message/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "voyager-message" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +serde = { version = "1.0.173", features = ["derive"] } +frame-support-procedural = "18.0.0" +unionlabs = { workspace = true, features = ["ethabi"] } +serde-utils.workspace = true +lightclient.workspace = true +derive_more = "0.99.17" +chain-utils.workspace = true +frunk = "0.4.2" +futures = "0.3.28" +num-bigint = "0.4" +prost = "0.11.0" +protos = { workspace = true, features = ["proto_full", "client"] } +tendermint = { workspace = true } +tendermint-proto = { workspace = true } +tendermint-rpc = { workspace = true, features = ["http-client", "websocket-client"] } +tonic = { version = "0.9", features = ["transport","tls","tls-roots","tls-webpki-roots"] } +tracing = "0.1.40" +beacon-api = { workspace = true, default-features = false } +contracts = { workspace = true } +typenum = "1.16.0" +ethers = { workspace = true, features = ["rustls", "ws"] } +thiserror = "1.0.50" +serde_json = "1.0.108" +tokio = { version = "1.33.0", default-features = false, features = ["time"] } + +[dev-dependencies] +hex-literal = "0.4.1" +serde_json = "1.0.108" diff --git a/voyager/voyager-message/src/aggregate.rs b/voyager/voyager-message/src/aggregate.rs new file mode 100644 index 0000000000..af3ca35d93 --- /dev/null +++ b/voyager/voyager-message/src/aggregate.rs @@ -0,0 +1,2208 @@ +use std::{collections::VecDeque, fmt::Display, marker::PhantomData}; + +use frame_support_procedural::{CloneNoBound, DebugNoBound, PartialEqNoBound}; +use frunk::{hlist_pat, HList}; +use serde::{Deserialize, Serialize}; +use unionlabs::{ + ethereum::H256, + events::{ + ChannelOpenAck, ChannelOpenInit, ChannelOpenTry, ConnectionOpenAck, ConnectionOpenInit, + ConnectionOpenTry, RecvPacket, SendPacket, + }, + ibc::core::{ + channel::{ + self, channel::Channel, msg_acknowledgement::MsgAcknowledgement, + msg_channel_open_ack::MsgChannelOpenAck, + msg_channel_open_confirm::MsgChannelOpenConfirm, + msg_channel_open_try::MsgChannelOpenTry, msg_recv_packet::MsgRecvPacket, + packet::Packet, + }, + client::{height::IsHeight, msg_create_client::MsgCreateClient}, + commitment::merkle_prefix::MerklePrefix, + connection::{ + self, msg_connection_open_ack::MsgConnectionOpenAck, + msg_connection_open_confirm::MsgConnectionOpenConfirm, + msg_connection_open_try::MsgConnectionOpenTry, + }, + }, + proof::{ + self, AcknowledgementPath, ChannelEndPath, ClientConsensusStatePath, ClientStatePath, + CommitmentPath, ConnectionPath, + }, + traits::{ChainIdOf, ChainOf, ClientState, HeightOf, LightClientBase}, + QueryHeight, DELAY_PERIOD, +}; + +use crate::{ + any_enum, + data::{ + AcknowledgementProof, ChannelEnd, ChannelEndProof, ClientConsensusStateProof, + ClientStateProof, CommitmentProof, ConnectionEnd, ConnectionProof, Data, + PacketAcknowledgement, SelfClientState, SelfConsensusState, TrustedClientState, + }, + fetch, + fetch::{ + FetchChannelEnd, FetchConnectionEnd, FetchPacketAcknowledgement, FetchStateProof, + FetchTrustedClientState, FetchUpdateHeaders, + }, + identified, msg, + msg::{ + MsgAckPacketData, MsgChannelOpenAckData, MsgChannelOpenConfirmData, MsgChannelOpenTryData, + MsgConnectionOpenAckData, MsgConnectionOpenConfirmData, MsgConnectionOpenTryData, + MsgCreateClientData, MsgRecvPacketData, + }, + use_aggregate::{do_aggregate, IsAggregateData, UseAggregate}, + wait, + wait::WaitForTrustedHeight, + AggregateData, AggregateReceiver, AnyLcMsg, AnyLightClientIdentified, DoAggregate, Identified, + LcMsg, LightClient, RelayerMsg, +}; + +any_enum! { + /// Aggregate data, using data from [`AggregateData`] + #[any = AnyAggregate] + pub enum Aggregate { + ConnectionOpenTry(AggregateConnectionOpenTry), + ConnectionOpenAck(AggregateConnectionOpenAck), + ConnectionOpenConfirm(AggregateConnectionOpenConfirm), + + ChannelOpenTry(AggregateChannelOpenTry), + ChannelOpenAck(AggregateChannelOpenAck), + ChannelOpenConfirm(AggregateChannelOpenConfirm), + + RecvPacket(AggregateRecvPacket), + AckPacket(AggregateAckPacket), + + ConnectionFetchFromChannelEnd(AggregateConnectionFetchFromChannelEnd), + + // Aggregate that fetches the connection info from the channel + ChannelHandshakeUpdateClient(AggregateChannelHandshakeUpdateClient), + + PacketUpdateClient(AggregatePacketUpdateClient), + + WaitForTrustedHeight(AggregateWaitForTrustedHeight), + + FetchCounterpartyStateproof(AggregateFetchCounterpartyStateProof), + + UpdateClientFromClientId(AggregateUpdateClientFromClientId), + + UpdateClient(AggregateUpdateClient), + UpdateClientWithCounterpartyChainIdData(AggregateUpdateClientWithCounterpartyChainId), + + CreateClient(AggregateCreateClient), + + AggregateMsgAfterUpdate(AggregateMsgAfterUpdate), + + LightClientSpecific(LightClientSpecificAggregate), + } +} + +impl identified!(Aggregate) { + pub fn handle(self, data: VecDeque) -> Vec + where + identified!(TrustedClientState): IsAggregateData, + identified!(TrustedClientState): IsAggregateData, + + identified!(ClientStateProof): IsAggregateData, + identified!(ClientConsensusStateProof): IsAggregateData, + identified!(ConnectionProof): IsAggregateData, + identified!(ChannelEndProof): IsAggregateData, + identified!(CommitmentProof): IsAggregateData, + identified!(AcknowledgementProof): IsAggregateData, + + identified!(SelfClientState): IsAggregateData, + identified!(SelfConsensusState): IsAggregateData, + + identified!(ChannelEnd): IsAggregateData, + identified!(ConnectionEnd): IsAggregateData, + identified!(PacketAcknowledgement): IsAggregateData, + + AnyLightClientIdentified: From)>, + AnyLightClientIdentified: From)>, + AggregateData: From)>, + AggregateReceiver: From)>, + { + let chain_id = self.chain_id; + + match self.data { + Aggregate::ConnectionOpenTry(init) => [do_aggregate::( + Identified { + chain_id, + data: init, + }, + data, + )] + .into(), + Aggregate::ConnectionOpenAck(ack) => [do_aggregate::( + Identified { + chain_id, + data: ack, + }, + data, + )] + .into(), + Aggregate::ConnectionOpenConfirm(confirm) => [do_aggregate::( + Identified { + chain_id, + data: confirm, + }, + data, + )] + .into(), + Aggregate::ChannelOpenTry(try_) => [do_aggregate::( + Identified { + chain_id, + data: try_, + }, + data, + )] + .into(), + Aggregate::ChannelOpenAck(ack) => [do_aggregate::( + Identified { + chain_id, + data: ack, + }, + data, + )] + .into(), + Aggregate::ChannelOpenConfirm(confirm) => [do_aggregate::( + Identified { + chain_id, + data: confirm, + }, + data, + )] + .into(), + Aggregate::UpdateClientFromClientId(update_client) => [do_aggregate::( + Identified { + chain_id, + data: update_client, + }, + data, + )] + .into(), + Aggregate::UpdateClient(update_client) => [do_aggregate::( + Identified { + chain_id, + data: update_client, + }, + data, + )] + .into(), + Aggregate::UpdateClientWithCounterpartyChainIdData(aggregate) => { + [do_aggregate::( + Identified { + chain_id, + data: aggregate, + }, + data, + )] + .into() + } + Aggregate::CreateClient(create_client) => [do_aggregate::( + Identified { + chain_id, + data: create_client, + }, + data, + )] + .into(), + Aggregate::AggregateMsgAfterUpdate(aggregate) => [do_aggregate::( + Identified { + chain_id, + data: aggregate, + }, + data, + )] + .into(), + Aggregate::LightClientSpecific(LightClientSpecificAggregate(aggregate)) => { + L::Aggregate::do_aggregate( + Identified { + chain_id, + data: aggregate, + }, + data, + ) + } + Aggregate::ConnectionFetchFromChannelEnd(aggregate) => [do_aggregate::( + Identified { + chain_id, + data: aggregate, + }, + data, + )] + .into(), + Aggregate::ChannelHandshakeUpdateClient(channel_handshake_update_client) => { + [do_aggregate::( + Identified { + chain_id, + data: channel_handshake_update_client, + }, + data, + )] + .into() + } + Aggregate::PacketUpdateClient(packet_update_client) => [do_aggregate::( + Identified { + chain_id, + data: packet_update_client, + }, + data, + )] + .into(), + Aggregate::RecvPacket(recv_packet) => [do_aggregate::( + Identified { + chain_id, + data: recv_packet, + }, + data, + )] + .into(), + Aggregate::AckPacket(ack_packet) => [do_aggregate::( + Identified { + chain_id, + data: ack_packet, + }, + data, + )] + .into(), + Aggregate::WaitForTrustedHeight(agg) => [do_aggregate::( + Identified { + chain_id, + data: agg, + }, + data, + )] + .into(), + Aggregate::FetchCounterpartyStateproof(agg) => [do_aggregate::( + Identified { + chain_id, + data: agg, + }, + data, + )] + .into(), + } + } +} + +impl Display for Aggregate { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Aggregate::ConnectionOpenTry(_) => write!(f, "ConnectionOpenTry"), + Aggregate::ConnectionOpenAck(_) => write!(f, "ConnectionOpenAck"), + Aggregate::ConnectionOpenConfirm(_) => write!(f, "ConnectionOpenConfirm"), + Aggregate::ChannelOpenTry(_) => write!(f, "ChannelOpenTry"), + Aggregate::ChannelOpenAck(_) => write!(f, "ChannelOpenAck"), + Aggregate::ChannelOpenConfirm(_) => write!(f, "ChannelOpenConfirm"), + Aggregate::RecvPacket(_) => write!(f, "RecvPacket"), + Aggregate::AckPacket(_) => write!(f, "AckPacket"), + Aggregate::ConnectionFetchFromChannelEnd(_) => { + write!(f, "ConnectionFetchFromChannelEnd") + } + Aggregate::ChannelHandshakeUpdateClient(_) => { + write!(f, "ChannelHandshakeUpdateClient") + } + Aggregate::PacketUpdateClient(msg) => { + write!( + f, + "PacketUpdateClient::{}", + match msg.packet_event { + PacketEvent::Send(_) => "Send", + PacketEvent::Recv(_) => "Recv", + } + ) + } + Aggregate::WaitForTrustedHeight(_) => write!(f, "WaitForTrustedHeight"), + Aggregate::FetchCounterpartyStateproof(_) => { + write!(f, "FetchCounterpartyStateproof") + } + Aggregate::UpdateClientFromClientId(_) => write!(f, "UpdateClientFromClientId"), + Aggregate::UpdateClient(_) => write!(f, "UpdateClient"), + Aggregate::UpdateClientWithCounterpartyChainIdData(_) => { + write!(f, "UpdateClientWithCounterpartyChainIdData") + } + Aggregate::CreateClient(_) => write!(f, "CreateClient"), + Aggregate::AggregateMsgAfterUpdate(msg) => { + write!(f, "AggregateMsgAfterUpdate::")?; + match msg { + AggregateMsgAfterUpdate::ConnectionOpenTry(_) => { + write!(f, "ConnectionOpenTry") + } + AggregateMsgAfterUpdate::ConnectionOpenAck(_) => { + write!(f, "ConnectionOpenAck") + } + AggregateMsgAfterUpdate::ConnectionOpenConfirm(_) => { + write!(f, "ConnectionOpenConfirm") + } + AggregateMsgAfterUpdate::ChannelOpenTry(_) => write!(f, "ChannelOpenTry"), + AggregateMsgAfterUpdate::ChannelOpenAck(_) => write!(f, "ChannelOpenAck"), + AggregateMsgAfterUpdate::ChannelOpenConfirm(_) => { + write!(f, "ChannelOpenConfirm") + } + AggregateMsgAfterUpdate::RecvPacket(_) => write!(f, "RecvPacket"), + AggregateMsgAfterUpdate::AckPacket(_) => write!(f, "AckPacket"), + } + } + Aggregate::LightClientSpecific(agg) => write!(f, "LightClientSpecific({})", agg.0), + } + } +} + +#[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] +#[serde(bound(serialize = "", deserialize = ""))] +pub struct AggregateConnectionOpenTry { + pub event_height: HeightOf, + pub event: ConnectionOpenInit::ClientId>, +} + +#[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] +#[serde(bound(serialize = "", deserialize = ""))] +pub struct AggregateConnectionOpenAck { + pub event_height: HeightOf, + pub event: ConnectionOpenTry::ClientId>, +} + +#[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] +#[serde(bound(serialize = "", deserialize = ""))] +pub struct AggregateConnectionOpenConfirm { + pub event_height: HeightOf, + pub event: ConnectionOpenAck::ClientId>, +} + +#[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] +#[serde(bound(serialize = "", deserialize = ""))] +pub struct AggregateChannelOpenTry { + pub event_height: HeightOf, + pub event: ChannelOpenInit, +} + +#[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] +#[serde(bound(serialize = "", deserialize = ""))] +pub struct AggregateChannelOpenAck { + pub event_height: HeightOf, + pub event: ChannelOpenTry, +} + +#[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] +#[serde(bound(serialize = "", deserialize = ""))] +pub struct AggregateChannelOpenConfirm { + pub event_height: HeightOf, + pub event: ChannelOpenAck, +} + +#[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] +#[serde(bound(serialize = "", deserialize = ""))] +pub struct AggregateRecvPacket { + pub event_height: HeightOf, + pub event: SendPacket, +} + +#[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] +#[serde(bound(serialize = "", deserialize = ""))] +pub struct AggregateAckPacket { + pub event_height: HeightOf, + pub event: RecvPacket, + // HACK: Need to pass the block hash through, figure out a better/cleaner way to do this + pub block_hash: H256, + pub counterparty_client_id: ::ClientId, +} + +#[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] +#[serde(bound(serialize = "", deserialize = ""))] +pub struct AggregateConnectionFetchFromChannelEnd { + pub at: HeightOf>, +} + +#[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] +#[serde(bound(serialize = "", deserialize = ""))] +pub struct AggregateChannelHandshakeUpdateClient { + // Will be threaded through to the update msg + pub update_to: HeightOf, + pub event_height: HeightOf, + pub channel_handshake_event: ChannelHandshakeEvent, +} + +#[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] +#[serde(bound(serialize = "", deserialize = ""))] +pub enum ChannelHandshakeEvent { + Init(ChannelOpenInit), + Try(ChannelOpenTry), + Ack(ChannelOpenAck), +} + +#[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] +#[serde(bound(serialize = "", deserialize = ""))] +pub struct AggregatePacketUpdateClient { + // Will be threaded through to the update msg + pub update_to: HeightOf, + pub event_height: HeightOf, + pub block_hash: H256, + pub packet_event: PacketEvent, +} + +#[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] +#[serde(bound(serialize = "", deserialize = ""))] +pub enum PacketEvent { + Send(SendPacket), + Recv(RecvPacket), +} + +#[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] +#[serde(bound(serialize = "", deserialize = ""))] +pub struct AggregateFetchCounterpartyStateProof { + pub counterparty_client_id: ::ClientId, + pub fetch: FetchStateProof, +} + +#[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] +#[serde(bound(serialize = "", deserialize = ""))] +pub struct AggregateUpdateClientFromClientId { + pub client_id: L::ClientId, + pub counterparty_client_id: ::ClientId, +} + +#[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] +#[serde(bound(serialize = "", deserialize = ""))] +pub struct AggregateUpdateClient { + pub update_to: HeightOf, + pub client_id: L::ClientId, + pub counterparty_client_id: ::ClientId, +} + +#[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] +#[serde(bound(serialize = "", deserialize = ""))] +pub struct AggregateWaitForTrustedHeight { + pub wait_for: HeightOf, + pub client_id: L::ClientId, + pub counterparty_client_id: ::ClientId, +} + +#[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] +#[serde(bound(serialize = "", deserialize = ""))] +pub struct AggregateUpdateClientWithCounterpartyChainId { + pub update_to: HeightOf, + pub client_id: L::ClientId, + pub counterparty_client_id: ::ClientId, + pub counterparty_chain_id: ChainIdOf, +} + +#[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] +#[serde(bound(serialize = "", deserialize = ""))] +pub struct AggregateMsgUpdateClient { + pub update_to: HeightOf, + pub client_id: L::ClientId, + pub counterparty_client_id: ::ClientId, + pub counterparty_chain_id: ChainIdOf, +} + +#[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] +#[serde(bound(serialize = "", deserialize = ""))] +pub struct AggregateCreateClient { + pub config: L::Config, +} + +#[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] +#[serde(bound(serialize = "", deserialize = ""))] +pub struct LightClientSpecificAggregate(pub L::Aggregate); + +/// Messages that will be re-queued after an update. +#[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] +#[serde(bound(serialize = "", deserialize = ""))] +pub enum AggregateMsgAfterUpdate { + ConnectionOpenTry(AggregateConnectionOpenTry), + ConnectionOpenAck(AggregateConnectionOpenAck), + ConnectionOpenConfirm(AggregateConnectionOpenConfirm), + + ChannelOpenTry(AggregateChannelOpenTry), + ChannelOpenAck(AggregateChannelOpenAck), + ChannelOpenConfirm(AggregateChannelOpenConfirm), + + RecvPacket(AggregateRecvPacket), + AckPacket(AggregateAckPacket), +} + +// #[derive( +// DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize, derive_more::Display, +// )] +// #[serde(bound(serialize = "", deserialize = ""))] +// pub enum AggregateAnyStateProof { +// #[display(fmt = "{_0}")] +// ClientState( +// AggregateStateProof< +// L, +// ClientStatePath<::ClientId>, +// >, +// ), +// #[display(fmt = "{_0}")] +// ClientConsensusState( +// AggregateStateProof< +// L, +// ClientConsensusStatePath< +// as unionlabs::traits::Chain>::ClientId, +// HeightOf>, +// >, +// >, +// ), +// #[display(fmt = "{_0}")] +// Connection(AggregateStateProof), +// #[display(fmt = "{_0}")] +// ChannelEnd(AggregateStateProof), +// #[display(fmt = "{_0}")] +// Commitment(AggregateStateProof), +// #[display(fmt = "{_0}")] +// Acknowledgement(AggregateStateProof), +// } + +// #[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] +// #[serde(bound(serialize = "", deserialize = ""))] +// pub struct AggregateStateProof> { +// height: HeightOf>, +// #[serde(skip)] +// pub __marker: PhantomData

, +// } + +impl UseAggregate for identified!(AggregateChannelHandshakeUpdateClient) +where + identified!(ConnectionEnd): IsAggregateData, + AnyLightClientIdentified: From)>, + AggregateReceiver: From)>, +{ + type AggregatedData = HList![identified!(ConnectionEnd)]; + + fn aggregate( + Identified { + chain_id: this_chain_id, + data: + AggregateChannelHandshakeUpdateClient { + update_to, + channel_handshake_event, + event_height, + }, + }: Self, + hlist_pat![Identified { + chain_id: self_chain_id, + data: ConnectionEnd(connection), + }]: Self::AggregatedData, + ) -> RelayerMsg { + assert_eq!(this_chain_id, self_chain_id); + + let event_msg = match channel_handshake_event { + ChannelHandshakeEvent::Init(init) => { + AggregateMsgAfterUpdate::ChannelOpenTry(AggregateChannelOpenTry { + event_height, + event: init, + }) + } + ChannelHandshakeEvent::Try(try_) => { + AggregateMsgAfterUpdate::ChannelOpenAck(AggregateChannelOpenAck { + event_height, + event: try_, + }) + } + ChannelHandshakeEvent::Ack(ack) => { + AggregateMsgAfterUpdate::ChannelOpenConfirm(AggregateChannelOpenConfirm { + event_height, + event: ack, + }) + } + }; + + RelayerMsg::Aggregate { + data: [].into(), + queue: [mk_aggregate_wait_for_update( + this_chain_id.clone(), + connection.client_id, + connection.counterparty.client_id, + update_to, + )] + .into(), + receiver: AggregateReceiver::from(Identified::new( + this_chain_id, + Aggregate::AggregateMsgAfterUpdate(event_msg), + )), + } + } +} + +pub fn mk_aggregate_wait_for_update( + chain_id: ChainIdOf, + client_id: L::ClientId, + counterparty_client_id: ::ClientId, + wait_for: HeightOf, +) -> RelayerMsg +where + AnyLightClientIdentified: From)>, + AggregateReceiver: From)>, +{ + RelayerMsg::Aggregate { + queue: [fetch::( + chain_id.clone(), + FetchTrustedClientState { + at: QueryHeight::Latest, + client_id: client_id.clone().clone(), + }, + )] + .into(), + data: [].into(), + receiver: AggregateReceiver::from(Identified::new( + chain_id, + Aggregate::::WaitForTrustedHeight(AggregateWaitForTrustedHeight { + wait_for, + client_id, + counterparty_client_id, + }), + )), + } +} + +impl UseAggregate for identified!(AggregatePacketUpdateClient) +where + identified!(ConnectionEnd): IsAggregateData, + AnyLightClientIdentified: From)>, + AggregateReceiver: From)>, +{ + type AggregatedData = HList![identified!(ConnectionEnd)]; + + fn aggregate( + Identified { + chain_id: this_chain_id, + data: + AggregatePacketUpdateClient { + update_to, + event_height, + block_hash, + packet_event, + }, + }: Self, + hlist_pat![Identified { + chain_id: self_chain_id, + data: ConnectionEnd(connection), + }]: Self::AggregatedData, + ) -> RelayerMsg { + assert_eq!(this_chain_id, self_chain_id); + + let event = match packet_event { + PacketEvent::Send(send) => Aggregate::AggregateMsgAfterUpdate( + AggregateMsgAfterUpdate::RecvPacket(AggregateRecvPacket { + event_height, + event: send, + }), + ), + PacketEvent::Recv(recv) => Aggregate::AggregateMsgAfterUpdate( + AggregateMsgAfterUpdate::AckPacket(AggregateAckPacket { + event_height, + event: recv, + block_hash, + counterparty_client_id: connection.counterparty.client_id.clone(), + }), + ), + }; + + let agg = RelayerMsg::Aggregate { + queue: [fetch::( + this_chain_id.clone().clone(), + FetchTrustedClientState { + at: QueryHeight::Latest, + client_id: connection.client_id.clone().clone(), + }, + )] + .into(), + data: [].into(), + receiver: AggregateReceiver::from(Identified::new( + this_chain_id.clone(), + Aggregate::::WaitForTrustedHeight(AggregateWaitForTrustedHeight { + wait_for: update_to, + client_id: connection.client_id.clone().clone(), + counterparty_client_id: connection.counterparty.client_id.clone(), + }), + )), + }; + + RelayerMsg::Aggregate { + data: [].into(), + queue: [agg].into(), + receiver: AggregateReceiver::from(Identified::new(this_chain_id, event)), + } + } +} + +impl UseAggregate for identified!(AggregateConnectionFetchFromChannelEnd) +where + identified!(ChannelEnd): IsAggregateData, + AnyLightClientIdentified: From)>, +{ + type AggregatedData = HList![identified!(ChannelEnd)]; + + fn aggregate( + Identified { + chain_id: this_chain_id, + data: AggregateConnectionFetchFromChannelEnd { at }, + }: Self, + hlist_pat![Identified { + chain_id: self_chain_id, + data: ChannelEnd { + channel, + __marker: _ + }, + }]: Self::AggregatedData, + ) -> RelayerMsg { + assert_eq!(this_chain_id, self_chain_id); + + fetch( + this_chain_id, + FetchConnectionEnd { + at, + connection_id: channel.connection_hops[0].clone(), + }, + ) + } +} + +impl UseAggregate for identified!(AggregateUpdateClientFromClientId) +where + identified!(TrustedClientState): IsAggregateData, + // AnyLightClientIdentified: From)>, + AnyLightClientIdentified: From)>, + AggregateReceiver: From)>, +{ + type AggregatedData = HList![identified!(TrustedClientState)]; + + fn aggregate( + Identified { + chain_id: this_chain_id, + data: + AggregateUpdateClientFromClientId { + client_id, + counterparty_client_id, + }, + }: Self, + hlist_pat![Identified { + chain_id: self_chain_id, + data: TrustedClientState { + fetched_at, + client_id: trusted_client_state_client_id, + trusted_client_state, + }, + }]: Self::AggregatedData, + ) -> RelayerMsg { + assert_eq!(this_chain_id, self_chain_id); + assert_eq!(trusted_client_state_client_id, client_id); + + let counterparty_chain_id = trusted_client_state.chain_id(); + + RelayerMsg::Aggregate { + queue: [fetch::( + counterparty_chain_id.clone(), + FetchTrustedClientState { + // NOTE: Use latest here since on client creation, the counterparty may not yet exist at the latest height. + // + // - client created on chain A of chain B at height 1-10, trusting height 2-10 of B + // - client created on chain B of chain A at height 2-15, trusting height 1-15 of A + // - attempt to update chain A by updating from it's latest trusted height, which is 2-10 - but the client state of B on doesn't exist at height 2-10 + // + // Note that updating chain B would work in this situation, since it was created after it's counterparty. + // + // Since this query is only to fetch the chain id of the counterparty, all that matters is that we get *a* client state, since it's expected that the chain id in a client state will never change. + // + // REVIEW: How will this work with chain upgrades? Since the revision number will change (on cosmos chains), so will the chain id - this will need to be handled + // at: QueryHeight::Specific(trusted_client_state.height()), + at: QueryHeight::Latest, + client_id: counterparty_client_id.clone(), + }, + )] + .into(), + data: [].into(), + receiver: AggregateReceiver::from(Identified::new( + this_chain_id, + Aggregate::UpdateClientWithCounterpartyChainIdData( + AggregateUpdateClientWithCounterpartyChainId { + update_to: fetched_at, + client_id, + counterparty_client_id, + counterparty_chain_id, + }, + ), + )), + } + } +} + +impl UseAggregate for identified!(AggregateUpdateClient) +where + identified!(TrustedClientState): IsAggregateData, + // AnyLightClientIdentified: From)>, + AnyLightClientIdentified: From)>, + AggregateReceiver: From)>, +{ + type AggregatedData = HList![identified!(TrustedClientState)]; + + fn aggregate( + Identified { + chain_id: this_chain_id, + data: + AggregateUpdateClient { + update_to, + client_id: update_client_id, + counterparty_client_id: update_counterparty_client_id, + }, + }: Self, + hlist_pat![Identified { + chain_id: self_chain_id, + data: TrustedClientState { + fetched_at: _, + client_id: trusted_client_state_client_id, + trusted_client_state, + }, + }]: Self::AggregatedData, + ) -> RelayerMsg { + assert_eq!(this_chain_id, self_chain_id); + assert_eq!(update_client_id, trusted_client_state_client_id); + + let counterparty_chain_id: ChainIdOf = trusted_client_state.chain_id(); + + RelayerMsg::Aggregate { + queue: [fetch::( + counterparty_chain_id.clone(), + FetchTrustedClientState { + at: QueryHeight::Latest, + client_id: update_counterparty_client_id.clone(), + }, + )] + .into(), + data: [].into(), + receiver: AggregateReceiver::from(Identified::new( + this_chain_id, + Aggregate::UpdateClientWithCounterpartyChainIdData( + AggregateUpdateClientWithCounterpartyChainId { + update_to, + client_id: update_client_id, + counterparty_client_id: update_counterparty_client_id, + counterparty_chain_id, + }, + ), + )), + } + } +} + +impl UseAggregate + for identified!(AggregateUpdateClientWithCounterpartyChainId) +where + identified!(TrustedClientState): IsAggregateData, + AnyLightClientIdentified: From)>, + AggregateReceiver: From)>, +{ + type AggregatedData = HList![identified!(TrustedClientState)]; + + fn aggregate( + Identified { + chain_id: this_chain_id, + data: + AggregateUpdateClientWithCounterpartyChainId { + update_to, + client_id: update_client_id, + counterparty_client_id: update_counterparty_client_id, + counterparty_chain_id: update_counterparty_chain_id, + }, + }: Self, + hlist_pat![Identified { + chain_id: counterparty_chain_id, + data: TrustedClientState { + fetched_at: _, + client_id: latest_trusted_client_state_client_id, + trusted_client_state + }, + }]: Self::AggregatedData, + ) -> RelayerMsg { + let self_chain_id: ChainIdOf = trusted_client_state.chain_id(); + + assert_eq!(this_chain_id, self_chain_id); + assert_eq!( + latest_trusted_client_state_client_id, + update_counterparty_client_id + ); + assert_eq!(counterparty_chain_id, update_counterparty_chain_id); + + fetch::( + this_chain_id, + FetchUpdateHeaders { + client_id: update_client_id, + counterparty_client_id: update_counterparty_client_id, + counterparty_chain_id, + update_from: trusted_client_state.height(), + update_to, + }, + ) + } +} + +impl UseAggregate for identified!(AggregateWaitForTrustedHeight) +where + identified!(TrustedClientState): IsAggregateData, + AnyLightClientIdentified: From)>, + AggregateReceiver: From)>, +{ + type AggregatedData = HList![identified!(TrustedClientState)]; + + fn aggregate( + Identified { + chain_id: this_chain_id, + data: + AggregateWaitForTrustedHeight { + wait_for, + client_id, + counterparty_client_id, + }, + }: Self, + hlist_pat![Identified { + chain_id: trusted_client_state_chain_id, + data: TrustedClientState { + fetched_at: _, + client_id: trusted_client_state_client_id, + trusted_client_state + }, + }]: Self::AggregatedData, + ) -> RelayerMsg { + assert_eq!(trusted_client_state_client_id, client_id); + assert_eq!(trusted_client_state_chain_id, this_chain_id); + + let counterparty_chain_id: ChainIdOf = trusted_client_state.chain_id(); + + tracing::debug!("building WaitForTrustedHeight"); + + wait::( + counterparty_chain_id, + WaitForTrustedHeight { + height: wait_for, + client_id: counterparty_client_id, + counterparty_client_id: client_id, + counterparty_chain_id: this_chain_id, + }, + ) + } +} + +impl UseAggregate for identified!(AggregateMsgAfterUpdate) +where + identified!(TrustedClientState): IsAggregateData, + AnyLightClientIdentified: From)>, + AnyLightClientIdentified: From)>, + AggregateData: From)>, + AggregateReceiver: From)>, +{ + type AggregatedData = HList![identified!(TrustedClientState)]; + + fn aggregate( + Identified { + chain_id: this_chain_id, + data: msg_to_aggregate, + }: Self, + hlist_pat![Identified { + chain_id: self_chain_id, + data: TrustedClientState { + fetched_at: trusted_client_state_fetched_at_height, + client_id: trusted_client_state_client_id, + trusted_client_state + }, + }]: Self::AggregatedData, + ) -> RelayerMsg { + assert_eq!(this_chain_id, self_chain_id); + // assert_eq!(client_id, trusted_client_state_client_id); + + match msg_to_aggregate { + AggregateMsgAfterUpdate::ConnectionOpenTry(AggregateConnectionOpenTry { + event_height, + event, + }) => { + let consensus_state_height = trusted_client_state_fetched_at_height; + + assert_eq!( + consensus_state_height.revision_number(), + event_height.revision_number(), + "{consensus_state_height}, {event_height}", + ); + + assert!( + consensus_state_height.revision_height() >= event_height.revision_height(), + "{} < {}", + consensus_state_height.revision_height(), + event_height.revision_height() + ); + + let trusted_client_state_height = trusted_client_state.height(); + + RelayerMsg::Aggregate { + data: [AggregateData::from(Identified::new( + this_chain_id.clone(), + Data::TrustedClientState(TrustedClientState { + fetched_at: trusted_client_state_fetched_at_height, + client_id: trusted_client_state_client_id, + trusted_client_state, + }), + ))] + .into(), + queue: [ + fetch::( + this_chain_id.clone(), + FetchStateProof { + at: trusted_client_state_fetched_at_height, + path: proof::Path::ClientStatePath(ClientStatePath { + client_id: event.client_id.clone().into(), + }), + }, + ), + fetch::( + this_chain_id.clone(), + FetchStateProof { + at: trusted_client_state_fetched_at_height, + path: proof::Path::ClientConsensusStatePath( + ClientConsensusStatePath { + client_id: event.client_id.clone().into(), + height: trusted_client_state_height, + }, + ), + }, + ), + fetch::( + this_chain_id.clone(), + FetchStateProof { + at: trusted_client_state_fetched_at_height, + path: proof::Path::ConnectionPath(ConnectionPath { + connection_id: event.connection_id.clone(), + }), + }, + ), + fetch::( + this_chain_id.clone(), + FetchConnectionEnd { + at: trusted_client_state_fetched_at_height, + connection_id: event.connection_id.clone(), + }, + ), + ] + .into(), + receiver: AggregateReceiver::from(Identified::new( + this_chain_id, + Aggregate::ConnectionOpenTry(AggregateConnectionOpenTry { + event_height, + event, + }), + )), + } + } + AggregateMsgAfterUpdate::ConnectionOpenAck(AggregateConnectionOpenAck { + event_height, + event, + }) => { + let consensus_state_height = trusted_client_state_fetched_at_height; + + assert_eq!( + consensus_state_height.revision_number(), + event_height.revision_number(), + "{consensus_state_height}, {event_height}", + ); + + assert!( + consensus_state_height.revision_height() >= event_height.revision_height(), + "{} < {}", + consensus_state_height.revision_height(), + event_height.revision_height() + ); + + let trusted_client_state_height = trusted_client_state.height(); + + RelayerMsg::Aggregate { + data: [AggregateData::from(Identified::new( + this_chain_id.clone(), + Data::TrustedClientState(TrustedClientState { + fetched_at: trusted_client_state_fetched_at_height, + client_id: trusted_client_state_client_id, + trusted_client_state, + }), + ))] + .into(), + queue: [ + fetch::( + this_chain_id.clone(), + FetchStateProof { + at: trusted_client_state_fetched_at_height, + path: proof::Path::ClientStatePath(ClientStatePath { + client_id: event.client_id.clone().into(), + }), + }, + ), + fetch::( + this_chain_id.clone(), + FetchStateProof { + at: trusted_client_state_fetched_at_height, + path: proof::Path::ClientConsensusStatePath( + ClientConsensusStatePath { + client_id: event.client_id.clone().into(), + height: trusted_client_state_height, + }, + ), + }, + ), + fetch::( + this_chain_id.clone(), + FetchStateProof { + at: trusted_client_state_fetched_at_height, + path: proof::Path::ConnectionPath(ConnectionPath { + connection_id: event.connection_id.clone(), + }), + }, + ), + fetch::( + this_chain_id.clone(), + FetchConnectionEnd { + at: trusted_client_state_fetched_at_height, + connection_id: event.connection_id.clone(), + }, + ), + ] + .into(), + receiver: AggregateReceiver::from(Identified::new( + this_chain_id, + Aggregate::ConnectionOpenAck(AggregateConnectionOpenAck { + event_height, + event, + }), + )), + } + } + AggregateMsgAfterUpdate::ConnectionOpenConfirm(AggregateConnectionOpenConfirm { + event_height, + event, + }) => { + let consensus_state_height = trusted_client_state_fetched_at_height; + + assert_eq!( + consensus_state_height.revision_number(), + event_height.revision_number(), + "{consensus_state_height}, {event_height}", + ); + + assert!( + consensus_state_height.revision_height() >= event_height.revision_height(), + "{} < {}", + consensus_state_height.revision_height(), + event_height.revision_height() + ); + + RelayerMsg::Aggregate { + data: [AggregateData::from(Identified::new( + this_chain_id.clone(), + Data::TrustedClientState(TrustedClientState { + fetched_at: trusted_client_state_fetched_at_height, + client_id: trusted_client_state_client_id, + trusted_client_state, + }), + ))] + .into(), + queue: [fetch::( + this_chain_id.clone(), + FetchStateProof { + at: trusted_client_state_fetched_at_height, + path: proof::Path::ConnectionPath(ConnectionPath { + connection_id: event.connection_id.clone(), + }), + }, + )] + .into(), + receiver: AggregateReceiver::from(Identified::new( + this_chain_id, + Aggregate::ConnectionOpenConfirm(AggregateConnectionOpenConfirm { + event_height, + event, + }), + )), + } + } + AggregateMsgAfterUpdate::ChannelOpenTry(AggregateChannelOpenTry { + event_height, + event, + }) => { + let consensus_state_height = trusted_client_state_fetched_at_height; + + assert_eq!( + consensus_state_height.revision_number(), + event_height.revision_number(), + "{consensus_state_height}, {event_height}", + ); + + assert!( + consensus_state_height.revision_height() >= event_height.revision_height(), + "{} < {}", + consensus_state_height.revision_height(), + event_height.revision_height() + ); + + RelayerMsg::Aggregate { + data: [AggregateData::from(Identified::new( + this_chain_id.clone(), + Data::TrustedClientState(TrustedClientState { + fetched_at: trusted_client_state_fetched_at_height, + client_id: trusted_client_state_client_id, + trusted_client_state, + }), + ))] + .into(), + queue: [ + RelayerMsg::Aggregate { + data: [].into(), + queue: [fetch::( + this_chain_id.clone(), + FetchChannelEnd { + at: trusted_client_state_fetched_at_height, + port_id: event.port_id.clone(), + channel_id: event.channel_id.clone(), + }, + )] + .into(), + receiver: AggregateReceiver::from(Identified::new( + this_chain_id.clone(), + Aggregate::ConnectionFetchFromChannelEnd( + AggregateConnectionFetchFromChannelEnd { + at: trusted_client_state_fetched_at_height, + }, + ), + )), + }, + fetch::( + this_chain_id.clone(), + FetchStateProof { + at: trusted_client_state_fetched_at_height, + path: proof::Path::ChannelEndPath(ChannelEndPath { + port_id: event.port_id.clone(), + channel_id: event.channel_id.clone(), + }), + }, + ), + fetch::( + this_chain_id.clone(), + FetchChannelEnd { + at: trusted_client_state_fetched_at_height, + port_id: event.port_id.clone(), + channel_id: event.channel_id.clone(), + }, + ), + ] + .into(), + receiver: AggregateReceiver::from(Identified::new( + this_chain_id, + Aggregate::ChannelOpenTry(AggregateChannelOpenTry { + event_height, + event, + }), + )), + } + } + AggregateMsgAfterUpdate::ChannelOpenAck(AggregateChannelOpenAck { + event_height, + event, + }) => { + let consensus_state_height = trusted_client_state_fetched_at_height; + + assert_eq!( + consensus_state_height.revision_number(), + event_height.revision_number(), + "{consensus_state_height}, {event_height}", + ); + + assert!( + consensus_state_height.revision_height() >= event_height.revision_height(), + "{} < {}", + consensus_state_height.revision_height(), + event_height.revision_height() + ); + + // RelayerMsg::Sequence([].into()); + RelayerMsg::Aggregate { + data: [AggregateData::from(Identified::new( + this_chain_id.clone(), + Data::TrustedClientState(TrustedClientState { + fetched_at: trusted_client_state_fetched_at_height, + client_id: trusted_client_state_client_id, + trusted_client_state, + }), + ))] + .into(), + queue: [ + fetch::( + this_chain_id.clone(), + FetchStateProof { + at: trusted_client_state_fetched_at_height, + path: proof::Path::ChannelEndPath(ChannelEndPath { + port_id: event.port_id.clone(), + channel_id: event.channel_id.clone(), + }), + }, + ), + fetch::( + this_chain_id.clone(), + FetchChannelEnd { + at: trusted_client_state_fetched_at_height, + port_id: event.port_id.clone(), + channel_id: event.channel_id.clone(), + }, + ), + ] + .into(), + receiver: AggregateReceiver::from(Identified::new( + this_chain_id, + Aggregate::ChannelOpenAck(AggregateChannelOpenAck { + event_height, + event, + }), + )), + } + } + AggregateMsgAfterUpdate::ChannelOpenConfirm(AggregateChannelOpenConfirm { + event_height, + event, + }) => { + let consensus_state_height = trusted_client_state_fetched_at_height; + + assert_eq!( + consensus_state_height.revision_number(), + event_height.revision_number(), + "{consensus_state_height}, {event_height}", + ); + + assert!( + consensus_state_height.revision_height() >= event_height.revision_height(), + "{} < {}", + consensus_state_height.revision_height(), + event_height.revision_height() + ); + + RelayerMsg::Aggregate { + data: [AggregateData::from(Identified::new( + this_chain_id.clone(), + Data::TrustedClientState(TrustedClientState { + fetched_at: trusted_client_state_fetched_at_height, + client_id: trusted_client_state_client_id, + trusted_client_state, + }), + ))] + .into(), + queue: [ + fetch::( + this_chain_id.clone(), + FetchStateProof { + at: trusted_client_state_fetched_at_height, + path: proof::Path::ChannelEndPath(ChannelEndPath { + port_id: event.port_id.clone(), + channel_id: event.channel_id.clone(), + }), + }, + ), + fetch::( + this_chain_id.clone(), + FetchChannelEnd { + at: trusted_client_state_fetched_at_height, + port_id: event.port_id.clone(), + channel_id: event.channel_id.clone(), + }, + ), + ] + .into(), + receiver: AggregateReceiver::from(Identified::new( + this_chain_id, + Aggregate::ChannelOpenConfirm(AggregateChannelOpenConfirm { + event_height, + event, + }), + )), + } + } + AggregateMsgAfterUpdate::RecvPacket(AggregateRecvPacket { + event_height, + event, + }) => { + // + tracing::debug!("building aggregate for RecvPacket"); + + RelayerMsg::Aggregate { + data: [AggregateData::from(Identified::new( + this_chain_id.clone(), + Data::TrustedClientState(TrustedClientState { + fetched_at: trusted_client_state_fetched_at_height, + client_id: trusted_client_state_client_id, + trusted_client_state, + }), + ))] + .into(), + queue: [fetch::( + this_chain_id.clone(), + FetchStateProof { + at: trusted_client_state_fetched_at_height, + path: proof::Path::CommitmentPath(CommitmentPath { + port_id: event.packet_src_port.clone(), + channel_id: event.packet_src_channel.clone(), + sequence: event.packet_sequence, + }), + }, + )] + .into(), + receiver: AggregateReceiver::from(Identified::new( + this_chain_id, + Aggregate::RecvPacket(AggregateRecvPacket { + event_height, + event, + }), + )), + } + } + AggregateMsgAfterUpdate::AckPacket(AggregateAckPacket { + event_height, + event, + block_hash, + counterparty_client_id, + }) => RelayerMsg::Aggregate { + data: [AggregateData::from(Identified::new( + this_chain_id.clone(), + Data::TrustedClientState(TrustedClientState { + fetched_at: trusted_client_state_fetched_at_height, + client_id: trusted_client_state_client_id, + trusted_client_state: trusted_client_state.clone(), + }), + ))] + .into(), + queue: [ + fetch::( + this_chain_id.clone(), + FetchPacketAcknowledgement { + block_hash: block_hash.clone(), + destination_port_id: event.packet_dst_port.clone(), + destination_channel_id: event.packet_dst_channel.clone(), + sequence: event.packet_sequence, + __marker: PhantomData, + }, + ), + fetch::( + this_chain_id.clone(), + FetchStateProof { + at: trusted_client_state_fetched_at_height, + path: proof::Path::AcknowledgementPath(AcknowledgementPath { + port_id: event.packet_dst_port.clone(), + channel_id: event.packet_dst_channel.clone(), + sequence: event.packet_sequence, + }), + }, + ), + ] + .into(), + receiver: AggregateReceiver::from(Identified::new( + this_chain_id, + Aggregate::AckPacket(AggregateAckPacket { + event_height, + event, + block_hash, + counterparty_client_id, + }), + )), + }, + } + } +} + +impl UseAggregate for identified!(AggregateConnectionOpenTry) +where + identified!(TrustedClientState): IsAggregateData, + identified!(ClientStateProof): IsAggregateData, + identified!(ClientConsensusStateProof): IsAggregateData, + identified!(ConnectionProof): IsAggregateData, + identified!(ConnectionEnd): IsAggregateData, + AnyLightClientIdentified: From)>, +{ + type AggregatedData = HList![ + identified!(TrustedClientState), + identified!(ClientStateProof), + identified!(ClientConsensusStateProof), + identified!(ConnectionProof), + identified!(ConnectionEnd), + ]; + + fn aggregate( + Identified { + chain_id: this_chain_id, + data: + AggregateConnectionOpenTry { + event_height: trusted_height, + event, + }, + }: Self, + hlist_pat![ + Identified { + chain_id: trusted_client_state_chain_id, + data: TrustedClientState { + fetched_at: _, + client_id: _, + trusted_client_state + } + }, + Identified { + chain_id: client_state_proof_chain_id, + data: ClientStateProof { + height: client_state_proof_height, + proof: client_state_proof, + } + }, + Identified { + chain_id: consensus_state_proof_chain_id, + data: ClientConsensusStateProof { + height: consensus_state_proof_height, + proof: consensus_state_proof, + } + }, + Identified { + chain_id: connection_proof_chain_id, + data: ConnectionProof { + height: connection_proof_height, + proof: connection_proof, + } + }, + Identified { + chain_id: connection_end_chain_id, + data: ConnectionEnd(connection_end), + }, + ]: Self::AggregatedData, + ) -> RelayerMsg { + assert!(consensus_state_proof_height.revision_height() >= trusted_height.revision_height()); + assert!(client_state_proof_height.revision_height() >= trusted_height.revision_height()); + + let counterparty_chain_id: ChainIdOf = trusted_client_state.chain_id(); + + assert_eq!(trusted_client_state_chain_id, this_chain_id); + + assert_eq!(client_state_proof_chain_id, this_chain_id); + assert_eq!(consensus_state_proof_chain_id, this_chain_id); + assert_eq!(connection_proof_chain_id, this_chain_id); + assert_eq!(connection_end_chain_id, this_chain_id); + + let consensus_height = trusted_client_state.height(); + + msg::( + counterparty_chain_id, + MsgConnectionOpenTryData { + msg: MsgConnectionOpenTry { + client_id: event.counterparty_client_id, + client_state: trusted_client_state, + counterparty: connection::counterparty::Counterparty { + client_id: event.client_id, + connection_id: event.connection_id, + prefix: MerklePrefix { + key_prefix: b"ibc".to_vec(), + }, + }, + delay_period: DELAY_PERIOD, + counterparty_versions: connection_end.versions, + proof_height: connection_proof_height, + proof_init: connection_proof, + proof_client: client_state_proof, + proof_consensus: consensus_state_proof, + consensus_height, + }, + }, + ) + } +} + +impl UseAggregate for identified!(AggregateConnectionOpenAck) +where + identified!(TrustedClientState): IsAggregateData, + identified!(ClientStateProof): IsAggregateData, + identified!(ClientConsensusStateProof): IsAggregateData, + identified!(ConnectionProof): IsAggregateData, + identified!(ConnectionEnd): IsAggregateData, + AnyLightClientIdentified: From)>, +{ + type AggregatedData = HList![ + identified!(TrustedClientState), + identified!(ClientStateProof), + identified!(ClientConsensusStateProof), + identified!(ConnectionProof), + identified!(ConnectionEnd), + ]; + + fn aggregate( + Identified { + chain_id: this_chain_id, + data: + AggregateConnectionOpenAck { + event_height: trusted_height, + event, + }, + }: Self, + hlist_pat![ + Identified { + chain_id: trusted_client_state_chain_id, + data: TrustedClientState { + fetched_at: _, + client_id: _, + trusted_client_state + } + }, + Identified { + chain_id: client_state_proof_chain_id, + data: ClientStateProof { + height: client_state_proof_height, + proof: client_state_proof, + } + }, + Identified { + chain_id: consensus_state_proof_chain_id, + data: ClientConsensusStateProof { + height: consensus_state_proof_height, + proof: consensus_state_proof, + } + }, + Identified { + chain_id: connection_proof_chain_id, + data: ConnectionProof { + height: connection_proof_height, + proof: connection_proof, + } + }, + Identified { + chain_id: connection_end_chain_id, + data: ConnectionEnd(connection_end), + }, + ]: Self::AggregatedData, + ) -> RelayerMsg { + assert!(consensus_state_proof_height.revision_height() >= trusted_height.revision_height()); + assert!(client_state_proof_height.revision_height() >= trusted_height.revision_height()); + + let counterparty_chain_id: ChainIdOf = trusted_client_state.chain_id(); + + assert_eq!(trusted_client_state_chain_id, this_chain_id); + assert_eq!(client_state_proof_chain_id, this_chain_id); + assert_eq!(consensus_state_proof_chain_id, this_chain_id); + assert_eq!(connection_proof_chain_id, this_chain_id); + assert_eq!(connection_end_chain_id, this_chain_id); + + let consensus_height = trusted_client_state.height(); + + msg::( + counterparty_chain_id, + MsgConnectionOpenAckData { + msg: MsgConnectionOpenAck { + connection_id: event.counterparty_connection_id, + counterparty_connection_id: event.connection_id, + // TODO: Figure out a way to not panic here, likely by encoding this invariant into the type somehow + version: connection_end.versions[0].clone(), + client_state: trusted_client_state, + proof_height: connection_proof_height.into(), + proof_try: connection_proof, + proof_client: client_state_proof, + proof_consensus: consensus_state_proof, + consensus_height: consensus_height.into(), + }, + }, + ) + } +} + +impl UseAggregate for identified!(AggregateConnectionOpenConfirm) +where + identified!(TrustedClientState): IsAggregateData, + identified!(ClientStateProof): IsAggregateData, + identified!(ClientConsensusStateProof): IsAggregateData, + identified!(ConnectionProof): IsAggregateData, + AnyLightClientIdentified: From)>, +{ + type AggregatedData = HList![ + identified!(TrustedClientState), + identified!(ConnectionProof), + ]; + + fn aggregate( + Identified { + chain_id: this_chain_id, + data: + AggregateConnectionOpenConfirm { + event_height: _, + event, + }, + }: Self, + hlist_pat![ + Identified { + chain_id: trusted_client_state_chain_id, + data: TrustedClientState { + fetched_at: _, + client_id: _, + trusted_client_state + } + }, + Identified { + chain_id: connection_proof_chain_id, + data: ConnectionProof { + height: connection_proof_height, + proof: connection_proof + } + }, + ]: Self::AggregatedData, + ) -> RelayerMsg { + let counterparty_chain_id: ChainIdOf = trusted_client_state.chain_id(); + + assert_eq!(trusted_client_state_chain_id, this_chain_id); + assert_eq!(connection_proof_chain_id, this_chain_id); + + msg::( + counterparty_chain_id, + MsgConnectionOpenConfirmData(MsgConnectionOpenConfirm { + connection_id: event.counterparty_connection_id, + proof_height: connection_proof_height, + proof_ack: connection_proof, + }), + ) + } +} + +impl UseAggregate for identified!(AggregateChannelOpenTry) +where + identified!(TrustedClientState): IsAggregateData, + identified!(ChannelEndProof): IsAggregateData, + identified!(ConnectionEnd): IsAggregateData, + identified!(ChannelEnd): IsAggregateData, + AnyLightClientIdentified: From)>, +{ + type AggregatedData = HList![ + identified!(TrustedClientState), + identified!(ChannelEndProof), + identified!(ConnectionEnd), + identified!(ChannelEnd), + ]; + + fn aggregate( + Identified { + chain_id: this_chain_id, + data: + AggregateChannelOpenTry { + event_height: _, + event, + }, + }: Self, + hlist_pat![ + Identified { + chain_id: trusted_client_state_chain_id, + data: TrustedClientState { + fetched_at: _, + client_id: _, + trusted_client_state + } + }, + Identified { + chain_id: channel_proof_chain_id, + data: ChannelEndProof { + proof: channel_proof, + height: channel_proof_height + } + }, + Identified { + chain_id: _connection_end_chain_id, + data: ConnectionEnd(connection) + }, + Identified { + chain_id: _channel_end_chain_id, + data: ChannelEnd { + channel, + __marker: _ + }, + }, + ]: Self::AggregatedData, + ) -> RelayerMsg { + assert_eq!(this_chain_id, trusted_client_state_chain_id); + + let counterparty_chain_id: ChainIdOf = trusted_client_state.chain_id(); + + assert_eq!(channel_proof_chain_id, this_chain_id); + + msg::( + counterparty_chain_id, + MsgChannelOpenTryData { + msg: MsgChannelOpenTry { + port_id: channel.counterparty.port_id.clone(), + channel: Channel { + state: channel::state::State::Tryopen, + ordering: channel.ordering, + counterparty: channel::counterparty::Counterparty { + port_id: event.port_id.clone(), + channel_id: event.channel_id.clone().to_string(), + }, + connection_hops: vec![connection + .counterparty + .connection_id + .parse() + .unwrap()], + version: event.version.clone(), + }, + // NOTE: Review behaviour here + counterparty_version: event.version, + proof_init: channel_proof, + proof_height: channel_proof_height.into(), + }, + __marker: PhantomData, + }, + ) + } +} + +impl UseAggregate for identified!(AggregateChannelOpenAck) +where + identified!(TrustedClientState): IsAggregateData, + identified!(ChannelEndProof): IsAggregateData, + identified!(ChannelEnd): IsAggregateData, + AnyLightClientIdentified: From)>, +{ + type AggregatedData = HList![ + identified!(TrustedClientState), + identified!(ChannelEndProof), + identified!(ChannelEnd), + ]; + + fn aggregate( + Identified { + chain_id: this_chain_id, + data: + AggregateChannelOpenAck { + event_height: _, + event, + }, + }: Self, + hlist_pat![ + Identified { + chain_id: trusted_client_state_chain_id, + data: TrustedClientState { + fetched_at: _, + client_id: _, + trusted_client_state + } + }, + Identified { + chain_id: channel_proof_chain_id, + data: ChannelEndProof { + height: channel_proof_height, + proof: channel_proof, + } + }, + Identified { + chain_id: channel_end_chain_id, + data: ChannelEnd { + channel, + __marker: _ + }, + }, + ]: Self::AggregatedData, + ) -> RelayerMsg { + let counterparty_chain_id: ChainIdOf = trusted_client_state.chain_id(); + + assert_eq!(trusted_client_state_chain_id, this_chain_id); + assert_eq!(channel_proof_chain_id, this_chain_id); + assert_eq!(channel_end_chain_id, this_chain_id); + + msg::( + counterparty_chain_id, + MsgChannelOpenAckData { + msg: MsgChannelOpenAck { + port_id: channel.counterparty.port_id.clone(), + channel_id: event.counterparty_channel_id, + counterparty_channel_id: event.channel_id, + counterparty_version: event.version, + proof_try: channel_proof, + proof_height: channel_proof_height.into(), + }, + __marker: PhantomData, + }, + ) + } +} + +impl UseAggregate for identified!(AggregateChannelOpenConfirm) +where + identified!(TrustedClientState): IsAggregateData, + identified!(ChannelEndProof): IsAggregateData, + identified!(ChannelEnd): IsAggregateData, + AnyLightClientIdentified: From)>, +{ + type AggregatedData = HList![ + identified!(TrustedClientState), + identified!(ChannelEndProof), + identified!(ChannelEnd), + ]; + + fn aggregate( + Identified { + chain_id: this_chain_id, + data: + AggregateChannelOpenConfirm { + event_height: _, + event, + }, + }: Self, + hlist_pat![ + Identified { + chain_id: trusted_client_state_chain_id, + data: TrustedClientState { + fetched_at: _, + client_id: _, + trusted_client_state + } + }, + Identified { + chain_id: channel_proof_chain_id, + data: ChannelEndProof { + height: channel_proof_height, + proof: channel_proof, + } + }, + Identified { + chain_id: channel_end_chain_id, + data: ChannelEnd { + channel, + __marker: _ + }, + }, + ]: Self::AggregatedData, + ) -> RelayerMsg { + assert_eq!(this_chain_id, trusted_client_state_chain_id); + assert_eq!(this_chain_id, channel_proof_chain_id); + assert_eq!(channel_end_chain_id, this_chain_id); + + let counterparty_chain_id: ChainIdOf = trusted_client_state.chain_id(); + + msg::( + counterparty_chain_id, + MsgChannelOpenConfirmData { + msg: MsgChannelOpenConfirm { + port_id: channel.counterparty.port_id, + channel_id: event.counterparty_channel_id, + proof_ack: channel_proof, + proof_height: channel_proof_height.into(), + }, + __marker: PhantomData, + }, + ) + } +} + +impl UseAggregate for identified!(AggregateRecvPacket) +where + identified!(TrustedClientState): IsAggregateData, + identified!(CommitmentProof): IsAggregateData, + AnyLightClientIdentified: From)>, +{ + type AggregatedData = HList![ + identified!(TrustedClientState), + identified!(CommitmentProof), + ]; + + fn aggregate( + Identified { + chain_id: this_chain_id, + data: + AggregateRecvPacket { + event_height: _, + event, + }, + }: Self, + hlist_pat![ + Identified { + chain_id: trusted_client_state_chain_id, + data: TrustedClientState { + fetched_at: _, + client_id: _, + trusted_client_state + } + }, + Identified { + chain_id: commitment_proof_chain_id, + data: CommitmentProof { + height: commitment_proof_height, + proof: commitment_proof + } + }, + ]: Self::AggregatedData, + ) -> RelayerMsg { + assert_eq!(this_chain_id, trusted_client_state_chain_id); + assert_eq!(this_chain_id, commitment_proof_chain_id); + + let counterparty_chain_id: ChainIdOf = trusted_client_state.chain_id(); + + msg::( + counterparty_chain_id, + MsgRecvPacketData { + msg: MsgRecvPacket { + packet: Packet { + sequence: event.packet_sequence, + source_port: event.packet_src_port, + source_channel: event.packet_src_channel, + destination_port: event.packet_dst_port, + destination_channel: event.packet_dst_channel, + data: event.packet_data_hex, + timeout_height: event.packet_timeout_height, + timeout_timestamp: event.packet_timeout_timestamp, + }, + proof_commitment: commitment_proof, + proof_height: commitment_proof_height.into(), + }, + __marker: PhantomData, + }, + ) + } +} + +impl UseAggregate for identified!(AggregateAckPacket) +where + identified!(TrustedClientState): IsAggregateData, + identified!(PacketAcknowledgement): IsAggregateData, + identified!(AcknowledgementProof): IsAggregateData, + AnyLightClientIdentified: From)>, +{ + type AggregatedData = HList![ + identified!(TrustedClientState), + identified!(PacketAcknowledgement), + identified!(AcknowledgementProof), + ]; + + fn aggregate( + Identified { + chain_id: this_chain_id, + data: + AggregateAckPacket { + event_height: _, + event, + block_hash: _, + counterparty_client_id: _, + }, + }: Self, + hlist_pat![ + Identified { + chain_id: trusted_client_state_chain_id, + data: TrustedClientState { + fetched_at: _, + client_id: _, + trusted_client_state + } + }, + Identified { + chain_id: packet_acknowledgement_chain_id, + data: PacketAcknowledgement { fetched_by: _, ack } + }, + Identified { + chain_id: commitment_proof_chain_id, + data: AcknowledgementProof { + proof: acknowledgement_proof, + height: acknowledgement_proof_height + }, + }, + ]: Self::AggregatedData, + ) -> RelayerMsg { + assert_eq!(this_chain_id, trusted_client_state_chain_id); + assert_eq!(this_chain_id, packet_acknowledgement_chain_id); + assert_eq!(commitment_proof_chain_id, this_chain_id); + + let counterparty_chain_id: ChainIdOf = trusted_client_state.chain_id(); + + msg::( + counterparty_chain_id, + MsgAckPacketData { + msg: MsgAcknowledgement { + proof_height: acknowledgement_proof_height.into(), + packet: Packet { + sequence: event.packet_sequence, + source_port: event.packet_src_port, + source_channel: event.packet_src_channel, + destination_port: event.packet_dst_port, + destination_channel: event.packet_dst_channel, + data: event.packet_data_hex, + timeout_height: event.packet_timeout_height, + timeout_timestamp: event.packet_timeout_timestamp, + }, + acknowledgement: ack, + proof_acked: acknowledgement_proof, + }, + __marker: PhantomData, + }, + ) + } +} + +impl UseAggregate for identified!(AggregateFetchCounterpartyStateProof) +where + identified!(TrustedClientState): IsAggregateData, + AnyLightClientIdentified: From)>, +{ + type AggregatedData = HList![identified!(TrustedClientState),]; + + fn aggregate( + Identified { + chain_id: this_chain_id, + data: + AggregateFetchCounterpartyStateProof { + counterparty_client_id: _, + fetch: fetch_, + }, + }: Self, + hlist_pat![Identified { + chain_id: trusted_client_state_chain_id, + data: TrustedClientState { + fetched_at: _, + client_id: _, + trusted_client_state + } + }]: Self::AggregatedData, + ) -> RelayerMsg { + assert_eq!(this_chain_id, trusted_client_state_chain_id); + + let counterparty_chain_id: ChainIdOf = trusted_client_state.chain_id(); + + fetch::(counterparty_chain_id, fetch_) + } +} + +impl UseAggregate for identified!(AggregateCreateClient) +where + identified!(SelfClientState): IsAggregateData, + identified!(SelfConsensusState): IsAggregateData, + AnyLightClientIdentified: From)>, +{ + type AggregatedData = HList![ + identified!(SelfClientState), + identified!(SelfConsensusState), + ]; + + fn aggregate( + Identified { + chain_id: this_chain_id, + data: this, + }: Self, + hlist_pat![ + Identified { + chain_id: self_client_state_chain_id, + data: SelfClientState(self_client_state) + }, + Identified { + chain_id: self_consensus_state_chain_id, + data: SelfConsensusState(self_consensus_state) + }, + ]: Self::AggregatedData, + ) -> RelayerMsg { + assert_eq!(self_client_state_chain_id, self_consensus_state_chain_id); + + // let counterparty_chain_id = self_client_state_chain_id; + + msg::( + this_chain_id, + MsgCreateClientData { + config: this.config, + msg: MsgCreateClient { + client_state: self_client_state, + consensus_state: self_consensus_state, + }, + }, + ) + } +} diff --git a/voyager/src/msg/data.rs b/voyager/voyager-message/src/data.rs similarity index 85% rename from voyager/src/msg/data.rs rename to voyager/voyager-message/src/data.rs index bcdceae830..e6f86fee5b 100644 --- a/voyager/src/msg/data.rs +++ b/voyager/voyager-message/src/data.rs @@ -2,13 +2,14 @@ use std::marker::PhantomData; use frame_support_procedural::{CloneNoBound, DebugNoBound, PartialEqNoBound}; use serde::{Deserialize, Serialize}; -use unionlabs::{self, ibc::core::channel::channel::Channel}; +use unionlabs::{ + self, + ibc::core::channel::channel::Channel, + traits::{ChainOf, ClientStateOf, ConsensusStateOf, HeaderOf, HeightOf, LightClientBase}, +}; use crate::{ - chain::{ - ChainOf, ClientStateOf, ConsensusStateOf, HeaderOf, HeightOf, LightClient, LightClientBase, - }, - msg::{any_enum, fetch::FetchPacketAcknowledgement, identified, AnyLightClientIdentified}, + any_enum, fetch::FetchPacketAcknowledgement, identified, AnyLightClientIdentified, LightClient, }; any_enum! { @@ -156,13 +157,13 @@ pub struct LightClientSpecificData(pub L::Data); macro_rules! data_msg { ($($Ty:ident,)+) => { $( - impl From>> for crate::msg::AggregateData + impl From>> for crate::AggregateData where $Ty: Into>, - crate::msg::AggregateData: From)>, + crate::AggregateData: From)>, { - fn from(crate::msg::Identified { chain_id, data }: identified!($Ty)) -> Self { - Self::from(crate::msg::Identified { + fn from(crate::Identified { chain_id, data }: identified!($Ty)) -> Self { + Self::from(crate::Identified { chain_id, data: Data::from(data), }) @@ -170,23 +171,23 @@ macro_rules! data_msg { } impl TryFrom> - for crate::msg::Identified> + for crate::Identified> where identified!(Data): TryFrom< - crate::msg::AnyLightClientIdentified, - Error = crate::msg::AnyLightClientIdentified, - > + Into>, + crate::AnyLightClientIdentified, + Error = crate::AnyLightClientIdentified, + > + Into>, { type Error = AnyLightClientIdentified; - fn try_from(value: crate::msg::AnyLightClientIdentified) -> Result { - let crate::msg::Identified { chain_id, data } = - >>::try_from(value)?; + fn try_from(value: crate::AnyLightClientIdentified) -> Result { + let crate::Identified { chain_id, data } = + >>::try_from(value)?; - Ok(crate::msg::Identified::new( + Ok(crate::Identified::new( chain_id.clone(), <$Ty>::try_from(data).map_err(|x: Data| { - Into::>::into(crate::msg::Identified::new(chain_id, x)) + Into::>::into(crate::Identified::new(chain_id, x)) })?, )) } diff --git a/voyager/voyager-message/src/event.rs b/voyager/voyager-message/src/event.rs new file mode 100644 index 0000000000..b7585b959c --- /dev/null +++ b/voyager/voyager-message/src/event.rs @@ -0,0 +1,401 @@ +use std::fmt::Display; + +use frame_support_procedural::{CloneNoBound, DebugNoBound, PartialEqNoBound}; +use serde::{Deserialize, Serialize}; +use unionlabs::{ + ethereum::H256, + traits::{Chain, ChainOf, HeightOf, LightClientBase}, + QueryHeight, +}; + +use crate::{ + aggregate::{ + mk_aggregate_wait_for_update, Aggregate, AggregateChannelHandshakeUpdateClient, + AggregateConnectionFetchFromChannelEnd, AggregateConnectionOpenAck, + AggregateConnectionOpenConfirm, AggregateConnectionOpenTry, AggregateMsgAfterUpdate, + AggregatePacketUpdateClient, AggregateUpdateClientFromClientId, ChannelHandshakeEvent, + PacketEvent, + }, + any_enum, fetch, + fetch::{FetchChannelEnd, FetchConnectionEnd, FetchTrustedClientState}, + identified, seq, wait, + wait::WaitForBlock, + AggregateReceiver, AnyLcMsg, AnyLightClientIdentified, Identified, LcMsg, LightClient, + RelayerMsg, +}; + +any_enum! { + #[any = AnyEvent] + pub enum Event { + Ibc(IbcEvent), + Command(Command), + } +} + +impl Event { + pub fn handle(self, l: L) -> Vec + where + AnyLightClientIdentified: From)>, + AggregateReceiver: From)>, + { + match self { + Event::Ibc(ibc_event) => match ibc_event.event { + unionlabs::events::IbcEvent::CreateClient(e) => { + println!("client created: {e:#?}"); + + vec![] + } + unionlabs::events::IbcEvent::UpdateClient(e) => { + println!( + "client updated: {:#?} to {:#?}", + e.client_id, e.consensus_heights + ); + + vec![] + } + + unionlabs::events::IbcEvent::ClientMisbehaviour(_) => unimplemented!(), + unionlabs::events::IbcEvent::SubmitEvidence(_) => unimplemented!(), + + unionlabs::events::IbcEvent::ConnectionOpenInit(init) => [seq([ + wait::(l.chain().chain_id(), WaitForBlock(ibc_event.height)), + RelayerMsg::Aggregate { + data: [].into(), + queue: [mk_aggregate_wait_for_update( + l.chain().chain_id(), + init.client_id.clone(), + init.counterparty_client_id.clone(), + ibc_event.height, + )] + .into(), + receiver: AggregateReceiver::from(Identified::new( + l.chain().chain_id(), + Aggregate::AggregateMsgAfterUpdate( + AggregateMsgAfterUpdate::ConnectionOpenTry( + AggregateConnectionOpenTry { + event_height: ibc_event.height, + event: init, + }, + ), + ), + )), + }, + ])] + .into(), + unionlabs::events::IbcEvent::ConnectionOpenTry(try_) => { + [seq([RelayerMsg::Aggregate { + data: [].into(), + queue: [mk_aggregate_wait_for_update( + l.chain().chain_id(), + try_.client_id.clone(), + try_.counterparty_client_id.clone(), + ibc_event.height, + )] + .into(), + receiver: AggregateReceiver::from(Identified::new( + l.chain().chain_id(), + Aggregate::AggregateMsgAfterUpdate( + AggregateMsgAfterUpdate::ConnectionOpenAck( + AggregateConnectionOpenAck { + event_height: ibc_event.height, + event: try_, + }, + ), + ), + )), + }])] + .into() + } + unionlabs::events::IbcEvent::ConnectionOpenAck(ack) => { + [seq([RelayerMsg::Aggregate { + data: [].into(), + queue: [mk_aggregate_wait_for_update( + l.chain().chain_id(), + ack.client_id.clone(), + ack.counterparty_client_id.clone(), + ibc_event.height, + )] + .into(), + receiver: AggregateReceiver::from(Identified::new( + l.chain().chain_id(), + Aggregate::AggregateMsgAfterUpdate( + AggregateMsgAfterUpdate::ConnectionOpenConfirm( + AggregateConnectionOpenConfirm { + event_height: ibc_event.height, + event: ack, + }, + ), + ), + )), + }])] + .into() + } + unionlabs::events::IbcEvent::ConnectionOpenConfirm(confirm) => { + println!("connection opened: {confirm:#?}"); + + vec![] + } + + unionlabs::events::IbcEvent::ChannelOpenInit(init) => { + [seq([RelayerMsg::Aggregate { + data: [].into(), + queue: [RelayerMsg::Aggregate { + data: [].into(), + queue: [fetch( + l.chain().chain_id(), + FetchChannelEnd { + at: ibc_event.height, + port_id: init.port_id.clone(), + channel_id: init.channel_id.clone(), + }, + )] + .into(), + receiver: AggregateReceiver::from(Identified::new( + l.chain().chain_id(), + Aggregate::ConnectionFetchFromChannelEnd( + AggregateConnectionFetchFromChannelEnd { + at: ibc_event.height, + }, + ), + )), + }] + .into(), + receiver: AggregateReceiver::from(Identified::new( + l.chain().chain_id(), + Aggregate::ChannelHandshakeUpdateClient( + AggregateChannelHandshakeUpdateClient { + update_to: ibc_event.height, + event_height: ibc_event.height, + channel_handshake_event: ChannelHandshakeEvent::Init(init), + }, + ), + )), + }])] + .into() + } + unionlabs::events::IbcEvent::ChannelOpenTry(try_) => { + [seq([RelayerMsg::Aggregate { + data: [].into(), + queue: [RelayerMsg::Aggregate { + data: [].into(), + queue: [fetch( + l.chain().chain_id(), + FetchChannelEnd { + at: ibc_event.height, + port_id: try_.port_id.clone(), + channel_id: try_.channel_id.clone(), + }, + )] + .into(), + receiver: AggregateReceiver::from(Identified::new( + l.chain().chain_id(), + Aggregate::ConnectionFetchFromChannelEnd( + AggregateConnectionFetchFromChannelEnd { + at: ibc_event.height, + }, + ), + )), + }] + .into(), + receiver: AggregateReceiver::from(Identified::new( + l.chain().chain_id(), + Aggregate::ChannelHandshakeUpdateClient( + AggregateChannelHandshakeUpdateClient { + update_to: ibc_event.height, + event_height: ibc_event.height, + channel_handshake_event: ChannelHandshakeEvent::Try(try_), + }, + ), + )), + }])] + .into() + } + unionlabs::events::IbcEvent::ChannelOpenAck(ack) => { + [seq([RelayerMsg::Aggregate { + data: [].into(), + queue: [RelayerMsg::Aggregate { + data: [].into(), + queue: [fetch( + l.chain().chain_id(), + FetchChannelEnd { + at: ibc_event.height, + port_id: ack.port_id.clone(), + channel_id: ack.channel_id.clone(), + }, + )] + .into(), + receiver: AggregateReceiver::from(Identified::new( + l.chain().chain_id(), + Aggregate::ConnectionFetchFromChannelEnd( + AggregateConnectionFetchFromChannelEnd { + at: ibc_event.height, + }, + ), + )), + }] + .into(), + receiver: AggregateReceiver::from(Identified::new( + l.chain().chain_id(), + Aggregate::ChannelHandshakeUpdateClient( + AggregateChannelHandshakeUpdateClient { + update_to: ibc_event.height, + event_height: ibc_event.height, + channel_handshake_event: ChannelHandshakeEvent::Ack(ack), + }, + ), + )), + }])] + .into() + } + + unionlabs::events::IbcEvent::ChannelOpenConfirm(confirm) => { + println!("channel opened: {confirm:#?}"); + + vec![] + } + + unionlabs::events::IbcEvent::RecvPacket(packet) => [seq([RelayerMsg::Aggregate { + data: [].into(), + queue: [fetch( + l.chain().chain_id(), + FetchConnectionEnd { + at: ibc_event.height, + connection_id: packet.connection_id.clone(), + }, + )] + .into(), + receiver: AggregateReceiver::from(Identified::new( + l.chain().chain_id(), + Aggregate::PacketUpdateClient(AggregatePacketUpdateClient { + update_to: ibc_event.height, + event_height: ibc_event.height, + block_hash: ibc_event.block_hash, + packet_event: PacketEvent::Recv(packet), + }), + )), + }])] + .into(), + unionlabs::events::IbcEvent::SendPacket(packet) => [seq([RelayerMsg::Aggregate { + data: [].into(), + queue: [fetch( + l.chain().chain_id(), + FetchConnectionEnd { + at: ibc_event.height, + connection_id: packet.connection_id.clone(), + }, + )] + .into(), + receiver: AggregateReceiver::from(Identified::new( + l.chain().chain_id(), + Aggregate::PacketUpdateClient(AggregatePacketUpdateClient { + update_to: ibc_event.height, + event_height: ibc_event.height, + block_hash: ibc_event.block_hash, + packet_event: PacketEvent::Send(packet), + }), + )), + }])] + .into(), + unionlabs::events::IbcEvent::AcknowledgePacket(ack) => { + tracing::info!(?ack, "packet acknowledged"); + [].into() + } + unionlabs::events::IbcEvent::TimeoutPacket(timeout) => { + tracing::error!(?timeout, "packet timed out"); + [].into() + } + unionlabs::events::IbcEvent::WriteAcknowledgement(write_ack) => { + tracing::info!(?write_ack, "packet acknowledgement written"); + [].into() + } + }, + Event::Command(command) => match command { + Command::UpdateClient { + client_id, + counterparty_client_id, + } => [RelayerMsg::Aggregate { + queue: [fetch::( + l.chain().chain_id(), + FetchTrustedClientState { + at: QueryHeight::Latest, + client_id: client_id.clone(), + }, + )] + .into(), + data: [].into(), + receiver: AggregateReceiver::from(Identified::new( + l.chain().chain_id(), + Aggregate::::UpdateClientFromClientId( + AggregateUpdateClientFromClientId { + client_id, + counterparty_client_id, + }, + ), + )), + }] + .into(), + }, + } + } +} + +impl Display for Event { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Event::Ibc(_) => write!(f, "Ibc"), + Event::Command(cmd) => write!(f, "{cmd}"), + } + } +} + +#[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] +#[serde(bound(serialize = "", deserialize = ""))] +pub struct IbcEvent { + pub block_hash: H256, + pub height: HeightOf>, + pub event: unionlabs::events::IbcEvent< + L::ClientId, + L::ClientType, + ::ClientId, + >, +} + +impl Display for IbcEvent { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use unionlabs::events::IbcEvent::*; + + match self.event { + CreateClient(_) => write!(f, "Ibc::CreateClient"), + UpdateClient(_) => write!(f, "Ibc::UpdateClient"), + ClientMisbehaviour(_) => write!(f, "Ibc::ClientMisbehaviour"), + SubmitEvidence(_) => write!(f, "Ibc::SubmitEvidence"), + ConnectionOpenInit(_) => write!(f, "Ibc::ConnectionOpenInit"), + ConnectionOpenTry(_) => write!(f, "Ibc::ConnectionOpenTry"), + ConnectionOpenAck(_) => write!(f, "Ibc::ConnectionOpenAck"), + ConnectionOpenConfirm(_) => write!(f, "Ibc::ConnectionOpenConfirm"), + ChannelOpenInit(_) => write!(f, "Ibc::ChannelOpenInit"), + ChannelOpenTry(_) => write!(f, "Ibc::ChannelOpenTry"), + ChannelOpenAck(_) => write!(f, "Ibc::ChannelOpenAck"), + ChannelOpenConfirm(_) => write!(f, "Ibc::ChannelOpenConfirm"), + WriteAcknowledgement(_) => write!(f, "Ibc::WriteAcknowledgement"), + RecvPacket(_) => write!(f, "Ibc::RecvPacket"), + SendPacket(_) => write!(f, "Ibc::SendPacket"), + AcknowledgePacket(_) => write!(f, "Ibc::AcknowledgePacket"), + TimeoutPacket(_) => write!(f, "Ibc::TimeoutPacket"), + } + } +} + +#[allow(non_camel_case_types, non_upper_case_globals)] +#[derive( + DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize, derive_more::Display, +)] +#[serde(bound(serialize = "", deserialize = ""))] +#[display(fmt = "Command::{}")] +pub enum Command { + #[display(fmt = "UpdateClient({client_id}, {counterparty_client_id})")] + UpdateClient { + client_id: L::ClientId, + counterparty_client_id: ::ClientId, + }, +} diff --git a/voyager/src/msg/fetch.rs b/voyager/voyager-message/src/fetch.rs similarity index 52% rename from voyager/src/msg/fetch.rs rename to voyager/voyager-message/src/fetch.rs index e112a079fe..97d2d2631b 100644 --- a/voyager/src/msg/fetch.rs +++ b/voyager/voyager-message/src/fetch.rs @@ -1,4 +1,7 @@ -use std::{fmt::Display, marker::PhantomData}; +use std::{ + fmt::{Debug, Display}, + marker::PhantomData, +}; use frame_support_procedural::{CloneNoBound, DebugNoBound, PartialEqNoBound}; use serde::{Deserialize, Serialize}; @@ -6,12 +9,17 @@ use unionlabs::{ ethereum::H256, id::{ChannelId, ConnectionId, PortId}, proof, - traits::Chain, + traits::{Chain, ChainIdOf, ChainOf, HeightOf, LightClientBase}, + QueryHeight, }; use crate::{ - chain::{ChainOf, HeightOf, LightClient, LightClientBase, QueryHeight}, - msg::{any_enum, ChainIdOf}, + any_enum, data, + data::{ + ChannelEnd, ConnectionEnd, PacketAcknowledgement, SelfClientState, SelfConsensusState, + TrustedClientState, + }, + identified, AnyLcMsg, AnyLightClientIdentified, LcMsg, LightClient, RelayerMsg, }; any_enum! { @@ -138,3 +146,119 @@ pub struct FetchUpdateHeaders { #[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] #[serde(bound(serialize = "", deserialize = ""))] pub struct LightClientSpecificFetch(pub L::Fetch); + +impl Fetch { + pub async fn handle(self, l: L) -> Vec + where + AnyLightClientIdentified: From)>, + // TODO: Remove once we no longer unwrap + <::ClientId as TryFrom< + <::HostChain as Chain>::ClientId, + >>::Error: Debug, + <::ClientId as TryFrom< + <::HostChain as Chain>::ClientId, + >>::Error: Debug, + { + let relayer_msg = match self { + Fetch::TrustedClientState(FetchTrustedClientState { at, client_id }) => { + // TODO: Split this into a separate query and aggregate + let height = match at { + QueryHeight::Latest => l.chain().query_latest_height().await, + QueryHeight::Specific(h) => h, + }; + + [data( + l.chain().chain_id(), + TrustedClientState { + fetched_at: height, + client_id: client_id.clone(), + trusted_client_state: l.query_client_state(client_id.into(), height).await, + }, + )] + .into() + } + Fetch::StateProof(msg) => [l.proof(msg)].into(), + Fetch::SelfClientState(FetchSelfClientState { at: height }) => { + // TODO: Split this into a separate query and aggregate + let height = match height { + QueryHeight::Latest => l.chain().query_latest_height().await, + QueryHeight::Specific(h) => h, + }; + + [data( + l.chain().chain_id(), + SelfClientState(l.chain().self_client_state(height).await), + )] + .into() + } + Fetch::SelfConsensusState(FetchSelfConsensusState { at: height }) => { + // TODO: Split this into a separate query and aggregate + let height = match height { + QueryHeight::Latest => l.chain().query_latest_height().await, + QueryHeight::Specific(h) => h, + }; + + [data( + l.chain().chain_id(), + SelfConsensusState(l.chain().self_consensus_state(height).await), + )] + .into() + } + Fetch::PacketAcknowledgement(FetchPacketAcknowledgement { + block_hash, + destination_port_id, + destination_channel_id, + sequence, + __marker, + }) => { + let ack = l + .chain() + .read_ack( + block_hash.clone(), + destination_channel_id.clone(), + destination_port_id.clone(), + sequence, + ) + .await; + + [data( + l.chain().chain_id(), + PacketAcknowledgement { + fetched_by: FetchPacketAcknowledgement { + block_hash, + destination_port_id, + destination_channel_id, + sequence, + __marker, + }, + ack, + }, + )] + .into() + } + Fetch::UpdateHeaders(fetch_update_headers) => { + l.generate_counterparty_updates(fetch_update_headers) + } + Fetch::LightClientSpecific(LightClientSpecificFetch(fetch)) => l.do_fetch(fetch).await, + Fetch::ChannelEnd(FetchChannelEnd { + at, + port_id, + channel_id, + }) => [data( + l.chain().chain_id(), + ChannelEnd { + channel: l.channel(channel_id, port_id, at).await, + __marker: PhantomData, + }, + )] + .into(), + Fetch::ConnectionEnd(FetchConnectionEnd { at, connection_id }) => [data( + l.chain().chain_id(), + ConnectionEnd(l.connection(connection_id, at).await), + )] + .into(), + }; + + relayer_msg + } +} diff --git a/voyager/src/msg.rs b/voyager/voyager-message/src/lib.rs similarity index 62% rename from voyager/src/msg.rs rename to voyager/voyager-message/src/lib.rs index 70129c1431..8a9e0a2571 100644 --- a/voyager/src/msg.rs +++ b/voyager/voyager-message/src/lib.rs @@ -1,36 +1,40 @@ +#![feature(return_position_impl_trait_in_trait, trait_alias)] #![allow(clippy::type_complexity)] use std::{ collections::VecDeque, fmt::{Debug, Display}, + future::Future, + time::{Duration, SystemTime, UNIX_EPOCH}, }; +use ::lightclient::{ + cometbls::{CometblsMainnet, CometblsMinimal}, + ethereum::{EthereumMainnet, EthereumMinimal}, +}; use frame_support_procedural::{CloneNoBound, DebugNoBound, PartialEqNoBound}; +use futures::{future::BoxFuture, FutureExt}; use serde::{Deserialize, Serialize}; use unionlabs::{ proof::{ AcknowledgementPath, ChannelEndPath, ClientConsensusStatePath, ClientStatePath, CommitmentPath, ConnectionPath, IbcPath, }, - traits::{Chain, ClientState}, + traits::{Chain, ChainIdOf, ChainOf, LightClientBase}, + MaybeRecoverableError, }; use crate::{ - chain::{ - evm::{CometblsMainnet, CometblsMinimal}, - union::{EthereumMainnet, EthereumMinimal}, - ChainOf, LightClient, LightClientBase, - }, - msg::{ - aggregate::AnyAggregate, - data::{AnyData, Data}, - event::{AnyEvent, Event}, - fetch::{AnyFetch, Fetch}, - msg::{AnyMsg, Msg}, - wait::{AnyWait, Wait}, - }, + aggregate::{Aggregate, AnyAggregate, LightClientSpecificAggregate}, + data::{AnyData, Data, LightClientSpecificData}, + event::{AnyEvent, Event}, + fetch::{AnyFetch, Fetch, FetchStateProof, FetchUpdateHeaders, LightClientSpecificFetch}, + msg::{AnyMsg, Msg}, + wait::{AnyWait, Wait}, }; +pub mod use_aggregate; + pub mod aggregate; pub mod data; pub mod event; @@ -39,8 +43,51 @@ pub mod fetch; pub mod msg; pub mod wait; -pub type ChainIdOf = - <<::HostChain as Chain>::SelfClientState as ClientState>::ChainId; +// TODO: Rename this module to something better, `lightclient` clashes with the workspace crate (could also rename the crate) +pub mod lightclient_impls; + +pub trait LightClient: LightClientBase { + // https://github.com/rust-lang/rust/issues/20671 + type BaseCounterparty: LightClient; + + type Data: Debug + + Display + + Clone + + PartialEq + + Serialize + + for<'de> Deserialize<'de> + + Into>; + type Fetch: Debug + + Display + + Clone + + PartialEq + + Serialize + + for<'de> Deserialize<'de> + + Into>; + type Aggregate: Debug + + Display + + Clone + + PartialEq + + Serialize + + for<'de> Deserialize<'de> + + Into> + + DoAggregate; + + /// Error type for [`Self::msg`]. + type MsgError: MaybeRecoverableError; + + fn proof(&self, msg: FetchStateProof) -> RelayerMsg; + + fn msg(&self, msg: Msg) -> impl Future> + '_; + + fn do_fetch(&self, msg: Self::Fetch) -> impl Future> + '_; + + // Should (eventually) resolve to UpdateClientData + fn generate_counterparty_updates( + &self, + update_info: FetchUpdateHeaders, + ) -> Vec; +} pub trait IntoRelayerMsg { fn into_relayer_msg(self) -> RelayerMsg; @@ -84,6 +131,195 @@ pub enum RelayerMsg { }, } +pub trait GetLc { + fn get_lc(&self, chain_id: &ChainIdOf) -> L; +} + +impl RelayerMsg { + // NOTE: Box is required bc recursion + pub fn handle( + self, + g: &G, + depth: usize, + ) -> BoxFuture<'_, Result, HandleMsgError>> + where + G: Send + + Sync + + GetLc + + GetLc + + GetLc + + GetLc, + { + tracing::info!( + depth, + %self, + "handling message", + ); + + async move { + match self { + RelayerMsg::Lc(any_lc_msg) => { + let res = match any_lc_msg { + AnyLightClientIdentified::EthereumMainnet(msg) => { + msg.data.handle(g.get_lc(&msg.chain_id)).await.map_err(AnyLcError::EthereumMainnet)? + } + AnyLightClientIdentified::EthereumMinimal(msg) => { + msg.data.handle(g.get_lc(&msg.chain_id)).await.map_err(AnyLcError::EthereumMinimal)? + } + AnyLightClientIdentified::CometblsMainnet(msg) => { + msg.data.handle(g.get_lc(&msg.chain_id)).await.map_err(AnyLcError::CometblsMainnet)? + } + AnyLightClientIdentified::CometblsMinimal(msg) => { + msg.data.handle(g.get_lc(&msg.chain_id)).await.map_err(AnyLcError::CometblsMinimal)? + } }; + + Ok(res) + } + + RelayerMsg::DeferUntil { point: DeferPoint::Relative, seconds } => + Ok([RelayerMsg::DeferUntil { point: DeferPoint::Absolute, seconds: now() + seconds }].into()), + + RelayerMsg::DeferUntil { seconds, .. } => { + // if we haven't hit the time yet, requeue the defer msg + if now() < seconds { + // TODO: Make the time configurable? + tokio::time::sleep(Duration::from_secs(1)).await; + + Ok([defer(seconds)].into()) + } else { + Ok(vec![]) + } + } + + RelayerMsg::Timeout { + timeout_timestamp, + msg, + } => { + // if we haven't hit the timeout yet, handle the msg + if now() > timeout_timestamp { + tracing::warn!(json = %serde_json::to_string(&msg).unwrap(), "message expired"); + + Ok([].into()) + } else { + msg.handle(g, depth + 1).await + } + } + RelayerMsg::Sequence(mut s) => { + let msgs = match s.pop_front() { + Some(msg) => msg.handle(g, depth + 1).await?, + None => return Ok(vec![]), + }; + + for msg in msgs.into_iter().rev() { + s.push_front(msg); + } + + Ok([flatten_seq(seq(s))].into()) + } + + RelayerMsg::Retry(count, msg) => { + const RETRY_DELAY_SECONDS: u64 = 3; + + match msg.clone().handle(g, depth + 1).await { + Ok(ok) => Ok(ok), + Err(err) => if count > 0 { + let retries_left = count - 1; + tracing::warn!( + %msg, + retries_left, + ?err, + "msg failed, retrying in {RETRY_DELAY_SECONDS} seconds" + ); + Ok([seq([defer(now() + RETRY_DELAY_SECONDS), retry(retries_left, *msg)])].into()) + } else { + tracing::error!(%msg, "msg failed after all retries"); + Err(err) + }, + } + }, + + RelayerMsg::Aggregate { + mut queue, + mut data, + receiver, + } => { + if let Some(msg) = queue.pop_front() { + let msgs = msg.handle(g, depth + 1).await?; + + for m in msgs { + match >::try_from(m) { + Ok(d) => { + data.push_back(d); + } + Err(m) => { + queue.push_back(m); + } + } + } + + let res = [RelayerMsg::Aggregate { + queue, + data, + receiver, + }] + .into(); + + Ok(res) + } else { + // queue is empty, handle msg + + let res = match receiver { + AggregateReceiver::EthereumMainnet(msg) => { + msg.handle(data) + } + AggregateReceiver::EthereumMinimal(msg) => { + msg.handle(data) + } + AggregateReceiver::CometblsMainnet(msg) => { + msg.handle(data) + } + AggregateReceiver::CometblsMinimal(msg) => { + msg.handle(data) + } + }; + + Ok(res) + } + } + RelayerMsg::Repeat { times: 0, .. } => Ok([].into()), + RelayerMsg::Repeat { times, msg } => { + Ok([flatten_seq(seq([*msg.clone(), RelayerMsg::Repeat { times: times - 1, msg}]))].into()) + }, + } + } + .boxed() + } +} + +#[derive(Debug, thiserror::Error)] +pub enum HandleMsgError { + #[error(transparent)] + Lc(#[from] AnyLcError), +} + +enum_variants_conversions! { + #[derive(Debug, thiserror::Error)] + pub enum AnyLcError { + // The 08-wasm client tracking the state of Evm. + #[error(transparent)] + EthereumMainnet(LcError), + // The 08-wasm client tracking the state of Evm. + #[error(transparent)] + EthereumMinimal(LcError), + // The solidity client on Evm tracking the state of Union. + #[error(transparent)] + CometblsMainnet(LcError), + // The solidity client on Evm tracking the state of Union. + #[error(transparent)] + CometblsMinimal(LcError), + } +} + impl TryFrom> for AnyLightClientIdentified { type Error = AnyLightClientIdentified; @@ -244,15 +480,15 @@ macro_rules! any_enum { } pub enum $Any {} - impl crate::msg::AnyLightClient for $Any { + impl crate::AnyLightClient for $Any { type Inner = $Enum; } - impl TryFrom> for $Enum { - type Error = crate::msg::LcMsg; + impl TryFrom> for $Enum { + type Error = crate::LcMsg; - fn try_from(value: crate::msg::LcMsg) -> Result { - if let crate::msg::LcMsg::$Enum(t) = value { + fn try_from(value: crate::LcMsg) -> Result { + if let crate::LcMsg::$Enum(t) = value { Ok(t) } else { Err(value) @@ -260,44 +496,44 @@ macro_rules! any_enum { } } - impl From>> for crate::msg::RelayerMsg + impl From>> for crate::RelayerMsg where - crate::msg::LcMsg: From>, - crate::msg::AnyLightClientIdentified: - From>> + crate::LcMsg: From>, + crate::AnyLightClientIdentified: + From>> { - fn from(value: crate::msg::Identified>) -> Self { + fn from(value: crate::Identified>) -> Self { Self::Lc( - >::from( - crate::msg::Identified { - chain_id: value.chain_id, data: crate::msg::LcMsg::from(value.data) + >::from( + crate::Identified { + chain_id: value.chain_id, data: crate::LcMsg::from(value.data) } ) ) } } - impl TryFrom for crate::msg::Identified> + impl TryFrom for crate::Identified> where - crate::msg::AnyLightClientIdentified: TryFrom + Into, - crate::msg::Identified>: TryFrom, Error = crate::msg::AnyLightClientIdentified> - + Into>, - crate::msg::InnerOf<$Any, L>: TryFrom, Error = crate::msg::LcMsg> + Into>, + crate::AnyLightClientIdentified: TryFrom + Into, + crate::Identified>: TryFrom, Error = crate::AnyLightClientIdentified> + + Into>, + crate::InnerOf<$Any, L>: TryFrom, Error = crate::LcMsg> + Into>, { - type Error = crate::msg::RelayerMsg; - fn try_from(value: crate::msg::RelayerMsg) -> Result { - let any_lc_msg = >::try_from(value)?; - let identified_lc_msg = >>::try_from(any_lc_msg) - .map_err(>::from)?; + type Error = crate::RelayerMsg; + fn try_from(value: crate::RelayerMsg) -> Result { + let any_lc_msg = >::try_from(value)?; + let identified_lc_msg = >>::try_from(any_lc_msg) + .map_err(>::from)?; let data = - >::try_from(identified_lc_msg.data).map_err(|x: crate::msg::LcMsg| { - Into::>::into(crate::msg::Identified::>::new( + >::try_from(identified_lc_msg.data).map_err(|x: crate::LcMsg| { + Into::>::into(crate::Identified::>::new( identified_lc_msg.chain_id.clone(), x, )) })?; - Ok(crate::msg::Identified::new(identified_lc_msg.chain_id, data)) + Ok(crate::Identified::new(identified_lc_msg.chain_id, data)) } } @@ -320,46 +556,46 @@ macro_rules! any_enum { } } - impl TryInto> for crate::msg::RelayerMsg + impl TryInto> for crate::RelayerMsg where - crate::msg::AnyLightClientIdentified: TryFrom + Into, - crate::msg::LcMsg: TryFrom, Error = crate::msg::AnyLightClientIdentified> + Into>, - crate::msg::Identified: TryFrom, Error = crate::msg::LcMsg> + Into>, + crate::AnyLightClientIdentified: TryFrom + Into, + crate::LcMsg: TryFrom, Error = crate::AnyLightClientIdentified> + Into>, + crate::Identified: TryFrom, Error = crate::LcMsg> + Into>, { - type Error = crate::msg::RelayerMsg; + type Error = crate::RelayerMsg; - fn try_into(self) -> Result, crate::msg::RelayerMsg> { - >::try_from(self) - .and_then(|x| >::try_from(x).map_err(Into::into)) + fn try_into(self) -> Result, crate::RelayerMsg> { + >::try_from(self) + .and_then(|x| >::try_from(x).map_err(Into::into)) .and_then(|x| { - >::try_from(x) - .map_err(Into::>::into) - .map_err(Into::>::into) - .map_err(Into::::into) + >::try_from(x) + .map_err(Into::>::into) + .map_err(Into::>::into) + .map_err(Into::::into) }) } } - impl TryFrom> for $VariantInner { - type Error = crate::msg::LcMsg; + impl TryFrom> for $VariantInner { + type Error = crate::LcMsg; - fn try_from(value: crate::msg::LcMsg) -> Result> { + fn try_from(value: crate::LcMsg) -> Result> { match value { - crate::msg::LcMsg::$Enum($Enum::$Variant(data)) => Ok(data), + crate::LcMsg::$Enum($Enum::$Variant(data)) => Ok(data), _ => Err(value), } } } - impl TryFrom> for crate::msg::Identified + impl TryFrom> for crate::Identified where - crate::msg::LcMsg: TryFrom, Error = crate::msg::AnyLightClientIdentified> + Into>, - Self: TryFrom, Error = crate::msg::LcMsg> + Into>, + crate::LcMsg: TryFrom, Error = crate::AnyLightClientIdentified> + Into>, + Self: TryFrom, Error = crate::LcMsg> + Into>, { - type Error = crate::msg::AnyLightClientIdentified; + type Error = crate::AnyLightClientIdentified; - fn try_from(value: crate::msg::AnyLightClientIdentified) -> Result> { - crate::msg::LcMsg::::try_from(value).and_then(|x| Self::try_from(x).map_err(Into::into)) + fn try_from(value: crate::AnyLightClientIdentified) -> Result> { + crate::LcMsg::::try_from(value).and_then(|x| Self::try_from(x).map_err(Into::into)) } } )? @@ -512,8 +748,7 @@ impl TryFrom> } } -pub type AnyDataIdentified = AnyLightClientIdentified; - +#[macro_export] macro_rules! enum_variants_conversions { ( $(#[$meta:meta])* @@ -553,7 +788,241 @@ macro_rules! enum_variants_conversions { }; } -pub(crate) use enum_variants_conversions; +#[macro_export] +macro_rules! identified { + ($Ty:ident<$L:ty>) => { + $crate::Identified<$L, $Ty<$L>> + }; +} + +#[derive( + DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize, derive_more::Display, +)] +#[serde(bound(serialize = "", deserialize = ""))] +#[allow(clippy::large_enum_variant)] +pub enum LcMsg { + #[display(fmt = "Event({})", "_0")] + Event(InnerOf), + // data that has been read + #[display(fmt = "Data({})", "_0")] + Data(InnerOf), + // read + #[display(fmt = "Fetch({})", "_0")] + Fetch(InnerOf), + // write + #[display(fmt = "Msg({})", "_0")] + Msg(InnerOf), + #[display(fmt = "Wait({})", "_0")] + Wait(InnerOf), + // REVIEW: Does this make sense as a top-level message? + #[display(fmt = "Aggregate({})", "_0")] + Aggregate(InnerOf), +} + +impl LcMsg { + pub async fn handle(self, l: L) -> Result, LcError> + where + AnyLightClientIdentified: From)>, + AnyLightClientIdentified: From)>, + AggregateReceiver: From)>, + AnyLightClientIdentified: From)>, + // TODO: Remove once we no longer unwrap in fetch.handle() + <::ClientId as TryFrom< + <::HostChain as Chain>::ClientId, + >>::Error: Debug, + <::ClientId as TryFrom< + <::HostChain as Chain>::ClientId, + >>::Error: Debug, + { + match self { + LcMsg::Event(event) => Ok(event.handle(l)), + LcMsg::Data(data) => { + // TODO: Figure out a way to bubble it up to the top level + + let data = AnyLightClientIdentified::::from(Identified::new( + l.chain().chain_id(), + data, + )); + + tracing::error!( + data = %serde_json::to_string(&data).unwrap(), + "received data outside of an aggregation" + ); + + Ok([].into()) + } + LcMsg::Fetch(fetch) => Ok(fetch.handle(l).await), + LcMsg::Msg(m) => { + // NOTE: `Msg`s don't requeue any `RelayerMsg`s; they are side-effect only. + l.msg(m).await.map_err(LcError::Msg)?; + + Ok([].into()) + } + LcMsg::Wait(wait) => Ok(wait.handle(l).await), + LcMsg::Aggregate(_) => { + todo!() + } + } + } +} + +#[derive(DebugNoBound, thiserror::Error)] +pub enum LcError { + #[error(transparent)] + Msg(L::MsgError), +} + +pub type InnerOf = ::Inner; + +pub enum AnyLcMsg {} + +impl AnyLightClient for AnyLcMsg { + type Inner = LcMsg; +} + +#[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] +#[serde(bound( + serialize = "Data: ::serde::Serialize", + deserialize = "Data: for<'d> Deserialize<'d>" +))] +// TODO: `Data: AnyLightClient` +// prerequisites: derive macro for AnyLightClient +pub struct Identified { + pub chain_id: ChainIdOf, + pub data: Data, +} + +impl Identified { + pub fn new(chain_id: ChainIdOf, data: Data) -> Self { + Self { chain_id, data } + } +} + +pub trait DoAggregate: Sized + Debug + Clone + PartialEq +where + L: LightClient, +{ + fn do_aggregate( + _: Identified, + _: VecDeque>, + ) -> Vec; +} + +// helper fns + +pub fn retry(count: u8, t: impl Into) -> RelayerMsg { + RelayerMsg::Retry(count, Box::new(t.into())) +} + +pub fn seq(ts: impl IntoIterator) -> RelayerMsg { + RelayerMsg::Sequence(ts.into_iter().collect()) +} + +pub fn defer(timestamp: u64) -> RelayerMsg { + RelayerMsg::DeferUntil { + point: DeferPoint::Absolute, + seconds: timestamp, + } +} + +pub fn defer_relative(seconds: u64) -> RelayerMsg { + RelayerMsg::DeferUntil { + point: DeferPoint::Relative, + seconds, + } +} + +pub fn fetch(chain_id: ChainIdOf, t: impl Into>) -> RelayerMsg +where + AnyLightClientIdentified: From)>, +{ + RelayerMsg::Lc(AnyLightClientIdentified::from(Identified::new( + chain_id, + LcMsg::Fetch(t.into()), + ))) +} + +pub fn msg(chain_id: ChainIdOf, t: impl Into>) -> RelayerMsg +where + AnyLightClientIdentified: From)>, +{ + RelayerMsg::Lc(AnyLightClientIdentified::from(Identified::new( + chain_id, + LcMsg::Msg(t.into()), + ))) +} + +pub fn data(chain_id: ChainIdOf, t: impl Into>) -> RelayerMsg +where + AnyLightClientIdentified: From)>, +{ + RelayerMsg::Lc(AnyLightClientIdentified::from(Identified::new( + chain_id, + LcMsg::Data(t.into()), + ))) +} + +pub fn wait(chain_id: ChainIdOf, t: impl Into>) -> RelayerMsg +where + AnyLightClientIdentified: From)>, +{ + RelayerMsg::Lc(AnyLightClientIdentified::from(Identified::new( + chain_id, + LcMsg::Wait(t.into()), + ))) +} + +pub fn event(chain_id: ChainIdOf, t: impl Into>) -> RelayerMsg +where + AnyLightClientIdentified: From)>, +{ + RelayerMsg::Lc(AnyLightClientIdentified::from(Identified::new( + chain_id, + LcMsg::Event(t.into()), + ))) +} + +/// Returns the current unix timestamp in seconds. +pub fn now() -> u64 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs() +} + +fn flatten_seq(msg: RelayerMsg) -> RelayerMsg { + fn flatten(msg: RelayerMsg) -> VecDeque { + if let RelayerMsg::Sequence(new_seq) = msg { + new_seq.into_iter().flat_map(flatten).collect() + } else { + [msg].into() + } + } + + let mut msgs = flatten(msg); + + if msgs.len() == 1 { + msgs.pop_front().unwrap() + } else { + seq(msgs) + } +} + +#[test] +fn flatten() { + use crate::{defer, seq}; + + let msg = seq([ + defer(1), + seq([defer(2), defer(3)]), + seq([defer(4)]), + defer(5), + ]); + + let msg = flatten_seq(msg); + + dbg!(msg); +} #[cfg(test)] mod tests { @@ -566,6 +1035,10 @@ mod tests { use std::{collections::VecDeque, fmt::Debug, marker::PhantomData}; use hex_literal::hex; + use lightclient::{ + cometbls::{CometblsConfig, CometblsMinimal}, + ethereum::{EthereumConfig, EthereumMinimal}, + }; use serde::{de::DeserializeOwned, Serialize}; use unionlabs::{ ethereum::{Address, H256, U256}, @@ -581,33 +1054,23 @@ mod tests { }, }, validated::ValidateT, - EmptyString, + EmptyString, QueryHeight, DELAY_PERIOD, }; use super::LcMsg; use crate::{ - chain::{ - evm::{CometblsConfig, CometblsMinimal}, - union::{EthereumConfig, EthereumMinimal}, - QueryHeight, - }, - msg::{ - aggregate::{Aggregate, AggregateCreateClient, AnyAggregate}, - data::Data, - defer_relative, event, - event::{Event, IbcEvent}, - fetch, - fetch::{ - AnyFetch, Fetch, FetchConnectionEnd, FetchSelfClientState, FetchSelfConsensusState, - FetchTrustedClientState, - }, - msg, - msg::{ - Msg, MsgChannelOpenInitData, MsgConnectionOpenInitData, MsgConnectionOpenTryData, - }, - seq, AggregateReceiver, AnyLcMsg, AnyMsg, Identified, RelayerMsg, + aggregate::{Aggregate, AggregateCreateClient, AnyAggregate}, + data::Data, + defer_relative, event, + event::{Event, IbcEvent}, + fetch, + fetch::{ + AnyFetch, Fetch, FetchConnectionEnd, FetchSelfClientState, FetchSelfConsensusState, + FetchTrustedClientState, }, - DELAY_PERIOD, + msg, + msg::{Msg, MsgChannelOpenInitData, MsgConnectionOpenInitData, MsgConnectionOpenTryData}, + seq, AggregateReceiver, AnyLcMsg, AnyMsg, Identified, RelayerMsg, }; macro_rules! parse { @@ -726,7 +1189,7 @@ mod tests { defer_relative(30), event::( eth_chain_id, - crate::msg::event::Command::UpdateClient { + crate::event::Command::UpdateClient { client_id: parse!("cometbls-0"), counterparty_client_id: parse!("08-wasm-0"), }, @@ -740,7 +1203,7 @@ mod tests { defer_relative(30), event::( union_chain_id.clone(), - crate::msg::event::Command::UpdateClient { + crate::event::Command::UpdateClient { client_id: parse!("08-wasm-0"), counterparty_client_id: parse!("cometbls-0"), }, @@ -816,7 +1279,7 @@ mod tests { // print_json(RelayerMsg::Lc(AnyLcMsg::EthereumMinimal(LcMsg::Event( // Identified { // chain_id: union_chain_id.clone(), - // data: crate::msg::event::Event { + // data: crate::event::Event { // block_hash: H256([0; 32]), // height: parse!("1-1433"), // event: IbcEvent::ConnectionOpenAck(ConnectionOpenAck { @@ -847,145 +1310,3 @@ mod tests { assert_eq!(&msg, &from_json, "json roundtrip failed"); } } - -macro_rules! identified { - ($Ty:ident<$L:ty>) => { - crate::msg::Identified<$L, $Ty<$L>> - }; -} - -pub(crate) use identified; - -#[derive( - DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize, derive_more::Display, -)] -#[serde(bound(serialize = "", deserialize = ""))] -#[allow(clippy::large_enum_variant)] -pub enum LcMsg { - #[display(fmt = "Event({})", "_0")] - Event(InnerOf), - // data that has been read - #[display(fmt = "Data({})", "_0")] - Data(InnerOf), - // read - #[display(fmt = "Fetch({})", "_0")] - Fetch(InnerOf), - // write - #[display(fmt = "Msg({})", "_0")] - Msg(InnerOf), - #[display(fmt = "Wait({})", "_0")] - Wait(InnerOf), - // REVIEW: Does this make sense as a top-level message? - #[display(fmt = "Aggregate({})", "_0")] - Aggregate(InnerOf), -} - -pub type InnerOf = ::Inner; - -pub enum AnyLcMsg {} - -impl AnyLightClient for AnyLcMsg { - type Inner = LcMsg; -} - -#[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] -#[serde(bound( - serialize = "Data: ::serde::Serialize", - deserialize = "Data: for<'d> Deserialize<'d>" -))] -// TODO: `Data: AnyLightClient` -// prerequisites: derive macro for AnyLightClient -pub struct Identified { - pub chain_id: ChainIdOf, - pub data: Data, -} - -impl Identified { - pub fn new(chain_id: ChainIdOf, data: Data) -> Self { - Self { chain_id, data } - } -} - -pub trait DoAggregate: Sized + Debug + Clone + PartialEq -where - L: LightClient, -{ - fn do_aggregate( - _: Identified, - _: VecDeque>, - ) -> Vec; -} - -// helper fns - -pub fn retry(count: u8, t: impl Into) -> RelayerMsg { - RelayerMsg::Retry(count, Box::new(t.into())) -} - -pub fn seq(ts: impl IntoIterator) -> RelayerMsg { - RelayerMsg::Sequence(ts.into_iter().collect()) -} - -pub fn defer(timestamp: u64) -> RelayerMsg { - RelayerMsg::DeferUntil { - point: DeferPoint::Absolute, - seconds: timestamp, - } -} - -pub fn defer_relative(seconds: u64) -> RelayerMsg { - RelayerMsg::DeferUntil { - point: DeferPoint::Relative, - seconds, - } -} - -pub fn fetch(chain_id: ChainIdOf, t: impl Into>) -> RelayerMsg -where - AnyLightClientIdentified: From)>, -{ - RelayerMsg::Lc(AnyLightClientIdentified::from(Identified::new( - chain_id, - LcMsg::Fetch(t.into()), - ))) -} - -pub fn msg(chain_id: ChainIdOf, t: impl Into>) -> RelayerMsg -where - AnyLightClientIdentified: From)>, -{ - RelayerMsg::Lc(AnyLightClientIdentified::from(Identified::new( - chain_id, - LcMsg::Msg(t.into()), - ))) -} - -pub fn data(chain_id: ChainIdOf, t: impl Into>) -> RelayerMsg -where - AnyLightClientIdentified: From)>, -{ - RelayerMsg::Lc(AnyLightClientIdentified::from(Identified::new( - chain_id, - LcMsg::Data(t.into()), - ))) -} - -pub fn wait(chain_id: ChainIdOf, t: impl Into>) -> RelayerMsg -where - AnyLightClientIdentified: From)>, -{ - RelayerMsg::Lc(AnyLightClientIdentified::from(Identified::new( - chain_id, - LcMsg::Wait(t.into()), - ))) -} - -pub fn event(chain_id: ChainIdOf, t: impl Into>) -> RelayerMsg -where - AnyLightClientIdentified: From)>, -{ - RelayerMsg::Lc(AnyLightClientIdentified::from(Identified::new( - chain_id, - LcMsg::Event(t.into()), - ))) -} diff --git a/voyager/voyager-message/src/lightclient_impls.rs b/voyager/voyager-message/src/lightclient_impls.rs new file mode 100644 index 0000000000..2165d96ffc --- /dev/null +++ b/voyager/voyager-message/src/lightclient_impls.rs @@ -0,0 +1,98 @@ +macro_rules! try_from_relayer_msg { + ( + #[ + $Lc:ident($( + lc_msg( + msg = $LcMsg:ident($Specific:ident), + ty = $Msg:ident, + variants($( $Var:ident($Ty:ty), )+), + ), + )+) + ] + ) => { + $( + $( + impl TryFrom for Identified<$Lc, $Ty> { + type Error = RelayerMsg; + fn try_from(value: RelayerMsg) -> Result, RelayerMsg> { + match value { + RelayerMsg::Lc(crate::AnyLightClientIdentified::$Lc(Identified { + chain_id, + data: LcMsg::$LcMsg( + $LcMsg::LightClientSpecific($Specific($Msg::$Var( + data, + ))), + ), + })) => Ok(Identified { chain_id, data }), + _ => Err(value), + } + } + } + )+ + + crate::lightclient_impls::this_is_a_hack_look_away! { + $Lc( + lc_msg( + msg = $LcMsg($Specific), + ty = $Msg, + variants($( $Var($Ty), )+), + ), + ) + } + + impl From<<$Lc as LightClient>::$LcMsg> for $Specific<$Lc> { + fn from(msg: <$Lc as LightClient>::$LcMsg) -> Self { + Self(msg) + } + } + )+ + }; +} + +macro_rules! this_is_a_hack_look_away { + ( + $Lc:ident( + lc_msg( + msg = Data(LightClientSpecificData), + ty = $Msg:ident, + variants($( $Var:ident($Ty:ty), )+), + ), + ) + ) => { + $( + impl From> for crate::AggregateData { + fn from(Identified { chain_id, data }: Identified<$Lc, $Ty>) -> crate::AggregateData { + crate::AggregateData::$Lc(Identified { + chain_id, + data: Data::LightClientSpecific(LightClientSpecificData($Msg::$Var( + data, + ))), + }) + } + } + + impl TryFrom for Identified<$Lc, $Ty> { + type Error = crate::AggregateData; + + fn try_from(value: crate::AggregateData) -> Result, crate::AggregateData> { + match value { + crate::AnyLightClientIdentified::$Lc(Identified { + chain_id, + data: Data::LightClientSpecific(LightClientSpecificData($Msg::$Var( + data, + ))), + }) => Ok(Identified { chain_id, data }), + _ => Err(value), + } + } + } + )+ + }; + + ($($_:tt)*) => {}; +} + +use this_is_a_hack_look_away; + +pub mod cometbls; +pub mod ethereum; diff --git a/voyager/src/chain/evm.rs b/voyager/voyager-message/src/lightclient_impls/cometbls.rs similarity index 75% rename from voyager/src/chain/evm.rs rename to voyager/voyager-message/src/lightclient_impls/cometbls.rs index f90eeaab5b..0756f86003 100644 --- a/voyager/src/chain/evm.rs +++ b/voyager/voyager-message/src/lightclient_impls/cometbls.rs @@ -1,21 +1,11 @@ use std::{collections::VecDeque, fmt::Debug, marker::PhantomData, ops::Div, sync::Arc}; use beacon_api::errors::{InternalServerError, NotFoundError}; -use chain_utils::{ - evm::{CometblsMiddleware, EthCallExt, Evm, TupleToOption}, - MaybeRecoverableError, -}; -use clap::Args; -use contracts::{ - ibc_handler::{ - self, AcknowledgePacketCall, ChannelOpenAckCall, ChannelOpenConfirmCall, - ChannelOpenInitCall, ChannelOpenTryCall, ConnectionOpenAckCall, ConnectionOpenConfirmCall, - ConnectionOpenInitCall, ConnectionOpenTryCall, CreateClientCall, GetChannelCall, - GetClientStateCall, GetConnectionCall, GetConsensusStateCall, - GetHashedPacketAcknowledgementCommitmentCall, GetHashedPacketCommitmentCall, IBCHandler, - RecvPacketCall, UpdateClientCall, - }, - shared_types::{IbcCoreChannelV1ChannelData, IbcCoreConnectionV1ConnectionEndData}, +use chain_utils::evm::{CometblsMiddleware, Evm}; +use contracts::ibc_handler::{ + self, AcknowledgePacketCall, ChannelOpenAckCall, ChannelOpenConfirmCall, ChannelOpenInitCall, + ChannelOpenTryCall, ConnectionOpenAckCall, ConnectionOpenConfirmCall, ConnectionOpenInitCall, + ConnectionOpenTryCall, CreateClientCall, IBCHandler, RecvPacketCall, UpdateClientCall, }; use ethers::{ abi::AbiEncode, @@ -27,6 +17,7 @@ use ethers::{ use frame_support_procedural::{CloneNoBound, DebugNoBound, PartialEqNoBound}; use frunk::{hlist_pat, HList}; use futures::Future; +use lightclient::cometbls::{CometblsConfig, CometblsMainnet, CometblsMinimal}; use prost::Message; use protos::union::ibc::lightclients::ethereum::v1 as ethereum_v1; use serde::{Deserialize, Serialize}; @@ -37,15 +28,8 @@ use unionlabs::{ Address, }, ethereum_consts_traits::{ChainSpec, Mainnet, Minimal}, - google::protobuf::any::Any, ibc::{ - core::{ - client::{ - height::{Height, IsHeight}, - msg_update_client::MsgUpdateClient, - }, - connection::connection_end::ConnectionEnd, - }, + core::client::{height::Height, msg_update_client::MsgUpdateClient}, lightclients::{ ethereum::{ self, @@ -57,56 +41,37 @@ use unionlabs::{ wasm, }, }, - id::ClientId, - proof::{ - AcknowledgementPath, ChannelEndPath, ClientConsensusStatePath, ClientStatePath, - CommitmentPath, ConnectionPath, IbcPath, + traits::{ + Chain, ChainOf, ClientState, ClientStateOf, ConsensusStateOf, HeaderOf, HeightOf, + LightClientBase, }, - traits::{Chain, ClientState}, - EthAbi, IntoEthAbi, IntoProto, Proto, TryFromProto, TryFromProtoErrorOf, + EthAbi, IntoEthAbi, IntoProto, MaybeRecoverableError, Proto, }; use crate::{ - chain::{ - try_from_relayer_msg, - union::{EthereumMainnet, EthereumMinimal}, - ChainOf, ClientStateOf, ConsensusStateOf, HeaderOf, HeightOf, IbcStateRead, LightClient, - LightClientBase, QueryHeight, + aggregate::{Aggregate, AnyAggregate, LightClientSpecificAggregate}, + data, + data::{ + AcknowledgementProof, ChannelEndProof, ClientConsensusStateProof, ClientStateProof, + CommitmentProof, ConnectionProof, Data, LightClientSpecificData, }, - msg::{ - aggregate::{Aggregate, AnyAggregate, LightClientSpecificAggregate}, - data, - data::{ - AcknowledgementProof, ChannelEndProof, ClientConsensusStateProof, ClientStateProof, - CommitmentProof, ConnectionProof, Data, LightClientSpecificData, - }, - fetch, - fetch::{ - Fetch, FetchStateProof, FetchTrustedClientState, FetchUpdateHeaders, - LightClientSpecificFetch, - }, - identified, - msg::{Msg, MsgUpdateClientData}, - seq, wait, - wait::WaitForTimestamp, - AggregateData, AggregateReceiver, AnyLcMsg, AnyLightClientIdentified, DoAggregate, - Identified, LcMsg, RelayerMsg, + fetch, + fetch::{ + Fetch, FetchStateProof, FetchTrustedClientState, FetchUpdateHeaders, + LightClientSpecificFetch, }, - queue::aggregate_data::{do_aggregate, UseAggregate}, + identified, msg, + msg::{Msg, MsgUpdateClientData}, + seq, + use_aggregate::{do_aggregate, UseAggregate}, + wait, + wait::WaitForTimestamp, + AggregateData, AggregateReceiver, AnyLcMsg, AnyLightClientIdentified, DoAggregate, Identified, + LcMsg, LightClient, RelayerMsg, }; pub const EVM_REVISION_NUMBER: u64 = 0; -/// The solidity light client, tracking the state of the 08-wasm light client on union. -pub struct CometblsMinimal { - chain: Evm, -} - -/// The solidity light client, tracking the state of the 08-wasm light client on union. -pub struct CometblsMainnet { - chain: Evm, -} - fn encode_dynamic_singleton_tuple(t: impl AbiEncode) -> Vec { U256::from(32) .encode() @@ -115,154 +80,6 @@ fn encode_dynamic_singleton_tuple(t: impl AbiEncode) -> Vec { .collect::>() } -pub async fn bind_port(this: &Evm, module_address: Address, port_id: String) { - // HACK: This will pop the top item out of the queue, but binding the port requires the contract owner; - // this will work as long as the first signer in the list is the owner. - this.ibc_handlers - .with(|ibc_handler| async move { - let bind_port_result = ibc_handler.bind_port(port_id, module_address.into()); - - match bind_port_result.send().await { - Ok(ok) => { - ok.await.unwrap().unwrap(); - } - Err(why) => eprintln!("{:?}", why.decode_revert::()), - }; - }) - .await -} - -#[allow(unused_variables)] -pub async fn setup_initial_channel( - this: &Evm, - module_address: Address, - channel_id: String, - port_id: String, - counterparty_port_id: String, -) { - // let signer_middleware = Arc::new(SignerMiddleware::new( - // this.provider.clone(), - // this.wallet.clone(), - // )); - - // let ibc_handler = devnet_ownable_ibc_handler::DevnetOwnableIBCHandler::new( - // this.ibc_handler.address(), - // signer_middleware, - // ); - - // ibc_handler - // .setup_initial_channel( - // "connection-0".into(), - // IbcCoreConnectionV1ConnectionEndData { - // client_id: "cometbls-new-0".into(), - // versions: vec![IbcCoreConnectionV1VersionData { - // identifier: "1".into(), - // features: vec!["ORDER_ORDERED".into(), "ORDER_UNORDERED".into()], - // }], - // state: 3, - // counterparty: IbcCoreConnectionV1CounterpartyData { - // client_id: "08-wasm-0".into(), - // connection_id: "connection-0".into(), - // prefix: IbcCoreCommitmentV1MerklePrefixData { - // key_prefix: b"ibc".to_vec().into(), - // }, - // }, - // delay_period: 6, - // }, - // port_id, - // channel_id.clone(), - // IbcCoreChannelV1ChannelData { - // state: 3, - // ordering: 1, - // counterparty: IbcCoreChannelV1CounterpartyData { - // port_id: counterparty_port_id, - // channel_id, - // }, - // connection_hops: vec!["connection-0".into()], - // version: "ics20-1".into(), - // }, - // module_address.into(), - // ) - // .send() - // .await - // .unwrap() - // .await - // .unwrap() - // .unwrap(); - todo!() -} - -impl LightClientBase for CometblsMainnet { - type HostChain = Evm; - type Counterparty = EthereumMainnet; - - type ClientId = ClientId; - type ClientType = String; - - type Config = CometblsConfig; - - fn chain(&self) -> &Self::HostChain { - &self.chain - } - - fn from_chain(chain: Self::HostChain) -> Self { - Self { chain } - } - - fn channel( - &self, - channel_id: unionlabs::id::ChannelId, - port_id: unionlabs::id::PortId, - at: HeightOf, - ) -> impl Future + '_ { - async move { - let execution_block_number = self.chain.execution_height(at).await; - - read_ibc_state::, _, _>( - &self.chain, - ChannelEndPath { - port_id, - channel_id, - }, - execution_block_number, - ) - .await - } - } - - fn connection( - &self, - connection_id: unionlabs::id::ConnectionId, - at: HeightOf, - ) -> impl Future< - Output = ConnectionEnd< - Self::ClientId, - ::ClientId, - String, - >, - > + '_ { - async move { - let execution_block_number = self.chain.execution_height(at).await; - - read_ibc_state::, _, _>( - &self.chain, - ConnectionPath { connection_id }, - execution_block_number, - ) - .await - } - } - - fn query_client_state( - &self, - client_id: ::ClientId, - height: HeightOf, - ) -> impl Future::HostChain>> + '_ - { - query_client_state(&self.chain, client_id, height) - } -} - impl LightClient for CometblsMainnet { type BaseCounterparty = Self::Counterparty; @@ -274,7 +91,7 @@ impl LightClient for CometblsMainnet { fn proof(&self, msg: FetchStateProof) -> RelayerMsg { fetch( - self.chain.chain_id(), + self.chain().chain_id(), LightClientSpecificFetch::(CometblsFetchMsg::FetchGetProof(GetProof { path: msg.path, height: msg.at, @@ -283,89 +100,18 @@ impl LightClient for CometblsMainnet { } fn msg(&self, msg: Msg) -> impl Future> + '_ { - self::msg(&self.chain, msg) + do_msg(self.chain(), msg) } fn do_fetch(&self, msg: Self::Fetch) -> impl Future> + '_ { - do_fetch::<_, Self>(&self.chain, msg) + do_fetch::<_, Self>(self.chain(), msg) } fn generate_counterparty_updates( &self, update_info: FetchUpdateHeaders, ) -> Vec { - generate_counterparty_updates::<_, Self>(&self.chain, update_info) - } -} - -impl LightClientBase for CometblsMinimal { - type HostChain = Evm; - type Counterparty = EthereumMinimal; - - type ClientId = ClientId; - type ClientType = String; - - type Config = CometblsConfig; - - fn chain(&self) -> &Self::HostChain { - &self.chain - } - - fn from_chain(chain: Self::HostChain) -> Self { - Self { chain } - } - - fn channel( - &self, - channel_id: unionlabs::id::ChannelId, - port_id: unionlabs::id::PortId, - at: HeightOf, - ) -> impl Future + '_ { - async move { - let execution_block_number = self.chain.execution_height(at).await; - - read_ibc_state::, _, _>( - &self.chain, - ChannelEndPath { - port_id, - channel_id, - }, - execution_block_number, - ) - .await - } - } - - fn connection( - &self, - connection_id: unionlabs::id::ConnectionId, - at: HeightOf, - ) -> impl Future< - Output = ConnectionEnd< - Self::ClientId, - ::ClientId, - String, - >, - > + '_ { - async move { - let execution_block_number = self.chain.execution_height(at).await; - - read_ibc_state::, _, _>( - &self.chain, - ConnectionPath { connection_id }, - execution_block_number, - ) - .await - } - } - - fn query_client_state( - &self, - client_id: ::ClientId, - height: HeightOf, - ) -> impl Future::HostChain>> + '_ - { - query_client_state(&self.chain, client_id, height) + generate_counterparty_updates::<_, Self>(self.chain(), update_info) } } @@ -380,7 +126,7 @@ impl LightClient for CometblsMinimal { fn proof(&self, msg: FetchStateProof) -> RelayerMsg { fetch( - self.chain.chain_id(), + self.chain().chain_id(), LightClientSpecificFetch::(CometblsFetchMsg::FetchGetProof(GetProof { path: msg.path, height: msg.at, @@ -389,44 +135,21 @@ impl LightClient for CometblsMinimal { } fn msg(&self, msg: Msg) -> impl Future> + '_ { - self::msg(&self.chain, msg) + do_msg(self.chain(), msg) } fn do_fetch(&self, msg: Self::Fetch) -> impl Future> + '_ { - do_fetch::<_, Self>(&self.chain, msg) + do_fetch::<_, Self>(self.chain(), msg) } fn generate_counterparty_updates( &self, update_info: FetchUpdateHeaders, ) -> Vec { - generate_counterparty_updates::<_, Self>(&self.chain, update_info) + generate_counterparty_updates::<_, Self>(self.chain(), update_info) } } -async fn read_ibc_state( - evm: &Evm, - p: P, - at: u64, -) -> P::Output -where - Counterparty: Chain, - C: ChainSpec, - P: IbcPath, Counterparty> - + EthereumStateRead< - C, - Counterparty, - Encoded = <<

>::EthCall as EthCallExt>::Return as TupleToOption>::Inner, - > + 'static, - ::Return: TupleToOption, -{ - evm.read_ibc_state(p.into_eth_call(), at) - .await - .unwrap() - .map(|x| P::decode_ibc_state(x)) - .unwrap() -} - fn generate_counterparty_updates( evm: &Evm, update_info: FetchUpdateHeaders, @@ -918,14 +641,6 @@ where } } -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, Args)] -pub struct CometblsConfig { - #[arg(long)] - pub client_type: String, - #[arg(long)] - pub cometbls_client_address: Address, -} - // async fn create_update( // &self, // currently_trusted_slot: u64, @@ -1079,7 +794,7 @@ fn sync_committee_period, C: ChainSpec>(height: H) -> u64 { height.into().div(C::PERIOD::U64) } -async fn msg(evm: &Evm, msg: Msg) -> Result<(), TxSubmitError> +async fn do_msg(evm: &Evm, msg: Msg) -> Result<(), TxSubmitError> where C: ChainSpec, L: LightClient, Config = CometblsConfig>, @@ -1242,29 +957,6 @@ fn mk_function_call( .expect("method selector is generated; qed;") } -async fn query_client_state( - evm: &Evm, - client_id: chain_utils::evm::EvmClientId, - height: Height, -) -> Any< - wasm::client_state::ClientState< - unionlabs::ibc::lightclients::cometbls::client_state::ClientState, - >, -> { - let execution_height = evm.execution_height(height).await; - - let (client_state_bytes, is_found) = evm - .readonly_ibc_handler - .get_client_state(client_id.to_string()) - .block(execution_height) - .await - .unwrap(); - - assert!(is_found); - - Any::try_from_proto_bytes(&client_state_bytes).unwrap() -} - async fn do_fetch(evm: &Evm, msg: CometblsFetchMsg) -> Vec where C: ChainSpec, @@ -1491,78 +1183,12 @@ where [data::(evm.chain_id, LightClientSpecificData::from(msg))].into() } -impl IbcStateRead for Evm -where - Counterparty: Chain, - C: ChainSpec, - P: IbcPath, Counterparty> - + EthereumStateRead< - C, - Counterparty, - Encoded = <<

>::EthCall as EthCallExt>::Return as TupleToOption>::Inner, - > + 'static, - ::Return: TupleToOption, -{ - fn state_proof( - &self, - path: P, - at: Height, - ) -> impl Future> + '_ { - async move { - let execution_height = self.execution_height(at).await; - - let path = path.to_string(); - - let location = keccak256( - keccak256(path.as_bytes()) - .into_iter() - .chain(U256::from(0).encode()) - .collect::>(), - ); - - let proof = self - .provider - .get_proof( - self.readonly_ibc_handler.address(), - vec![location.into()], - Some(execution_height.into()), - ) - .await - .unwrap(); - - tracing::info!(?proof); - - let proof = match <[_; 1]>::try_from(proof.storage_proof) { - Ok([proof]) => proof, - Err(invalid) => { - panic!("received invalid response from eth_getProof, expected length of 1 but got `{invalid:#?}`"); - } - }; - - ethereum_v1::StorageProof { - proofs: [ethereum_v1::Proof { - key: proof.key.to_fixed_bytes().to_vec(), - // REVIEW(benluelo): Make sure this encoding works - value: proof.value.encode(), - proof: proof - .proof - .into_iter() - .map(|bytes| bytes.to_vec()) - .collect(), - }] - .to_vec(), - } - .encode_to_vec() - } - } -} - #[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, serde::Serialize, serde::Deserialize)] #[serde(bound(serialize = "", deserialize = ""))] pub struct GetProof>> { path: unionlabs::proof::Path< ::ClientId, - as Chain>::Height, + HeightOf>, >, height: as Chain>::Height, } @@ -1573,136 +1199,6 @@ pub struct GetProof>> { // type _AnyGetProof = Path2>; -trait EthereumStateRead: IbcPath, Counterparty> -where - Counterparty: Chain, - C: ChainSpec, -{ - /// The type of the encoded state returned from the contract. This may be bytes (see client state) - /// or a type (see connection end) - /// Since solidity doesn't support generics, it emulates generics by using bytes in interfaces and - /// "downcasting" (via parsing) to expected types in implementations. - type Encoded; - - type EthCall: EthCallExt + 'static; - - fn into_eth_call(self) -> Self::EthCall; - - fn decode_ibc_state(encoded: Self::Encoded) -> Self::Output; -} - -impl EthereumStateRead - for ClientStatePath< as Chain>::ClientId> -where - ClientStateOf: TryFromProto, - TryFromProtoErrorOf>: Debug, -{ - type Encoded = Vec; - - type EthCall = GetClientStateCall; - - fn into_eth_call(self) -> Self::EthCall { - Self::EthCall { - client_id: self.client_id.to_string(), - } - } - - fn decode_ibc_state(encoded: Self::Encoded) -> Self::Output { - TryFromProto::try_from_proto_bytes(&encoded).unwrap() - } -} - -impl EthereumStateRead - for ClientConsensusStatePath< as Chain>::ClientId, ::Height> -where - ConsensusStateOf: TryFromProto, - TryFromProtoErrorOf>: Debug, -{ - type Encoded = Vec; - - type EthCall = GetConsensusStateCall; - - fn into_eth_call(self) -> Self::EthCall { - Self::EthCall { - client_id: self.client_id.to_string(), - height: self.height.into_height().into(), - } - } - - fn decode_ibc_state(encoded: Self::Encoded) -> Self::Output { - TryFromProto::try_from_proto_bytes(&encoded).unwrap() - } -} - -impl EthereumStateRead for ConnectionPath { - type Encoded = IbcCoreConnectionV1ConnectionEndData; - - type EthCall = GetConnectionCall; - - fn into_eth_call(self) -> Self::EthCall { - Self::EthCall { - connection_id: self.connection_id.to_string(), - } - } - - fn decode_ibc_state(encoded: Self::Encoded) -> Self::Output { - encoded.try_into().unwrap() - } -} - -impl EthereumStateRead for ChannelEndPath { - type Encoded = IbcCoreChannelV1ChannelData; - - type EthCall = GetChannelCall; - - fn into_eth_call(self) -> Self::EthCall { - Self::EthCall { - port_id: self.port_id.to_string(), - channel_id: self.channel_id.to_string(), - } - } - - fn decode_ibc_state(encoded: Self::Encoded) -> Self::Output { - encoded.try_into().unwrap() - } -} - -impl EthereumStateRead for CommitmentPath { - type Encoded = [u8; 32]; - - type EthCall = GetHashedPacketCommitmentCall; - - fn into_eth_call(self) -> Self::EthCall { - Self::EthCall { - port_id: self.port_id.to_string(), - channel_id: self.channel_id.to_string(), - sequence: self.sequence, - } - } - - fn decode_ibc_state(encoded: Self::Encoded) -> Self::Output { - encoded.into() - } -} - -impl EthereumStateRead for AcknowledgementPath { - type Encoded = [u8; 32]; - - type EthCall = GetHashedPacketAcknowledgementCommitmentCall; - - fn into_eth_call(self) -> Self::EthCall { - Self::EthCall { - port_id: self.port_id.to_string(), - channel_id: self.channel_id.to_string(), - sequence: self.sequence, - } - } - - fn decode_ibc_state(encoded: Self::Encoded) -> Self::Output { - encoded.into() - } -} - // fn decode_log(logs: impl IntoIterator>) -> T { // let t = decode_logs::(&logs.into_iter().map(Into::into).collect::>()).unwrap(); @@ -1819,7 +1315,7 @@ where __marker: PhantomData, }, ), - crate::msg::msg::( + msg::( req.counterparty_chain_id, MsgUpdateClientData { msg: MsgUpdateClient { @@ -1871,9 +1367,9 @@ where let trusted_period = sync_committee_period::<_, C>(req.update_from.revision_height); assert!( - trusted_period <= target_period, - "trusted period {trusted_period} is behind target period {target_period}, something is wrong!", - ); + trusted_period <= target_period, + "trusted period {trusted_period} is behind target period {target_period}, something is wrong!", + ); // Eth chain is more than 1 signature period ahead of us. We need to do sync committee // updates until we reach the `target_period - 1`. @@ -2006,7 +1502,7 @@ where .chain([fetch::( chain_id, FetchTrustedClientState { - at: QueryHeight::Specific(Height { + at: unionlabs::QueryHeight::Specific(Height { revision_number: EVM_REVISION_NUMBER, revision_height: (!does_not_have_finality_update) .then_some(finality_update_attested_header_slot) diff --git a/voyager/src/chain/union.rs b/voyager/voyager-message/src/lightclient_impls/ethereum.rs similarity index 78% rename from voyager/src/chain/union.rs rename to voyager/voyager-message/src/lightclient_impls/ethereum.rs index d951c78e0f..af169b50ab 100644 --- a/voyager/src/chain/union.rs +++ b/voyager/voyager-message/src/lightclient_impls/ethereum.rs @@ -8,10 +8,10 @@ use chain_utils::{ evm::Evm, union::{BroadcastTxCommitError, Union}, }; -use clap::Args; use frame_support_procedural::{CloneNoBound, DebugNoBound, PartialEqNoBound}; use frunk::{hlist_pat, HList}; use futures::Future; +use lightclient::ethereum::{EthereumConfig, EthereumMainnet, EthereumMinimal}; use num_bigint::BigUint; use prost::Message; use protos::{ @@ -26,17 +26,9 @@ use unionlabs::{ ethereum_consts_traits::{ChainSpec, Mainnet, Minimal}, google::protobuf::{any::Any, timestamp::Timestamp}, ibc::{ - core::{ - client::{height::Height, msg_update_client::MsgUpdateClient}, - connection::connection_end::ConnectionEnd, - }, + core::client::{height::Height, msg_update_client::MsgUpdateClient}, lightclients::{cometbls, ethereum, wasm}, }, - id::ClientId, - proof::{ - AcknowledgementPath, ChannelEndPath, ClientConsensusStatePath, ClientStatePath, - CommitmentPath, ConnectionPath, IbcPath, - }, tendermint::{ crypto::public_key::PublicKey, types::{ @@ -47,117 +39,35 @@ use unionlabs::{ signed_msg_type::SignedMsgType, simple_validator::SimpleValidator, }, }, + traits::{Chain, HeightOf, LightClientBase}, union::galois::{ prove_request::ProveRequest, prove_response, validator_set_commit::ValidatorSetCommit, }, - IntoProto, MsgIntoProto, Proto, TryFromProto, TryFromProtoErrorOf, + IntoProto, MsgIntoProto, QueryHeight, }; use crate::{ - chain::{ - evm::{CometblsMainnet, CometblsMinimal}, - try_from_relayer_msg, Chain, ChainOf, ClientStateOf, ConsensusStateOf, HeightOf, - IbcStateRead, LightClient, LightClientBase, QueryHeight, + aggregate::{Aggregate, LightClientSpecificAggregate}, + data, + data::{ + AcknowledgementProof, ChannelEndProof, ClientConsensusStateProof, ClientStateProof, + CommitmentProof, ConnectionProof, Data, LightClientSpecificData, }, - msg::{ - aggregate::{Aggregate, LightClientSpecificAggregate}, - data, - data::{ - AcknowledgementProof, ChannelEndProof, ClientConsensusStateProof, ClientStateProof, - CommitmentProof, ConnectionProof, Data, LightClientSpecificData, - }, - defer_relative, fetch, - fetch::{ - Fetch, FetchStateProof, FetchTrustedClientState, FetchUpdateHeaders, - LightClientSpecificFetch, - }, - identified, - msg::{Msg, MsgUpdateClientData}, - seq, wait, - wait::WaitForBlock, - AggregateData, AggregateReceiver, AnyLcMsg, AnyLightClientIdentified, DoAggregate, - Identified, LcMsg, PathOf, RelayerMsg, + defer_relative, fetch, + fetch::{ + Fetch, FetchStateProof, FetchTrustedClientState, FetchUpdateHeaders, + LightClientSpecificFetch, }, - queue::aggregate_data::{do_aggregate, UseAggregate}, + identified, msg, + msg::{Msg, MsgUpdateClientData}, + seq, + use_aggregate::{do_aggregate, UseAggregate}, + wait, + wait::WaitForBlock, + AggregateData, AggregateReceiver, AnyLcMsg, AnyLightClientIdentified, DoAggregate, Identified, + LcMsg, LightClient, PathOf, RelayerMsg, }; -/// The 08-wasm light client tracking ethereum, running on the union chain. -pub struct EthereumMinimal { - chain: ::HostChain, -} - -/// The 08-wasm light client tracking ethereum, running on the union chain. -pub struct EthereumMainnet { - chain: ::HostChain, -} - -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, Args)] -pub struct EthereumConfig { - #[arg(long)] - pub code_id: H256, -} - -impl LightClientBase for EthereumMinimal { - type HostChain = Union; - type Counterparty = CometblsMinimal; - - type ClientId = ClientId; - type ClientType = String; - - type Config = EthereumConfig; - - fn chain(&self) -> &Self::HostChain { - &self.chain - } - - fn from_chain(chain: Self::HostChain) -> Self { - Self { chain } - } - - fn query_client_state( - &self, - client_id: ::ClientId, - height: HeightOf, - ) -> impl Future::HostChain>> + '_ - { - query_client_state::(&self.chain, client_id, height) - } - - fn channel( - &self, - channel_id: unionlabs::id::ChannelId, - port_id: unionlabs::id::PortId, - at: HeightOf, - ) -> impl Future + '_ { - read_ibc_state::, _>( - &self.chain, - ChannelEndPath { - port_id, - channel_id, - }, - at, - ) - } - - fn connection( - &self, - connection_id: unionlabs::id::ConnectionId, - at: HeightOf, - ) -> impl Future< - Output = ConnectionEnd< - Self::ClientId, - ::ClientId, - String, - >, - > + '_ { - read_ibc_state::, _>( - &self.chain, - ConnectionPath { connection_id }, - at, - ) - } -} - impl LightClient for EthereumMinimal { type BaseCounterparty = Self::Counterparty; @@ -169,9 +79,9 @@ impl LightClient for EthereumMinimal { fn proof(&self, msg: FetchStateProof) -> RelayerMsg { seq([ - wait::(self.chain.chain_id(), WaitForBlock(msg.at.increment())), + wait::(self.chain().chain_id(), WaitForBlock(msg.at.increment())), fetch::( - self.chain.chain_id(), + self.chain().chain_id(), LightClientSpecificFetch(EthereumFetchMsg::AbciQuery(FetchAbciQuery { path: msg.path, height: msg.at, @@ -181,79 +91,18 @@ impl LightClient for EthereumMinimal { } fn msg(&self, msg: Msg) -> impl Future> + '_ { - self::msg::(self.chain.clone(), msg) + do_msg::(self.chain().clone(), msg) } fn do_fetch(&self, msg: Self::Fetch) -> impl Future> + '_ { - do_fetch::(&self.chain, msg) + do_fetch::(self.chain(), msg) } fn generate_counterparty_updates( &self, update_info: FetchUpdateHeaders, ) -> Vec { - generate_counterparty_updates::<_, Self>(&self.chain, update_info) - } -} - -impl LightClientBase for EthereumMainnet { - type HostChain = Union; - type Counterparty = CometblsMainnet; - - type ClientId = ClientId; - type ClientType = String; - - type Config = EthereumConfig; - - fn chain(&self) -> &Self::HostChain { - &self.chain - } - - fn from_chain(chain: Self::HostChain) -> Self { - Self { chain } - } - - fn query_client_state( - &self, - client_id: ::ClientId, - height: HeightOf, - ) -> impl Future::HostChain>> + '_ - { - query_client_state::(&self.chain, client_id, height) - } - - fn channel( - &self, - channel_id: unionlabs::id::ChannelId, - port_id: unionlabs::id::PortId, - at: HeightOf, - ) -> impl Future + '_ { - read_ibc_state::, _>( - &self.chain, - ChannelEndPath { - port_id, - channel_id, - }, - at, - ) - } - - fn connection( - &self, - connection_id: unionlabs::id::ConnectionId, - at: HeightOf, - ) -> impl Future< - Output = ConnectionEnd< - Self::ClientId, - ::ClientId, - String, - >, - > + '_ { - read_ibc_state::, _>( - &self.chain, - ConnectionPath { connection_id }, - at, - ) + generate_counterparty_updates::<_, Self>(self.chain(), update_info) } } @@ -268,9 +117,9 @@ impl LightClient for EthereumMainnet { fn proof(&self, msg: FetchStateProof) -> RelayerMsg { seq([ - wait::(self.chain.chain_id(), WaitForBlock(msg.at.increment())), + wait::(self.chain().chain_id(), WaitForBlock(msg.at.increment())), fetch::( - self.chain.chain_id(), + self.chain().chain_id(), LightClientSpecificFetch(EthereumFetchMsg::AbciQuery(FetchAbciQuery { path: msg.path, height: msg.at, @@ -280,18 +129,18 @@ impl LightClient for EthereumMainnet { } fn msg(&self, msg: Msg) -> impl Future> + '_ { - self::msg(self.chain.clone(), msg) + do_msg(self.chain().clone(), msg) } fn do_fetch(&self, msg: Self::Fetch) -> impl Future> + '_ { - do_fetch::<_, Self>(&self.chain, msg) + do_fetch::<_, Self>(self.chain(), msg) } fn generate_counterparty_updates( &self, update_info: FetchUpdateHeaders, ) -> Vec { - generate_counterparty_updates::<_, Self>(&self.chain, update_info) + generate_counterparty_updates::<_, Self>(self.chain(), update_info) } } @@ -525,13 +374,7 @@ where // a proof and we have to wait for the prover to be done (max 40s as of writing this). Err(err) if err.message() == "Busy building" => [seq([ defer_relative(20), - RelayerMsg::Lc( - Identified::new( - union.chain_id.clone(), - LcMsg::Fetch(Fetch::LightClientSpecific(LightClientSpecificFetch(msg))), - ) - .into(), - ), + fetch::(union.chain_id.clone(), LightClientSpecificFetch(msg)), ])] .into(), Err(err) => { @@ -979,7 +822,7 @@ where // pub update_to: HeightOf>, } -async fn msg(union: Union, msg: Msg) -> Result<(), BroadcastTxCommitError> +async fn do_msg(union: Union, msg: Msg) -> Result<(), BroadcastTxCommitError> where L: LightClient, ::HostChain: Chain< @@ -1040,179 +883,6 @@ where .await } -async fn query_client_state( - union: &Union, - client_id: chain_utils::union::UnionClientId, - height: Height, -) -> ClientStateOf<::HostChain> -where - L: LightClient, - ClientStateOf<::HostChain>: Proto - + TryFrom - + TryFromProto, - // NOTE: This bound can be removed once we don't unwrap anymore - TryFromProtoErrorOf::HostChain>>: Debug, - <::HostChain as Chain>::SelfClientState: Proto - + TryFrom - + TryFromProto, -{ - let mut client = - protos::cosmos::base::tendermint::v1beta1::service_client::ServiceClient::connect( - union.grpc_url.clone(), - ) - .await - .unwrap(); - - ::HostChain>>::try_from_proto_bytes( - &client - .abci_query(AbciQueryRequest { - data: ClientStatePath { client_id }.to_string().into_bytes(), - path: "store/ibc/key".to_string(), - height: height.revision_height.try_into().unwrap(), - prove: false, - }) - .await - .unwrap() - .into_inner() - .value, - ) - .unwrap() -} - -// IbcStateRead stuff - -trait AbciStateRead: IbcPath -where - Counterparty: Chain, -{ - fn from_abci_bytes(bytes: Vec) -> Self::Output; -} - -impl AbciStateRead for ClientStatePath<::ClientId> -where - Counterparty: Chain, - ClientStateOf: TryFromProto, - TryFromProtoErrorOf>: Debug, -{ - fn from_abci_bytes(bytes: Vec) -> Self::Output { - Self::Output::try_from_proto_bytes(&bytes).unwrap() - } -} - -impl AbciStateRead - for ClientConsensusStatePath<::ClientId, ::Height> -where - Counterparty: Chain, - ConsensusStateOf: TryFromProto, - TryFromProtoErrorOf>: Debug, -{ - fn from_abci_bytes(bytes: Vec) -> Self::Output { - Self::Output::try_from_proto_bytes(&bytes).unwrap() - } -} - -impl AbciStateRead for ConnectionPath -where - Counterparty: Chain, - // ::ClientId: ClientId, - // Self::Output: Proto + TryFrom, -{ - fn from_abci_bytes(bytes: Vec) -> Self::Output { - Self::Output::try_from_proto_bytes(&bytes).unwrap() - } -} - -impl AbciStateRead for ChannelEndPath -where - Counterparty: Chain, -{ - fn from_abci_bytes(bytes: Vec) -> Self::Output { - Self::Output::try_from_proto_bytes(&bytes).unwrap() - } -} - -impl AbciStateRead for CommitmentPath -where - Counterparty: Chain, -{ - fn from_abci_bytes(bytes: Vec) -> Self::Output { - bytes.try_into().unwrap() - } -} - -impl AbciStateRead for AcknowledgementPath -where - Counterparty: Chain, -{ - fn from_abci_bytes(bytes: Vec) -> Self::Output { - bytes.try_into().unwrap() - } -} - -impl IbcStateRead for Union -where - Counterparty: Chain, - ClientStateOf: TryFromProto, - ConsensusStateOf: TryFromProto, - P: IbcPath + AbciStateRead + 'static, -{ - fn state_proof(&self, path: P, at: Height) -> impl Future> + '_ { - async move { - tracing::info!(%path, %at, "fetching state proof"); - - let mut client = - protos::cosmos::base::tendermint::v1beta1::service_client::ServiceClient::connect( - self.grpc_url.clone(), - ) - .await - .unwrap(); - - let query_result = client - .abci_query(AbciQueryRequest { - data: path.to_string().into_bytes(), - path: "store/ibc/key".to_string(), - height: i64::try_from(at.revision_height).unwrap(), - prove: true, - }) - .await - .unwrap() - .into_inner(); - - let _state = P::from_abci_bytes(query_result.value); - - todo!() - } - } -} - -async fn read_ibc_state(union: &Union, path: P, at: HeightOf) -> P::Output -where - Counterparty: Chain, - ClientStateOf: TryFromProto, - ConsensusStateOf: TryFromProto, - P: IbcPath + AbciStateRead + 'static, -{ - let mut client = - protos::cosmos::base::tendermint::v1beta1::service_client::ServiceClient::connect( - union.grpc_url.clone(), - ) - .await - .unwrap(); - - let query_result = client - .abci_query(AbciQueryRequest { - data: path.to_string().into_bytes(), - path: "store/ibc/key".to_string(), - height: i64::try_from(at.revision_height).unwrap(), - prove: false, - }) - .await - .unwrap() - .into_inner(); - - P::from_abci_bytes(query_result.value) -} - fn tendermint_hash_to_h256(hash: tendermint::Hash) -> H256 { match hash { tendermint::Hash::Sha256(hash) => hash.into(), @@ -1449,7 +1119,7 @@ where assert_eq!(chain_id, untrusted_commit_chain_id); seq([ - crate::msg::msg::( + msg::( req.counterparty_chain_id, MsgUpdateClientData { msg: MsgUpdateClient { @@ -1463,13 +1133,14 @@ where update_from: req.update_from, }, ), + // REVIEW: Is this required anymore, now that all messages require externally-queued updates? fetch::( chain_id, - Fetch::TrustedClientState(FetchTrustedClientState { + FetchTrustedClientState { // NOTE: We can pass update_to directly here since cosmos -> evm always updates to the exact height requested. at: QueryHeight::Specific(req.update_to), client_id: req.client_id, - }), + }, ), ]) } diff --git a/voyager/src/msg/msg.rs b/voyager/voyager-message/src/msg.rs similarity index 86% rename from voyager/src/msg/msg.rs rename to voyager/voyager-message/src/msg.rs index 7ec64f4936..2f92cb9026 100644 --- a/voyager/src/msg/msg.rs +++ b/voyager/voyager-message/src/msg.rs @@ -2,27 +2,26 @@ use std::{fmt::Display, marker::PhantomData}; use frame_support_procedural::{CloneNoBound, DebugNoBound, PartialEqNoBound}; use serde::{Deserialize, Serialize}; -use unionlabs::ibc::core::{ - channel::{ - msg_acknowledgement::MsgAcknowledgement, msg_channel_open_ack::MsgChannelOpenAck, - msg_channel_open_confirm::MsgChannelOpenConfirm, msg_channel_open_init::MsgChannelOpenInit, - msg_channel_open_try::MsgChannelOpenTry, msg_recv_packet::MsgRecvPacket, - }, - client::{msg_create_client::MsgCreateClient, msg_update_client::MsgUpdateClient}, - connection::{ - msg_connection_open_ack::MsgConnectionOpenAck, - msg_connection_open_confirm::MsgConnectionOpenConfirm, - msg_connection_open_init::MsgConnectionOpenInit, - msg_connection_open_try::MsgConnectionOpenTry, +use unionlabs::{ + ibc::core::{ + channel::{ + msg_acknowledgement::MsgAcknowledgement, msg_channel_open_ack::MsgChannelOpenAck, + msg_channel_open_confirm::MsgChannelOpenConfirm, + msg_channel_open_init::MsgChannelOpenInit, msg_channel_open_try::MsgChannelOpenTry, + msg_recv_packet::MsgRecvPacket, + }, + client::{msg_create_client::MsgCreateClient, msg_update_client::MsgUpdateClient}, + connection::{ + msg_connection_open_ack::MsgConnectionOpenAck, + msg_connection_open_confirm::MsgConnectionOpenConfirm, + msg_connection_open_init::MsgConnectionOpenInit, + msg_connection_open_try::MsgConnectionOpenTry, + }, }, + traits::{ChainOf, ClientStateOf, ConsensusStateOf, HeaderOf, HeightOf, LightClientBase}, }; -use crate::{ - chain::{ - ChainOf, ClientStateOf, ConsensusStateOf, HeaderOf, HeightOf, LightClient, LightClientBase, - }, - msg::any_enum, -}; +use crate::{any_enum, LightClient}; any_enum! { /// Defines messages that are sent *to* the lightclient `L`. diff --git a/voyager/voyager-message/src/use_aggregate.rs b/voyager/voyager-message/src/use_aggregate.rs new file mode 100644 index 0000000000..fd52fd70cc --- /dev/null +++ b/voyager/voyager-message/src/use_aggregate.rs @@ -0,0 +1,157 @@ +use std::{collections::VecDeque, fmt::Display, ops::ControlFlow}; + +use frunk::{HCons, HNil}; + +use crate::{data::AnyData, AnyLightClientIdentified, LightClient, RelayerMsg}; + +pub trait IsAggregateData = TryFrom, Error = AnyLightClientIdentified> + + Into>; + +pub fn do_aggregate>( + event: T, + data: VecDeque>, +) -> RelayerMsg { + // let data_json = serde_json::to_string(&data).expect("serialization should not fail"); + + // tracing::info!(%data_json, "aggregating data"); + + let data = match HListTryFromIterator::try_from_iter(data) { + Ok(ok) => ok, + Err(_) => { + panic!( + "could not aggregate data into {}", + std::any::type_name::() + ) + } + }; + T::aggregate(event, data) +} + +pub(crate) fn pluck, U>( + mut vec: VecDeque, +) -> ControlFlow<(VecDeque, T), VecDeque> { + let mut new_vec = VecDeque::new(); + + while let Some(data) = vec.pop_front() { + match T::try_from(data) { + Ok(t) => { + new_vec.extend(vec); + return ControlFlow::Break((new_vec, t)); + } + Err(value) => new_vec.push_back(value), + } + } + + ControlFlow::Continue(new_vec) +} + +// TODO: Figure out how to return the entire source list on error +pub trait HListTryFromIterator: Sized { + fn try_from_iter(vec: VecDeque) -> Result>; +} + +impl HListTryFromIterator for HCons +where + T: TryFrom + Into, + Tail: HListTryFromIterator, + // REVIEW: Should debug be used instead? + U: Display, +{ + fn try_from_iter(vec: VecDeque) -> Result> { + match pluck::(vec) { + ControlFlow::Continue(invalid) => { + let invalid_str = invalid + .iter() + .map(|x| x.to_string()) + .collect::>() + .join(", "); + + tracing::error!( + %invalid_str, + "type didn't match" + ); + + Err(invalid) + } + ControlFlow::Break((vec, u)) => Ok(HCons { + head: u, + tail: Tail::try_from_iter(vec)?, + }), + } + } +} + +impl HListTryFromIterator for HNil { + fn try_from_iter(vec: VecDeque) -> Result> { + if vec.is_empty() { + Ok(Self) + } else { + Err(vec) + } + } +} + +pub trait UseAggregate { + type AggregatedData: HListTryFromIterator>; + + fn aggregate(this: Self, data: Self::AggregatedData) -> RelayerMsg; +} + +#[cfg(test)] +pub(crate) mod tests { + use frunk::HList; + + use super::*; + use crate::enum_variants_conversions; + + #[test] + fn hlist_try_from_iter() { + enum_variants_conversions! { + #[derive(Debug, PartialEq, derive_more::Display)] + pub enum A { + B(B), + C(C), + D(D), + } + } + + #[derive(Debug, PartialEq, derive_more::Display)] + pub struct B; + + #[derive(Debug, PartialEq, derive_more::Display)] + pub struct C; + + #[derive(Debug, PartialEq, derive_more::Display)] + pub struct D; + + // correct items, correct order + assert_eq!( + ::try_from_iter(VecDeque::from_iter([A::B(B), A::C(C)])), + Ok(frunk::hlist![B, C]) + ); + + // correct items, wrong order + assert_eq!( + ::try_from_iter(VecDeque::from_iter([A::C(C), A::B(B)])), + Ok(frunk::hlist![B, C]) + ); + + // extra items + assert_eq!( + ::try_from_iter(VecDeque::from_iter([A::C(C), A::B(B), A::D(D)])), + Err(VecDeque::from_iter([A::D(D)])) + ); + + // missing items + assert_eq!( + ::try_from_iter(VecDeque::from_iter([A::C(C)])), + Err(VecDeque::from_iter([A::C(C)])) + ); + + // missing items, extra items present + assert_eq!( + ::try_from_iter(VecDeque::from_iter([A::C(C), A::D(D)])), + Err(VecDeque::from_iter([A::C(C), A::D(D)])) + ); + } +} diff --git a/voyager/voyager-message/src/wait.rs b/voyager/voyager-message/src/wait.rs new file mode 100644 index 0000000000..0ae5c57a9c --- /dev/null +++ b/voyager/voyager-message/src/wait.rs @@ -0,0 +1,152 @@ +use std::{fmt::Display, marker::PhantomData}; + +use frame_support_procedural::{CloneNoBound, DebugNoBound, PartialEqNoBound}; +use serde::{Deserialize, Serialize}; +use unionlabs::{ + ibc::core::client::height::IsHeight, + traits::{Chain, ChainIdOf, ChainOf, ClientState, HeightOf, LightClientBase}, + QueryHeight, +}; + +use crate::{ + any_enum, defer, fetch, fetch::FetchTrustedClientState, identified, now, seq, wait, AnyLcMsg, + AnyLightClientIdentified, LcMsg, LightClient, RelayerMsg, +}; + +any_enum! { + /// Defines messages that are sent *to* the lightclient `L`. + #[any = AnyWait] + pub enum Wait { + Block(WaitForBlock), + Timestamp(WaitForTimestamp), + TrustedHeight(WaitForTrustedHeight), + } +} + +impl Wait { + pub async fn handle(self, l: L) -> Vec + where + AnyLightClientIdentified: From)>, + AnyLightClientIdentified: From)>, + { + match self { + Wait::Block(WaitForBlock(height)) => { + let chain_height = l.chain().query_latest_height().await; + + assert_eq!( + chain_height.revision_number(), + height.revision_number(), + "chain_height: {chain_height}, height: {height}", + ); + + if chain_height.revision_height() >= height.revision_height() { + [].into() + } else { + [seq([ + // REVIEW: Defer until `now + chain.block_time()`? Would require a new method on chain + defer(now() + 1), + wait::(l.chain().chain_id(), WaitForBlock(height)), + ])] + .into() + } + } + Wait::Timestamp(WaitForTimestamp { + timestamp, + __marker, + }) => { + let chain_ts = l.chain().query_latest_timestamp().await; + + if chain_ts >= timestamp { + [].into() + } else { + [seq([ + // REVIEW: Defer until `now + chain.block_time()`? Would require a new method on chain + defer(now() + 1), + wait::( + l.chain().chain_id(), + WaitForTimestamp { + timestamp, + __marker, + }, + ), + ])] + .into() + } + } + Wait::TrustedHeight(WaitForTrustedHeight { + client_id, + height, + counterparty_client_id, + counterparty_chain_id, + }) => { + let latest_height = l.chain().query_latest_height_as_destination().await; + let trusted_client_state = l + .query_client_state(client_id.clone().into(), latest_height) + .await; + + if trusted_client_state.height().revision_height() >= height.revision_height() { + tracing::debug!( + "client height reached ({} >= {})", + trusted_client_state.height(), + height + ); + + [fetch::( + counterparty_chain_id, + FetchTrustedClientState { + at: QueryHeight::Specific(trusted_client_state.height()), + client_id: counterparty_client_id.clone(), + }, + )] + .into() + } else { + [seq([ + // REVIEW: Defer until `now + counterparty_chain.block_time()`? Would require a new method on chain + defer(now() + 1), + wait::( + l.chain().chain_id(), + Wait::TrustedHeight(WaitForTrustedHeight { + client_id, + height, + counterparty_client_id, + counterparty_chain_id, + }), + ), + ])] + .into() + } + } + } + } +} + +impl Display for Wait { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Wait::Block(block) => write!(f, "Block({})", block.0), + Wait::Timestamp(ts) => write!(f, "Timestamp({})", ts.timestamp), + Wait::TrustedHeight(th) => write!(f, "TrustedHeight({})", th.height), + } + } +} + +#[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] +#[serde(bound(serialize = "", deserialize = ""))] +pub struct WaitForBlock(pub HeightOf>); + +#[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] +#[serde(bound(serialize = "", deserialize = ""))] +pub struct WaitForTimestamp { + pub timestamp: i64, + #[serde(skip)] + pub __marker: PhantomData, +} + +#[derive(DebugNoBound, CloneNoBound, PartialEqNoBound, Serialize, Deserialize)] +#[serde(bound(serialize = "", deserialize = ""))] +pub struct WaitForTrustedHeight { + pub client_id: L::ClientId, + pub counterparty_client_id: ::ClientId, + pub counterparty_chain_id: ChainIdOf, + pub height: HeightOf>, +}