diff --git a/cosmwasm/ibc-union/core/msg/src/msg.rs b/cosmwasm/ibc-union/core/msg/src/msg.rs index 7698dae1e6..949f156032 100644 --- a/cosmwasm/ibc-union/core/msg/src/msg.rs +++ b/cosmwasm/ibc-union/core/msg/src/msg.rs @@ -51,6 +51,7 @@ pub enum ExecuteMsg { PacketSend(MsgSendPacket), WriteAcknowledgement(MsgWriteAcknowledgement), MigrateState(MsgMigrateState), + CommitClientStatus(MsgCommitClientStatus), } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -244,3 +245,9 @@ pub struct MsgSendPacket { pub timeout_timestamp: Timestamp, pub data: Bytes, } + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct MsgCommitClientStatus { + pub client_id: ClientId, +} diff --git a/cosmwasm/ibc-union/core/msg/src/query.rs b/cosmwasm/ibc-union/core/msg/src/query.rs index 7e7a9c7e4a..0c276596cb 100644 --- a/cosmwasm/ibc-union/core/msg/src/query.rs +++ b/cosmwasm/ibc-union/core/msg/src/query.rs @@ -34,4 +34,6 @@ pub enum QueryMsg { GetClientImpl { client_id: ClientId }, #[cfg_attr(feature = "cw-orch-interface", returns(String))] GetRegisteredClientType { client_type: String }, + #[cfg_attr(feature = "cw-orch-interface", returns(Status))] + GetCommittedClientStatus { client_id: ClientId }, } diff --git a/cosmwasm/ibc-union/core/src/contract.rs b/cosmwasm/ibc-union/core/src/contract.rs index 2b77300229..f2b20d2eab 100644 --- a/cosmwasm/ibc-union/core/src/contract.rs +++ b/cosmwasm/ibc-union/core/src/contract.rs @@ -18,17 +18,19 @@ use ibc_union_msg::{ msg::{ ExecuteMsg, InitMsg, MsgBatchAcks, MsgBatchSend, MsgChannelCloseConfirm, MsgChannelCloseInit, MsgChannelOpenAck, MsgChannelOpenConfirm, MsgChannelOpenInit, - MsgChannelOpenTry, MsgConnectionOpenAck, MsgConnectionOpenConfirm, MsgConnectionOpenInit, - MsgConnectionOpenTry, MsgCreateClient, MsgForceUpdateClient, MsgIntentPacketRecv, - MsgMigrateState, MsgPacketAcknowledgement, MsgPacketRecv, MsgPacketTimeout, - MsgRegisterClient, MsgSendPacket, MsgUpdateClient, MsgWriteAcknowledgement, + MsgChannelOpenTry, MsgCommitClientStatus, MsgConnectionOpenAck, MsgConnectionOpenConfirm, + MsgConnectionOpenInit, MsgConnectionOpenTry, MsgCreateClient, MsgForceUpdateClient, + MsgIntentPacketRecv, MsgMigrateState, MsgPacketAcknowledgement, MsgPacketRecv, + MsgPacketTimeout, MsgRegisterClient, MsgSendPacket, MsgUpdateClient, + MsgWriteAcknowledgement, }, query::QueryMsg, }; use ibc_union_spec::{ path::{ commit_packets, BatchPacketsPath, BatchReceiptsPath, ChannelPath, ClientStatePath, - ConnectionPath, ConsensusStatePath, COMMITMENT_MAGIC, COMMITMENT_MAGIC_ACK, + ClientStatusPath, ConnectionPath, ConsensusStatePath, COMMITMENT_MAGIC, + COMMITMENT_MAGIC_ACK, }, Channel, ChannelId, ChannelState, ClientId, Connection, ConnectionId, ConnectionState, MustBeZero, Packet, Status, Timestamp, @@ -36,7 +38,7 @@ use ibc_union_spec::{ use serde::{de::DeserializeOwned, Deserialize, Serialize}; use unionlabs::{ ethereum::keccak256, - primitives::{Bytes, H256}, + primitives::{Bytes, H256, U256}, }; use crate::{ @@ -624,6 +626,26 @@ pub fn execute( .add_attribute("relayer", relayer), )) } + ExecuteMsg::CommitClientStatus(MsgCommitClientStatus { client_id }) => { + let client_impl = client_impl(deps.as_ref(), client_id)?; + let status = query_light_client::( + deps.as_ref(), + client_impl, + LightClientQuery::GetStatus { client_id }, + )?; + + store_commit( + deps, + &ClientStatusPath { client_id }.key(), + &U256::from(status).to_be_bytes().into(), + ); + + Ok(Response::new().add_event( + Event::new("commit_client_status") + .add_attribute(events::attribute::CLIENT_ID, client_id.to_string()) + .add_attribute("status", status.to_string()), + )) + } } } @@ -2207,6 +2229,13 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result { + let commit = read_commit(deps, &ClientStatusPath { client_id }.key()); + Ok(to_json_binary(&commit.map(|commit| { + Status::try_from(U256::from_be_bytes(*commit.get())) + .expect("invalid client status; impossible") + }))?) + } } } diff --git a/lib/ibc-union-spec/src/datagram.rs b/lib/ibc-union-spec/src/datagram.rs index 9cbe0bbf17..7a916f8f7c 100644 --- a/lib/ibc-union-spec/src/datagram.rs +++ b/lib/ibc-union-spec/src/datagram.rs @@ -34,6 +34,7 @@ pub enum Datagram { IntentPacketRecv(MsgIntentPacketRecv), BatchSend(MsgBatchSend), BatchAcks(MsgBatchAcks), + CommitClientStatus(MsgCommitClientStatus), } impl Datagram { @@ -59,6 +60,7 @@ impl Datagram { Self::IntentPacketRecv(_msg) => todo!(), Self::BatchSend(_msg) => todo!(), Self::BatchAcks(_msg) => todo!(), + Self::CommitClientStatus(_msg) => None, } } @@ -82,6 +84,7 @@ impl Datagram { Self::IntentPacketRecv(_) => "intent_packet_recv", Self::BatchSend(_) => "batch_send", Self::BatchAcks(_) => "batch_acks", + Self::CommitClientStatus(_msg) => "commit_client_status", } } } @@ -316,3 +319,14 @@ pub struct MsgBatchAcks { // TODO: Ensure same length as packets somehow (maybe zip the lists into one field?) pub acks: Vec, } + +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "snake_case", deny_unknown_fields) +)] +pub struct MsgCommitClientStatus { + pub client_id: ClientId, +} diff --git a/lib/ibc-union-spec/src/path.rs b/lib/ibc-union-spec/src/path.rs index 594dd0b532..f1461e33d4 100644 --- a/lib/ibc-union-spec/src/path.rs +++ b/lib/ibc-union-spec/src/path.rs @@ -7,7 +7,7 @@ use voyager_primitives::IbcStorePathKey; use crate::Packet; use crate::{ types::{ChannelId, ClientId, ConnectionId}, - Channel, Connection, IbcUnion, + Channel, Connection, IbcUnion, Status, }; pub const IBC_UNION_COSMWASM_COMMITMENT_PREFIX: [u8; 1] = [0x00]; @@ -34,6 +34,7 @@ pub const CONNECTIONS: U256 = U256::from_limbs([2, 0, 0, 0]); pub const CHANNELS: U256 = U256::from_limbs([3, 0, 0, 0]); pub const PACKETS: U256 = U256::from_limbs([4, 0, 0, 0]); pub const PACKET_ACKS: U256 = U256::from_limbs([5, 0, 0, 0]); +pub const CLIENT_STATUS: U256 = U256::from_limbs([6, 0, 0, 0]); #[cfg(feature = "ethabi")] #[must_use] @@ -60,6 +61,7 @@ pub enum StorePath { Channel(ChannelPath), BatchReceipts(BatchReceiptsPath), BatchPackets(BatchPacketsPath), + ClientStatus(ClientStatusPath), } impl StorePath { @@ -72,6 +74,7 @@ impl StorePath { StorePath::Channel(path) => path.key(), StorePath::BatchReceipts(path) => path.key(), StorePath::BatchPackets(path) => path.key(), + StorePath::ClientStatus(path) => path.key(), } } } @@ -265,3 +268,31 @@ impl IbcStorePathKey for BatchPacketsPath { type Value = H256; } + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "snake_case", deny_unknown_fields) +)] +pub struct ClientStatusPath { + pub client_id: ClientId, +} + +impl ClientStatusPath { + #[must_use] + pub fn key(&self) -> H256 { + Keccak256::new() + .chain_update(CLIENT_STATUS.to_be_bytes()) + .chain_update(U256::from(self.client_id.get()).to_be_bytes()) + .finalize() + .into() + } +} + +impl IbcStorePathKey for ClientStatusPath { + type Spec = IbcUnion; + + type Value = Status; +} diff --git a/lib/ibc-union-spec/src/types.rs b/lib/ibc-union-spec/src/types.rs index 4816421b08..88d3a3e727 100644 --- a/lib/ibc-union-spec/src/types.rs +++ b/lib/ibc-union-spec/src/types.rs @@ -1,6 +1,6 @@ use core::{fmt, num::NonZeroU32}; -use unionlabs::primitives::U256; +use unionlabs::{errors::UnknownEnumVariant, primitives::U256}; pub(crate) mod channel; pub(crate) mod connection; @@ -93,7 +93,7 @@ id!(ClientId); id!(ConnectionId); id!(ChannelId); -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[serde(deny_unknown_fields, rename_all = "snake_case")] pub enum Status { @@ -102,6 +102,25 @@ pub enum Status { Frozen = 3, } +impl From for U256 { + fn from(value: Status) -> Self { + U256::from((value as u8) as u64) + } +} + +impl TryFrom for Status { + type Error = UnknownEnumVariant; + + fn try_from(value: U256) -> Result { + match u64::try_from(value).map_err(|()| UnknownEnumVariant(value))? { + 1 => Ok(Status::Active), + 2 => Ok(Status::Expired), + 3 => Ok(Status::Frozen), + unknown => Err(UnknownEnumVariant(unknown.into())), + } + } +} + impl fmt::Display for Status { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(match self { diff --git a/voyager/modules/state/cosmos-sdk-union/src/main.rs b/voyager/modules/state/cosmos-sdk-union/src/main.rs index 31a586e8e6..38ecb01fa1 100644 --- a/voyager/modules/state/cosmos-sdk-union/src/main.rs +++ b/voyager/modules/state/cosmos-sdk-union/src/main.rs @@ -368,6 +368,29 @@ impl Module { Ok(commitment.flatten()) } + + #[instrument( + skip_all, + fields( + chain_id = %self.chain_id, + %height, + %client_id + ) + )] + async fn query_committed_client_status( + &self, + height: Height, + client_id: ClientId, + ) -> RpcResult> { + let commitment = self + .query_smart::<_, Option>( + &ibc_union_msg::query::QueryMsg::GetCommittedClientStatus { client_id }, + Some(height), + ) + .await?; + + Ok(commitment.flatten()) + } } #[derive(Debug, thiserror::Error)] @@ -526,6 +549,10 @@ impl StateModuleServer for Module { .query_batch_receipts(at, path.batch_hash) .await .map(into_value), + StorePath::ClientStatus(path) => self + .query_committed_client_status(at, path.client_id) + .await + .map(into_value), } } } diff --git a/voyager/modules/state/ethereum/src/main.rs b/voyager/modules/state/ethereum/src/main.rs index a44854f1e6..7fb34adfe3 100644 --- a/voyager/modules/state/ethereum/src/main.rs +++ b/voyager/modules/state/ethereum/src/main.rs @@ -430,6 +430,15 @@ impl Module { } } + #[instrument(skip_all, fields(chain_id = %self.chain_id, %height, %client_id))] + async fn query_committed_client_status( + &self, + height: Height, + client_id: ClientId, + ) -> RpcResult> { + todo!() + } + #[instrument(skip_all, fields(chain_id = %self.chain_id, %channel_id, %packet_hash))] async fn packet_by_packet_hash( &self, @@ -704,6 +713,10 @@ impl StateModuleServer for Module { .query_batch_packets(at, path.batch_hash) .await .map(into_value), + StorePath::ClientStatus(path) => self + .query_committed_client_status(at, path.client_id) + .await + .map(into_value), } } diff --git a/voyager/plugins/transaction/cosmos-sdk/src/main.rs b/voyager/plugins/transaction/cosmos-sdk/src/main.rs index 54d377f7eb..0dd0d12260 100644 --- a/voyager/plugins/transaction/cosmos-sdk/src/main.rs +++ b/voyager/plugins/transaction/cosmos-sdk/src/main.rs @@ -1164,6 +1164,9 @@ fn process_msgs( }) } ibc_union_spec::datagram::Datagram::BatchAcks(_msg_batch_acks) => todo!(), + ibc_union_spec::datagram::Datagram::CommitClientStatus( + _msg_commit_client_status, + ) => todo!(), }, };