Skip to content

Commit

Permalink
solana-ibc: implement host_height method (#97)
Browse files Browse the repository at this point in the history
Properly implement host_height method to return Solana’s block height
(i.e. slot number) rather than using data from account.  The existing
implementation was just a quick hack to get some data going but is by
no means correct.
  • Loading branch information
mina86 authored Nov 17, 2023
1 parent d142c36 commit e04362c
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 33 deletions.
29 changes: 12 additions & 17 deletions solana/solana-ibc/programs/solana-ibc/src/chain.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use anchor_lang::prelude::*;
use anchor_lang::solana_program;
pub use blockchain::Config;

use crate::error::Error;
Expand Down Expand Up @@ -29,11 +28,11 @@ impl ChainData {
config: Config,
genesis_epoch: Epoch,
) -> Result {
let (height, timestamp) = host_head()?;
let host_head = crate::host::Head::get()?;
let genesis = Block::generate_genesis(
1.into(),
height,
timestamp,
host_head.height,
host_head.timestamp,
trie.hash().clone(),
genesis_epoch,
)
Expand Down Expand Up @@ -67,7 +66,7 @@ impl ChainData {
/// block opportunistically at the beginning of handling any smart contract
/// request.
pub fn generate_block(&mut self, trie: &storage::AccountTrie) -> Result {
self.generate_block_impl(trie, true)
self.generate_block_impl(trie, None, true)
}

/// Generates a new guest block if possible.
Expand All @@ -80,8 +79,9 @@ impl ChainData {
pub fn maybe_generate_block(
&mut self,
trie: &storage::AccountTrie,
host_head: Option<crate::host::Head>,
) -> Result {
self.generate_block_impl(trie, false)
self.generate_block_impl(trie, host_head, false)
}

/// Attempts generating a new guest block.
Expand All @@ -93,28 +93,29 @@ impl ChainData {
fn generate_block_impl(
&mut self,
trie: &storage::AccountTrie,
host_head: Option<crate::host::Head>,
force: bool,
) -> Result {
let host_head = host_head.map_or_else(crate::host::Head::get, Ok)?;
let inner = self.get_mut()?;
let (height, timestamp) = host_head()?;

// We attempt generating guest blocks only once per host block. This has
// two reasons:
// 1. We don’t want to repeat the same checks each block.
// 2. We don’t want a situation where some IBC packets are created during
// a Solana block but only some of them end up in a guest block generated
// during that block.
if inner.last_check_height == height {
if inner.last_check_height == host_head.height {
return if force {
Err(Error::GenerationAlreadyAttempted.into())
} else {
Ok(())
};
}
inner.last_check_height = height;
inner.last_check_height = host_head.height;
let res = inner.manager.generate_next(
height,
timestamp,
host_head.height,
host_head.timestamp,
trie.hash().clone(),
false,
);
Expand Down Expand Up @@ -195,9 +196,3 @@ struct ChainInner {
/// The guest blockchain manager handling generation of new guest blocks.
manager: Manager,
}

/// Returns Solana block height and timestamp.
fn host_head() -> Result<(blockchain::HostHeight, u64)> {
let clock = solana_program::clock::Clock::get()?;
Ok((clock.slot.into(), clock.unix_timestamp.try_into().unwrap()))
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,6 @@ impl ClientExecutionContext for IbcStorage<'_, '_> {
(consensus_state_path.epoch, consensus_state_path.height),
);
store.private.consensus_states.insert(key, serialized);
store.private.height =
(consensus_state_path.epoch, consensus_state_path.height);
Ok(())
}

Expand Down
74 changes: 74 additions & 0 deletions solana/solana-ibc/programs/solana-ibc/src/host.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
use anchor_lang::solana_program;
use ibc::core::ics02_client::error::ClientError;
use ibc::core::timestamp::Timestamp;

/// Representation of Solana’s head.
#[derive(Clone, Copy, Debug)]
pub struct Head {
/// Solana’s slot number which we interpret as block height.
pub height: blockchain::HostHeight,
/// Solana’s UNix timestamp in nanoseconds.
pub timestamp: u64,
}

impl Head {
/// Construct’s object from Solana’s Clock sysvar.
#[inline]
pub fn get() -> Result<Head, Error> {
use solana_program::sysvar::Sysvar;
Ok(solana_program::clock::Clock::get()?.into())
}

/// Returns height as an IBC type.
#[inline]
pub fn ibc_height(&self) -> Result<ibc::Height, ClientError> {
ibc::Height::new(0, self.height.into())
}

/// Returns timestamp as an IBC type.
#[inline]
pub fn ibc_timestamp(&self) -> Result<Timestamp, ClientError> {
Timestamp::from_nanoseconds(self.timestamp)
.map_err(|err| ClientError::Other { description: err.to_string() })
}
}

impl From<solana_program::clock::Clock> for Head {
#[inline]
fn from(clock: solana_program::clock::Clock) -> Head {
Self {
height: clock.slot.into(),
timestamp: clock.unix_timestamp as u64,
}
}
}

/// Error possible when fetching Solana’s clock.
///
/// This is just a simple wrapper which offers trivial conversion on Solana and
/// IBC error types so that question mark operator works in all contexts.
#[derive(derive_more::From, derive_more::Into)]
pub struct Error(solana_program::program_error::ProgramError);

impl From<Error> for anchor_lang::error::Error {
#[inline]
fn from(error: Error) -> Self { Self::from(error.0) }
}

impl From<Error> for ClientError {
#[inline]
fn from(error: Error) -> Self {
Self::Other { description: error.0.to_string() }
}
}

impl From<Error> for ibc::core::ContextError {
#[inline]
fn from(error: Error) -> Self { Self::ClientError(error.into()) }
}

impl core::fmt::Debug for Error {
fn fmt(&self, fmtr: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.0.fmt(fmtr)
}
}
9 changes: 6 additions & 3 deletions solana/solana-ibc/programs/solana-ibc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ mod ed25519;
mod error;
mod events;
mod execution_context;
mod host;
mod storage;
#[cfg(test)]
mod tests;
Expand Down Expand Up @@ -90,7 +91,7 @@ pub mod solana_ibc {
&signature.into(),
&verifier,
)? {
ctx.accounts.chain.maybe_generate_block(&provable)?;
ctx.accounts.chain.maybe_generate_block(&provable, None)?;
}
Ok(())
}
Expand All @@ -107,7 +108,7 @@ pub mod solana_ibc {
/// and not intended for production use.
pub fn set_stake(ctx: Context<Chain>, amount: u128) -> Result<()> {
let provable = storage::get_provable_from(&ctx.accounts.trie, "trie")?;
ctx.accounts.chain.maybe_generate_block(&provable)?;
ctx.accounts.chain.maybe_generate_block(&provable, None)?;
ctx.accounts.chain.set_stake((*ctx.accounts.sender.key).into(), amount)
}

Expand All @@ -122,16 +123,18 @@ pub mod solana_ibc {
msg!("This is private: {:?}", private);
let provable = storage::get_provable_from(&ctx.accounts.trie, "trie")?;
let packets: &mut IBCPackets = &mut ctx.accounts.packets;
let host_head = host::Head::get()?;

// Before anything else, try generating a new guest block. However, if
// that fails it’s not an error condition. We do this at the beginning
// of any request.
ctx.accounts.chain.maybe_generate_block(&provable)?;
ctx.accounts.chain.maybe_generate_block(&provable, Some(host_head))?;

let mut store = storage::IbcStorage::new(storage::IbcStorageInner {
private,
provable,
packets,
host_head,
});

{
Expand Down
6 changes: 4 additions & 2 deletions solana/solana-ibc/programs/solana-ibc/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ use anchor_lang::prelude::*;
use ibc::core::ics04_channel::msgs::PacketMsg;
use ibc::core::ics04_channel::packet::Sequence;

type Result<T, E = anchor_lang::error::Error> = core::result::Result<T, E>;

pub(crate) type InnerHeight = (u64, u64);
pub(crate) type HostHeight = InnerHeight;
pub(crate) type SolanaTimestamp = u64;
Expand Down Expand Up @@ -80,7 +82,6 @@ pub struct IBCPackets(pub Vec<PacketMsg>);
/// All the structs from IBC are stored as String since they dont implement
/// AnchorSerialize and AnchorDeserialize
pub(crate) struct PrivateStorage {
pub height: InnerHeight,
pub clients: BTreeMap<InnerClientId, InnerClient>,
/// The client ids of the clients.
pub client_id_set: Vec<InnerClientId>,
Expand Down Expand Up @@ -157,6 +158,7 @@ pub(crate) struct IbcStorageInner<'a, 'b> {
pub private: &'a mut PrivateStorage,
pub provable: AccountTrie<'a, 'b>,
pub packets: &'a mut IBCPackets,
pub host_head: crate::host::Head,
}

/// A reference-counted reference to the IBC storage.
Expand Down Expand Up @@ -197,7 +199,7 @@ impl<'a, 'b> IbcStorage<'a, 'b> {
///
/// # Panics
///
/// Panics if the value is currently mutably borrowed.
/// Panics if the value is currently borrowed.
pub fn borrow_mut<'c>(
&'c self,
) -> core::cell::RefMut<'c, IbcStorageInner<'a, 'b>> {
Expand Down
14 changes: 5 additions & 9 deletions solana/solana-ibc/programs/solana-ibc/src/validation_context.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::str::FromStr;
use std::time::Duration;

use anchor_lang::prelude::{borsh, Clock, Pubkey, SolanaSysvar};
use anchor_lang::prelude::{borsh, Pubkey};
use ibc::core::ics02_client::error::ClientError;
use ibc::core::ics03_connection::connection::ConnectionEnd;
use ibc::core::ics03_connection::error::ConnectionError;
Expand All @@ -27,7 +27,7 @@ use crate::consensus_state::AnyConsensusState;
use crate::storage::IbcStorage;
use crate::trie_key::TrieKey;

type Result<T = (), E = ibc::core::ContextError> = core::result::Result<T, E>;
type Result<T = (), E = ContextError> = core::result::Result<T, E>;

impl ValidationContext for IbcStorage<'_, '_> {
type V = Self; // ClientValidationContext
Expand Down Expand Up @@ -82,19 +82,15 @@ impl ValidationContext for IbcStorage<'_, '_> {
)?,
}),
}
.map_err(ibc::core::ContextError::from)
.map_err(ContextError::from)
}

fn host_height(&self) -> Result<ibc::Height> {
let store = self.borrow();
ibc::Height::new(store.private.height.0, store.private.height.1)
.map_err(ContextError::ClientError)
self.borrow().host_head.ibc_height().map_err(Into::into)
}

fn host_timestamp(&self) -> Result<Timestamp> {
let clock = Clock::get().unwrap();
let current_timestamp = clock.unix_timestamp as u64;
Ok(Timestamp::from_nanoseconds(current_timestamp).unwrap())
self.borrow().host_head.ibc_timestamp().map_err(Into::into)
}

fn host_consensus_state(
Expand Down

0 comments on commit e04362c

Please sign in to comment.