diff --git a/cosmwasm/ibc-union/core/msg/src/msg.rs b/cosmwasm/ibc-union/core/msg/src/msg.rs index 7698dae1e6..c5b3d6150a 100644 --- a/cosmwasm/ibc-union/core/msg/src/msg.rs +++ b/cosmwasm/ibc-union/core/msg/src/msg.rs @@ -51,6 +51,13 @@ pub enum ExecuteMsg { PacketSend(MsgSendPacket), WriteAcknowledgement(MsgWriteAcknowledgement), MigrateState(MsgMigrateState), + CommitClientStatus(MsgCommitClientStatus), +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct MsgCommitClientStatus { + pub client_id: ClientId, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] diff --git a/cosmwasm/ibc-union/core/msg/src/query.rs b/cosmwasm/ibc-union/core/msg/src/query.rs index 7e7a9c7e4a..4e74c48057 100644 --- a/cosmwasm/ibc-union/core/msg/src/query.rs +++ b/cosmwasm/ibc-union/core/msg/src/query.rs @@ -18,6 +18,8 @@ pub enum QueryMsg { GetConsensusState { client_id: ClientId, height: u64 }, #[cfg_attr(feature = "cw-orch-interface", returns(crate::lightclient::Status))] GetStatus { client_id: ClientId }, + #[cfg_attr(feature = "cw-orch-interface", returns(crate::lightclient::Status))] + GetCommittedStatus { client_id: ClientId }, #[cfg_attr(feature = "cw-orch-interface", returns(String))] GetClientType { client_id: ClientId }, #[cfg_attr(feature = "cw-orch-interface", returns(ibc_union_spec::Connection))] diff --git a/cosmwasm/ibc-union/core/src/contract.rs b/cosmwasm/ibc-union/core/src/contract.rs index 2b77300229..f831696a38 100644 --- a/cosmwasm/ibc-union/core/src/contract.rs +++ b/cosmwasm/ibc-union/core/src/contract.rs @@ -28,7 +28,8 @@ use ibc_union_msg::{ 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 +37,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 +625,23 @@ pub fn execute( .add_attribute("relayer", relayer), )) } + ExecuteMsg::CommitClientStatus(msg) => { + let status = query_light_client::( + deps.as_ref(), + client_impl(deps.as_ref(), msg.client_id)?, + LightClientQuery::GetStatus { + client_id: msg.client_id, + }, + )?; + let key = ClientStatusPath { + client_id: msg.client_id, + } + .key(); + let value = U256::from(status as u32).to_le_bytes(); + let value_h256 = H256::new(value); + store_commit(deps, &key, &value_h256); + Ok(Response::default()) + } } } @@ -2186,6 +2204,15 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result { + let commit = read_commit(deps, &ClientStatusPath { client_id }.key()) + .ok_or(ContractError::CommittedClientStatusNotFound { client_id })?; + let commit_bytes: [u8; 32] = *commit.get(); + let u256 = U256::from_le_bytes(commit_bytes); + let status = Status::try_from(u256) + .map_err(|err| ContractError::InvalidClientStatusValue { value: err.value })?; + Ok(to_json_binary(&status)?) + } QueryMsg::GetChannels { contract } => { let contract = deps.api.addr_validate(&contract)?; let channels = deps.storage.read::(&contract)?; diff --git a/cosmwasm/ibc-union/core/src/lib.rs b/cosmwasm/ibc-union/core/src/lib.rs index 009e8f7f3c..6336cb757a 100644 --- a/cosmwasm/ibc-union/core/src/lib.rs +++ b/cosmwasm/ibc-union/core/src/lib.rs @@ -158,6 +158,16 @@ pub enum ContractError { OnlyWhitelistedRelayer, #[error("sender is not the relayer admin")] OnlyRelayerAdmin, + #[error( + "{} committed client status not found for client id {client_id}", + ContractErrorKind::from(self) + )] + CommittedClientStatusNotFound { client_id: ClientId }, + #[error( + "{} invalid committed status value: {value}", + ContractErrorKind::from(self) + )] + InvalidClientStatusValue { value: u8 }, } impl ContractErrorKind { diff --git a/lib/ibc-union-spec/src/path.rs b/lib/ibc-union-spec/src/path.rs index 594dd0b532..44eb778dbd 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,30 @@ 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..8d8fac2d11 100644 --- a/lib/ibc-union-spec/src/types.rs +++ b/lib/ibc-union-spec/src/types.rs @@ -65,7 +65,6 @@ macro_rules! id { id.raw().into() } } - impl From for $T { fn from(id: NonZeroU32) -> Self { <$T>::new(id) @@ -96,6 +95,7 @@ id!(ChannelId); #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[serde(deny_unknown_fields, rename_all = "snake_case")] +#[repr(u8)] pub enum Status { Active = 1, Expired = 2, @@ -111,3 +111,30 @@ impl fmt::Display for Status { }) } } + +#[derive(Debug, Clone, PartialEq)] +pub struct UnknownEnumVariant { + pub value: u8, +} + +impl TryFrom for Status { + type Error = UnknownEnumVariant; + + fn try_from(value: u8) -> Result { + match value { + 1 => Ok(Status::Active), + 2 => Ok(Status::Expired), + 3 => Ok(Status::Frozen), + _ => Err(UnknownEnumVariant { value }), + } + } +} + +impl TryFrom for Status { + type Error = UnknownEnumVariant; + + fn try_from(value: U256) -> Result { + let byte = value.to_le_bytes()[0]; + Status::try_from(byte) + } +} diff --git a/voyager/modules/state/cosmos-sdk-union/src/main.rs b/voyager/modules/state/cosmos-sdk-union/src/main.rs index 31a586e8e6..8974865185 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::GetCommittedStatus { 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..baa136447d 100644 --- a/voyager/modules/state/ethereum/src/main.rs +++ b/voyager/modules/state/ethereum/src/main.rs @@ -623,6 +623,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> { + unimplemented!("query_client_status is not implemented yet"); + } } fn mk_windows(mut latest_height: u64, window: u64) -> Vec<(BlockNumberOrTag, BlockNumberOrTag)> { @@ -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), } }