Skip to content

Commit

Permalink
solana-ibc: add support for cf-solana light client
Browse files Browse the repository at this point in the history
  • Loading branch information
mina86 committed Sep 2, 2024
1 parent 4030bac commit d798c44
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 4 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions solana/solana-ibc/programs/solana-ibc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
114 changes: 114 additions & 0 deletions solana/solana-ibc/programs/solana-ibc/src/client_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ type Result<T = (), E = ibc::ClientError> = core::result::Result<T, E>;
pub enum AnyClientState {
Tendermint(ibc::tm::ClientState),
Wasm(ibc::wasm::ClientState),
Rollup(cf_solana::ClientState),
#[cfg(any(test, feature = "mocks"))]
Mock(ibc::mock::MockClientState),
}
Expand All @@ -26,6 +27,7 @@ impl ibc::Protobuf<ibc::Any> for AnyClientState {}
enum AnyClientStateTag {
Tendermint = 0,
Wasm = 1,
Rollup = 2,
#[cfg(any(test, feature = "mocks"))]
Mock = 255,
}
Expand All @@ -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,
Expand All @@ -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;
Expand Down Expand Up @@ -78,6 +84,11 @@ impl AnyClientState {
Self::WASM_TYPE,
Protobuf::<ibc::wasm::ClientStatePB>::encode_vec(state),
),
Self::Rollup(state) => (
AnyClientStateTag::Rollup,
Self::ROLLUP_TYPE,
Protobuf::<cf_solana::proto::ClientState>::encode_vec(state),
),
#[cfg(any(test, feature = "mocks"))]
Self::Mock(state) => (
AnyClientStateTag::Mock,
Expand All @@ -103,6 +114,11 @@ impl AnyClientState {
.map_err(|err| err.to_string())
.map(Self::Wasm)
}
AnyClientStateTag::Rollup => {
Protobuf::<cf_solana::proto::ClientState>::decode_vec(&value)
.map_err(|err| err.to_string())
.map(Self::Rollup)
}
#[cfg(any(test, feature = "mocks"))]
AnyClientStateTag::Mock => {
Protobuf::<ibc::mock::ClientStatePB>::decode_vec(&value)
Expand Down Expand Up @@ -323,6 +339,104 @@ impl guestchain::Verifier<sigverify::ed25519::PubKey> 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::AnyConsensusState> {
self.consensus_state_impl(client_id, height)
}

fn consensus_state_neighbourhood(
&self,
client_id: &ibc::ClientId,
height: ibc::Height,
) -> Result<cf_guest::Neighbourhood<Self::AnyConsensusState>> {
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<Option<(ibc::Height, Self::AnyConsensusState)>> {
Ok(None)
}
}

#[cfg(any(test, feature = "mocks"))]
impl ibc::mock::MockClientContext for IbcStorage<'_, '_> {
type ConversionError = &'static str;
Expand Down
12 changes: 12 additions & 0 deletions solana/solana-ibc/programs/solana-ibc/src/client_state/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),*),
}
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -132,6 +140,9 @@ impl<'a, 'b> ibc::ClientStateValidation<IbcStorage<'a, 'b>> 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)
Expand All @@ -155,6 +166,7 @@ impl<'a, 'b> ibc::ClientStateValidation<IbcStorage<'a, 'b>> for AnyClientState {
)
}
AnyClientState::Wasm(_) => unimplemented!(),
AnyClientState::Rollup(_) => unimplemented!(),
#[cfg(any(test, feature = "mocks"))]
AnyClientState::Mock(_) => unimplemented!(),
}
Expand Down
24 changes: 20 additions & 4 deletions solana/solana-ibc/programs/solana-ibc/src/consensus_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}
Expand All @@ -25,6 +26,7 @@ pub enum AnyConsensusState {
enum AnyConsensusStateTag {
Tendermint = 0,
Wasm = 1,
Rollup = 2,
#[cfg(any(test, feature = "mocks"))]
Mock = 255,
}
Expand All @@ -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,
Expand All @@ -44,21 +47,24 @@ 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
/// value later.
///
/// 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
Expand All @@ -77,6 +83,11 @@ impl AnyConsensusState {
Self::WASM_TYPE,
Protobuf::<ibc::wasm::ConsensusStatePB>::encode_vec(state),
),
AnyConsensusState::Rollup(state) => (
AnyConsensusStateTag::Rollup,
Self::ROLLUP_TYPE,
Protobuf::<cf_solana::proto::ConsensusState>::encode_vec(state),
),
#[cfg(any(test, feature = "mocks"))]
AnyConsensusState::Mock(state) => (
AnyConsensusStateTag::Mock,
Expand All @@ -102,6 +113,11 @@ impl AnyConsensusState {
.map_err(|err| err.to_string())
.map(Self::Wasm)
}
AnyConsensusStateTag::Rollup => {
Protobuf::<cf_solana::proto::ConsensusState>::decode_vec(&value)
.map_err(|err| err.to_string())
.map(Self::Rollup)
}
#[cfg(any(test, feature = "mocks"))]
AnyConsensusStateTag::Mock => {
Protobuf::<ibc::mock::ConsensusStatePB>::decode_vec(&value)
Expand Down

0 comments on commit d798c44

Please sign in to comment.