From d798c448a7e822e72e0fb3540accf895026d1b34 Mon Sep 17 00:00:00 2001 From: Michal Nazarewicz Date: Mon, 26 Aug 2024 04:15:15 +0200 Subject: [PATCH] solana-ibc: add support for cf-solana light client --- Cargo.lock | 1 + .../solana-ibc/programs/solana-ibc/Cargo.toml | 1 + .../programs/solana-ibc/src/client_state.rs | 114 ++++++++++++++++++ .../solana-ibc/src/client_state/impls.rs | 12 ++ .../solana-ibc/src/consensus_state.rs | 24 +++- 5 files changed, 148 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d4f02c6f..c97e1595 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5587,6 +5587,7 @@ dependencies = [ "base64 0.21.7", "bytemuck", "cf-guest", + "cf-solana", "derive_more", "guestchain", "hex-literal", diff --git a/solana/solana-ibc/programs/solana-ibc/Cargo.toml b/solana/solana-ibc/programs/solana-ibc/Cargo.toml index 51210ad8..d9c228c6 100644 --- a/solana/solana-ibc/programs/solana-ibc/Cargo.toml +++ b/solana/solana-ibc/programs/solana-ibc/Cargo.toml @@ -43,6 +43,7 @@ uint.workspace = true guestchain.workspace = true cf-guest.workspace = true +cf-solana = { workspace = true, features = ["solana-program", "no-blake3-syscall"] } lib = { workspace = true, features = ["solana-program"] } memory.workspace = true solana-allocator = { workspace = true, optional = true } diff --git a/solana/solana-ibc/programs/solana-ibc/src/client_state.rs b/solana/solana-ibc/programs/solana-ibc/src/client_state.rs index 23572ca5..9b10c32a 100644 --- a/solana/solana-ibc/programs/solana-ibc/src/client_state.rs +++ b/solana/solana-ibc/programs/solana-ibc/src/client_state.rs @@ -14,6 +14,7 @@ type Result = core::result::Result; pub enum AnyClientState { Tendermint(ibc::tm::ClientState), Wasm(ibc::wasm::ClientState), + Rollup(cf_solana::ClientState), #[cfg(any(test, feature = "mocks"))] Mock(ibc::mock::MockClientState), } @@ -26,6 +27,7 @@ impl ibc::Protobuf for AnyClientState {} enum AnyClientStateTag { Tendermint = 0, Wasm = 1, + Rollup = 2, #[cfg(any(test, feature = "mocks"))] Mock = 255, } @@ -37,6 +39,7 @@ impl AnyClientStateTag { match url { AnyClientState::TENDERMINT_TYPE => Some(Self::Tendermint), AnyClientState::WASM_TYPE => Some(Self::Wasm), + AnyClientState::ROLLUP_TYPE => Some(Self::Rollup), #[cfg(any(test, feature = "mocks"))] AnyClientState::MOCK_TYPE => Some(Self::Mock), _ => None, @@ -50,6 +53,9 @@ impl AnyClientState { ibc::tm::TENDERMINT_CLIENT_STATE_TYPE_URL; /// Protobuf type URL for WASM client state used in Any message. const WASM_TYPE: &'static str = ibc::wasm::WASM_CLIENT_STATE_TYPE_URL; + /// Protobuf type URL for Rollup client state used in Any message. + const ROLLUP_TYPE: &'static str = + cf_solana::proto::ClientState::IBC_TYPE_URL; #[cfg(any(test, feature = "mocks"))] /// Protobuf type URL for Mock client state used in Any message. const MOCK_TYPE: &'static str = ibc::mock::MOCK_CLIENT_STATE_TYPE_URL; @@ -78,6 +84,11 @@ impl AnyClientState { Self::WASM_TYPE, Protobuf::::encode_vec(state), ), + Self::Rollup(state) => ( + AnyClientStateTag::Rollup, + Self::ROLLUP_TYPE, + Protobuf::::encode_vec(state), + ), #[cfg(any(test, feature = "mocks"))] Self::Mock(state) => ( AnyClientStateTag::Mock, @@ -103,6 +114,11 @@ impl AnyClientState { .map_err(|err| err.to_string()) .map(Self::Wasm) } + AnyClientStateTag::Rollup => { + Protobuf::::decode_vec(&value) + .map_err(|err| err.to_string()) + .map(Self::Rollup) + } #[cfg(any(test, feature = "mocks"))] AnyClientStateTag::Mock => { Protobuf::::decode_vec(&value) @@ -323,6 +339,104 @@ impl guestchain::Verifier for IbcStorage<'_, '_> { } } +impl cf_solana::CommonContext for IbcStorage<'_, '_> { + type ConversionError = &'static str; + type AnyClientState = AnyClientState; + type AnyConsensusState = AnyConsensusState; + + fn host_metadata(&self) -> Result<(ibc::Timestamp, ibc::Height)> { + let timestamp = self.borrow().chain.head()?.timestamp_ns.get(); + let timestamp = + ibc::Timestamp::from_nanoseconds(timestamp).map_err(|err| { + ibc::ClientError::Other { description: err.to_string() } + })?; + + let height = u64::from(self.borrow().chain.head()?.block_height); + let height = ibc::Height::new(1, height)?; + + Ok((timestamp, height)) + } + + fn set_client_state( + &mut self, + client_id: &ibc::ClientId, + state: Self::AnyClientState, + ) -> Result<()> { + self.store_client_state_impl(client_id, state) + } + + fn consensus_state( + &self, + client_id: &ibc::ClientId, + height: ibc::Height, + ) -> Result { + self.consensus_state_impl(client_id, height) + } + + fn consensus_state_neighbourhood( + &self, + client_id: &ibc::ClientId, + height: ibc::Height, + ) -> Result> { + use core::cmp::Ordering; + + let height = (height.revision_number(), height.revision_height()); + let mut prev = ((0, 0), None); + let mut next = ((u64::MAX, u64::MAX), None); + + let storage = self.borrow(); + let states = &storage.private.client(client_id)?.consensus_states; + for (key, value) in states.iter() { + let key = (key.revision_number(), key.revision_height()); + match key.cmp(&height) { + Ordering::Less if key >= prev.0 => prev = (key, Some(value)), + Ordering::Greater if key <= next.0 => next = (key, Some(value)), + Ordering::Equal => { + return value.state().map(cf_guest::Neighbourhood::This) + } + _ => (), + } + } + + let prev = prev.1.map(|state| state.state()).transpose()?; + let next = next.1.map(|state| state.state()).transpose()?; + Ok(cf_guest::Neighbourhood::Neighbours(prev, next)) + } + + fn store_consensus_state_and_metadata( + &mut self, + client_id: &ibc::ClientId, + height: ibc::Height, + consensus: Self::AnyConsensusState, + _host_timestamp: ibc::Timestamp, + _host_height: ibc::Height, + ) -> Result { + self.store_consensus_state_impl(client_id, height, consensus) + } + + fn delete_consensus_state_and_metadata( + &mut self, + client_id: &ibc::ClientId, + height: ibc::Height, + ) -> Result { + self.delete_consensus_state_impl(client_id, height) + } + + /// Returns `None`. + /// + /// This method is used by the light client to prune old states. However, + /// we are limiting number of consensus states we’re keeping in + /// store_consensus_state_and_metadata method, which makes it unnecessary + /// for the light client to perform the pruning. Because of that, this + /// method returns `None`. + fn earliest_consensus_state( + &self, + _client_id: &ibc::ClientId, + ) -> Result> { + Ok(None) + } +} + #[cfg(any(test, feature = "mocks"))] impl ibc::mock::MockClientContext for IbcStorage<'_, '_> { type ConversionError = &'static str; diff --git a/solana/solana-ibc/programs/solana-ibc/src/client_state/impls.rs b/solana/solana-ibc/programs/solana-ibc/src/client_state/impls.rs index 3a9357f5..01a8bb30 100644 --- a/solana/solana-ibc/programs/solana-ibc/src/client_state/impls.rs +++ b/solana/solana-ibc/programs/solana-ibc/src/client_state/impls.rs @@ -18,6 +18,7 @@ macro_rules! delegate { match self { AnyClientState::Tendermint(cs) => cs.$name($($arg),*), AnyClientState::Wasm(_) => unimplemented!(), + AnyClientState::Rollup(cs) => cs.$name($($arg),*), #[cfg(any(test, feature = "mocks"))] AnyClientState::Mock(cs) => cs.$name($($arg),*), } @@ -53,6 +54,7 @@ impl ibc::ClientStateCommon for AnyClientState { ) } AnyClientState::Wasm(_) => unimplemented!(), + AnyClientState::Rollup(_) => unimplemented!(), #[cfg(any(test, feature = "mocks"))] AnyClientState::Mock(cs) => cs.verify_upgrade_client( upgraded_client_state, @@ -84,6 +86,9 @@ impl ibc::ClientStateCommon for AnyClientState { ) } AnyClientState::Wasm(_) => unimplemented!(), + AnyClientState::Rollup(cs) => { + cs.verify_membership(prefix, proof, root, path, value) + } #[cfg(any(test, feature = "mocks"))] AnyClientState::Mock(cs) => { cs.verify_membership(prefix, proof, root, path, value) @@ -107,6 +112,9 @@ impl ibc::ClientStateCommon for AnyClientState { ) } AnyClientState::Wasm(_) => unimplemented!(), + AnyClientState::Rollup(cs) => { + cs.verify_non_membership(prefix, proof, root, path) + } #[cfg(any(test, feature = "mocks"))] AnyClientState::Mock(cs) => { cs.verify_non_membership(prefix, proof, root, path) @@ -132,6 +140,9 @@ impl<'a, 'b> ibc::ClientStateValidation> for AnyClientState { ) } AnyClientState::Wasm(_) => unimplemented!(), + AnyClientState::Rollup(cs) => { + cs.verify_client_message(ctx, client_id, client_message) + } #[cfg(any(test, feature = "mocks"))] AnyClientState::Mock(cs) => { cs.verify_client_message(ctx, client_id, client_message) @@ -155,6 +166,7 @@ impl<'a, 'b> ibc::ClientStateValidation> for AnyClientState { ) } AnyClientState::Wasm(_) => unimplemented!(), + AnyClientState::Rollup(_) => unimplemented!(), #[cfg(any(test, feature = "mocks"))] AnyClientState::Mock(_) => unimplemented!(), } diff --git a/solana/solana-ibc/programs/solana-ibc/src/consensus_state.rs b/solana/solana-ibc/programs/solana-ibc/src/consensus_state.rs index 46f3d4b3..17a5bd55 100644 --- a/solana/solana-ibc/programs/solana-ibc/src/consensus_state.rs +++ b/solana/solana-ibc/programs/solana-ibc/src/consensus_state.rs @@ -15,6 +15,7 @@ use crate::ibc::{self, Protobuf}; pub enum AnyConsensusState { Tendermint(ibc::tm::ConsensusState), Wasm(ibc::wasm::ConsensusState), + Rollup(cf_solana::ConsensusState), #[cfg(any(test, feature = "mocks"))] Mock(ibc::mock::MockConsensusState), } @@ -25,6 +26,7 @@ pub enum AnyConsensusState { enum AnyConsensusStateTag { Tendermint = 0, Wasm = 1, + Rollup = 2, #[cfg(any(test, feature = "mocks"))] Mock = 255, } @@ -36,6 +38,7 @@ impl AnyConsensusStateTag { match url { AnyConsensusState::TENDERMINT_TYPE => Some(Self::Tendermint), AnyConsensusState::WASM_TYPE => Some(Self::Wasm), + AnyConsensusState::ROLLUP_TYPE => Some(Self::Rollup), #[cfg(any(test, feature = "mocks"))] AnyConsensusState::MOCK_TYPE => Some(Self::Mock), _ => None, @@ -44,13 +47,16 @@ impl AnyConsensusStateTag { } impl AnyConsensusState { - /// Protobuf type URL for Tendermint client state used in Any message. + /// Protobuf type URL for Tendermint consensus state used in Any message. const TENDERMINT_TYPE: &'static str = ibc::tm::TENDERMINT_CONSENSUS_STATE_TYPE_URL; - /// Protobuf type URL for WASM client state used in Any message. + /// Protobuf type URL for WASM consensus state used in Any message. const WASM_TYPE: &'static str = ibc::wasm::WASM_CONSENSUS_STATE_TYPE_URL; + /// Protobuf type URL for Rollup consensus state used in Any message. + const ROLLUP_TYPE: &'static str = + cf_solana::proto::ConsensusState::IBC_TYPE_URL; #[cfg(any(test, feature = "mocks"))] - /// Protobuf type URL for Mock client state used in Any message. + /// Protobuf type URL for Mock consensus state used in Any message. const MOCK_TYPE: &'static str = ibc::mock::MOCK_CONSENSUS_STATE_TYPE_URL; /// Encodes the payload and returns discriminants that allow decoding the @@ -58,7 +64,7 @@ impl AnyConsensusState { /// /// Returns a `(tag, type, value)` triple where `tag` is discriminant /// identifying variant of the enum, `type` is protobuf type URL - /// corresponding to the client state and `value` is the client state + /// corresponding to the consensus state and `value` is the consensus state /// encoded as protobuf. /// /// `(tag, value)` is used when borsh-encoding and `(type, value)` is used @@ -77,6 +83,11 @@ impl AnyConsensusState { Self::WASM_TYPE, Protobuf::::encode_vec(state), ), + AnyConsensusState::Rollup(state) => ( + AnyConsensusStateTag::Rollup, + Self::ROLLUP_TYPE, + Protobuf::::encode_vec(state), + ), #[cfg(any(test, feature = "mocks"))] AnyConsensusState::Mock(state) => ( AnyConsensusStateTag::Mock, @@ -102,6 +113,11 @@ impl AnyConsensusState { .map_err(|err| err.to_string()) .map(Self::Wasm) } + AnyConsensusStateTag::Rollup => { + Protobuf::::decode_vec(&value) + .map_err(|err| err.to_string()) + .map(Self::Rollup) + } #[cfg(any(test, feature = "mocks"))] AnyConsensusStateTag::Mock => { Protobuf::::decode_vec(&value)