diff --git a/solana/solana-ibc/programs/solana-ibc/Cargo.toml b/solana/solana-ibc/programs/solana-ibc/Cargo.toml index bb4690d7..079a5bfd 100644 --- a/solana/solana-ibc/programs/solana-ibc/Cargo.toml +++ b/solana/solana-ibc/programs/solana-ibc/Cargo.toml @@ -23,6 +23,11 @@ ibc-proto.workspace = true serde.workspace = true serde_json.workspace = true +lib.workspace = true +memory.workspace = true +sealable-trie.workspace = true +stdx.workspace = true + [dev-dependencies] anchor-client.workspace = true anyhow.workspace = 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 35d855b4..1bf571e9 100644 --- a/solana/solana-ibc/programs/solana-ibc/src/client_state.rs +++ b/solana/solana-ibc/programs/solana-ibc/src/client_state.rs @@ -82,7 +82,7 @@ impl From for Any { } } -impl ClientStateValidation for AnyClientState { +impl ClientStateValidation> for AnyClientState { fn verify_client_message( &self, ctx: &SolanaIbcStorage, @@ -283,7 +283,7 @@ impl From for AnyClientState { fn from(value: MockClientState) -> Self { AnyClientState::Mock(value) } } -impl ClientStateExecution for AnyClientState { +impl ClientStateExecution> for AnyClientState { fn initialise( &self, ctx: &mut SolanaIbcStorage, @@ -371,7 +371,7 @@ impl ClientStateExecution for AnyClientState { } } -impl ibc::clients::ics07_tendermint::CommonContext for SolanaIbcStorage { +impl ibc::clients::ics07_tendermint::CommonContext for SolanaIbcStorage<'_, '_> { type ConversionError = ClientError; type AnyConsensusState = AnyConsensusState; @@ -385,7 +385,7 @@ impl ibc::clients::ics07_tendermint::CommonContext for SolanaIbcStorage { } #[cfg(any(test, feature = "mocks"))] -impl MockClientContext for SolanaIbcStorage { +impl MockClientContext for SolanaIbcStorage<'_, '_> { type ConversionError = ClientError; type AnyConsensusState = AnyConsensusState; @@ -401,7 +401,7 @@ impl MockClientContext for SolanaIbcStorage { } } -impl ibc::clients::ics07_tendermint::ValidationContext for SolanaIbcStorage { +impl ibc::clients::ics07_tendermint::ValidationContext for SolanaIbcStorage<'_, '_> { fn host_timestamp(&self) -> Result { ValidationContext::host_timestamp(self) } diff --git a/solana/solana-ibc/programs/solana-ibc/src/execution_context.rs b/solana/solana-ibc/programs/solana-ibc/src/execution_context.rs index bdfb21a4..0e464205 100644 --- a/solana/solana-ibc/programs/solana-ibc/src/execution_context.rs +++ b/solana/solana-ibc/programs/solana-ibc/src/execution_context.rs @@ -31,7 +31,7 @@ use crate::{ type Result = core::result::Result; -impl ClientExecutionContext for SolanaIbcStorage { +impl ClientExecutionContext for SolanaIbcStorage<'_, '_> { type ClientValidationContext = Self; type AnyClientState = AnyClientState; type AnyConsensusState = AnyConsensusState; @@ -77,7 +77,7 @@ impl ClientExecutionContext for SolanaIbcStorage { } } -impl ExecutionContext for SolanaIbcStorage { +impl ExecutionContext for SolanaIbcStorage<'_, '_> { fn increase_client_counter(&mut self) -> Result { self.client_counter.checked_add(1).unwrap(); msg!("client_counter has increased to: {}", self.client_counter); diff --git a/solana/solana-ibc/programs/solana-ibc/src/lib.rs b/solana/solana-ibc/programs/solana-ibc/src/lib.rs index b2d86651..c1973bd8 100644 --- a/solana/solana-ibc/programs/solana-ibc/src/lib.rs +++ b/solana/solana-ibc/programs/solana-ibc/src/lib.rs @@ -11,6 +11,7 @@ use ibc::core::router::{Module, ModuleId, Router}; use module_holder::ModuleHolder; const SOLANA_IBC_STORAGE_SEED: &[u8] = b"solana_ibc_storage"; +const TEST_TRIE_SEED: &[u8] = b"test_trie"; declare_id!("EnfDJsAK7BGgetnmKzBx86CsgC5kfSPcsktFCQ4YLC81"); @@ -21,9 +22,16 @@ mod module_holder; #[cfg(test)] mod tests; mod transfer; +mod trie; mod validation_context; // mod client_context; +/// Discriminants for the data stored in the accounts. +mod magic { + pub(crate) const UNINITIALISED: u32 = 0; + pub(crate) const TRIE_ROOT: u32 = 1; +} + #[anchor_lang::program] pub mod solana_ibc { use super::*; @@ -34,7 +42,8 @@ pub mod solana_ibc { ) -> Result<()> { msg!("Called deliver method"); let _sender = ctx.accounts.sender.to_account_info(); - let solana_ibc_store: &mut SolanaIbcStorage = &mut ctx.accounts.storage; + let solana_ibc_store: &SolanaIbcStorageTemp = + &ctx.accounts.storage; msg!("This is solana_ibc_store {:?}", solana_ibc_store); let all_messages = messages @@ -46,13 +55,73 @@ pub mod solana_ibc { .collect::>(); msg!("These are messages {:?}", all_messages); - let router = &mut solana_ibc_store.clone(); + + + let account = &ctx.accounts.trie; + let mut trie = trie::AccountTrie::new(account.try_borrow_mut_data()?) + .ok_or(ProgramError::InvalidAccountData)?; + + let mut solana_real_storage = SolanaIbcStorage { + height: solana_ibc_store.height, + module_holder: solana_ibc_store.module_holder.clone(), + clients: solana_ibc_store.clients.clone(), + client_id_set: solana_ibc_store.client_id_set.clone(), + client_counter: solana_ibc_store.client_counter.clone(), + client_processed_times: solana_ibc_store.client_processed_times.clone(), + client_processed_heights: solana_ibc_store.client_processed_heights.clone(), + consensus_states: solana_ibc_store.consensus_states.clone(), + client_consensus_state_height_sets: solana_ibc_store.client_consensus_state_height_sets.clone(), + connection_id_set: solana_ibc_store.connection_id_set.clone(), + connection_counter: solana_ibc_store.connection_counter.clone(), + connections: solana_ibc_store.connections.clone(), + channel_ends: solana_ibc_store.channel_ends.clone(), + connection_to_client: solana_ibc_store.connection_to_client.clone(), + port_channel_id_set: solana_ibc_store.port_channel_id_set.clone(), + channel_counter: solana_ibc_store.channel_counter.clone(), + next_sequence_send: solana_ibc_store.next_sequence_send.clone(), + next_sequence_recv: solana_ibc_store.next_sequence_recv.clone(), + next_sequence_ack: solana_ibc_store.next_sequence_ack.clone(), + packet_commitment_sequence_sets: solana_ibc_store.packet_commitment_sequence_sets.clone(), + packet_receipt_sequence_sets: solana_ibc_store.packet_receipt_sequence_sets.clone(), + packet_acknowledgement_sequence_sets: solana_ibc_store.packet_acknowledgement_sequence_sets.clone(), + ibc_events_history: solana_ibc_store.ibc_events_history.clone(), + trie: Some(trie) + }; + + let mut solana_real_storage_another = SolanaIbcStorage { + height: solana_ibc_store.height, + module_holder: solana_ibc_store.module_holder.clone(), + clients: solana_ibc_store.clients.clone(), + client_id_set: solana_ibc_store.client_id_set.clone(), + client_counter: solana_ibc_store.client_counter.clone(), + client_processed_times: solana_ibc_store.client_processed_times.clone(), + client_processed_heights: solana_ibc_store.client_processed_heights.clone(), + consensus_states: solana_ibc_store.consensus_states.clone(), + client_consensus_state_height_sets: solana_ibc_store.client_consensus_state_height_sets.clone(), + connection_id_set: solana_ibc_store.connection_id_set.clone(), + connection_counter: solana_ibc_store.connection_counter.clone(), + connections: solana_ibc_store.connections.clone(), + channel_ends: solana_ibc_store.channel_ends.clone(), + connection_to_client: solana_ibc_store.connection_to_client.clone(), + port_channel_id_set: solana_ibc_store.port_channel_id_set.clone(), + channel_counter: solana_ibc_store.channel_counter.clone(), + next_sequence_send: solana_ibc_store.next_sequence_send.clone(), + next_sequence_recv: solana_ibc_store.next_sequence_recv.clone(), + next_sequence_ack: solana_ibc_store.next_sequence_ack.clone(), + packet_commitment_sequence_sets: solana_ibc_store.packet_commitment_sequence_sets.clone(), + packet_receipt_sequence_sets: solana_ibc_store.packet_receipt_sequence_sets.clone(), + packet_acknowledgement_sequence_sets: solana_ibc_store.packet_acknowledgement_sequence_sets.clone(), + ibc_events_history: solana_ibc_store.ibc_events_history.clone(), + trie: None, + }; + + let router = &mut solana_real_storage_another; let errors = all_messages.into_iter().fold(vec![], |mut errors, msg| { match ibc::core::MsgEnvelope::try_from(msg) { Ok(msg) => { - match ibc::core::dispatch(solana_ibc_store, router, msg) + match ibc::core::dispatch(&mut solana_real_storage, router, msg) { Ok(()) => (), Err(e) => errors.push(e), @@ -68,6 +137,7 @@ pub mod solana_ibc { Ok(()) } + } #[derive(Accounts)] @@ -75,10 +145,15 @@ pub struct Deliver<'info> { #[account(mut)] pub sender: Signer<'info>, #[account(init_if_needed, payer = sender, seeds = [SOLANA_IBC_STORAGE_SEED],bump, space = 10000)] - pub storage: Account<'info, SolanaIbcStorage>, + pub storage: Account<'info, SolanaIbcStorageTemp>, + #[account(init_if_needed, payer = sender, seeds = [TEST_TRIE_SEED], bump, space = 1000)] + /// CHECK: + pub trie: AccountInfo<'info>, pub system_program: Program<'info, System>, } +pub struct MyTrie {} + #[event] pub struct EmitIBCEvent { pub ibc_event: Vec, @@ -116,7 +191,7 @@ pub type InnerConsensusState = String; // Serialized #[account] #[derive(Debug)] /// All the structs from IBC are stored as String since they dont implement AnchorSerialize and AnchorDeserialize -pub struct SolanaIbcStorage { +pub struct SolanaIbcStorageTemp { pub height: InnerHeight, /// To support the mutable borrow in `Router::get_route_mut`. pub module_holder: ModuleHolder, @@ -163,47 +238,70 @@ pub struct SolanaIbcStorage { pub ibc_events_history: BTreeMap>, } -impl SolanaIbcStorage { - pub fn new(account: Pubkey) -> Self { - SolanaIbcStorage { - height: (0, 0), - module_holder: ModuleHolder::new(account), - clients: BTreeMap::new(), - client_id_set: Vec::new(), - client_counter: 0, - client_processed_times: BTreeMap::new(), - client_processed_heights: BTreeMap::new(), - consensus_states: BTreeMap::new(), - client_consensus_state_height_sets: BTreeMap::new(), - connection_id_set: Vec::new(), - connection_counter: 0, - connections: BTreeMap::new(), - channel_ends: BTreeMap::new(), - connection_to_client: BTreeMap::new(), - port_channel_id_set: Vec::new(), - channel_counter: 0, - next_sequence_send: BTreeMap::new(), - next_sequence_recv: BTreeMap::new(), - next_sequence_ack: BTreeMap::new(), - packet_commitment_sequence_sets: BTreeMap::new(), - packet_receipt_sequence_sets: BTreeMap::new(), - packet_acknowledgement_sequence_sets: BTreeMap::new(), - ibc_events_history: BTreeMap::new(), - } - } +/// All the structs from IBC are stored as String since they dont implement AnchorSerialize and AnchorDeserialize +pub struct SolanaIbcStorage<'a, 'b> { + pub height: InnerHeight, + /// To support the mutable borrow in `Router::get_route_mut`. + pub module_holder: ModuleHolder, + pub clients: BTreeMap, + /// The client ids of the clients. + pub client_id_set: Vec, + pub client_counter: u64, + pub client_processed_times: + BTreeMap>, + pub client_processed_heights: + BTreeMap>, + pub consensus_states: + BTreeMap<(InnerClientId, InnerHeight), InnerConsensusState>, + /// This collection contains the heights corresponding to all consensus states of + /// all clients stored in the contract. + pub client_consensus_state_height_sets: + BTreeMap>, + /// The connection ids of the connections. + pub connection_id_set: Vec, + pub connection_counter: u64, + pub connections: BTreeMap, + pub channel_ends: BTreeMap<(InnerPortId, InnerChannelId), InnerChannelEnd>, + // Contains the client id corresponding to the connectionId + pub connection_to_client: BTreeMap, + /// The port and channel id tuples of the channels. + pub port_channel_id_set: Vec<(InnerPortId, InnerChannelId)>, + pub channel_counter: u64, + pub next_sequence_send: + BTreeMap<(InnerPortId, InnerChannelId), InnerSequence>, + pub next_sequence_recv: + BTreeMap<(InnerPortId, InnerChannelId), InnerSequence>, + pub next_sequence_ack: + BTreeMap<(InnerPortId, InnerChannelId), InnerSequence>, + /// The sequence numbers of the packet commitments. + pub packet_commitment_sequence_sets: + BTreeMap<(InnerPortId, InnerChannelId), Vec>, + /// The sequence numbers of the packet receipts. + pub packet_receipt_sequence_sets: + BTreeMap<(InnerPortId, InnerChannelId), Vec>, + /// The sequence numbers of the packet acknowledgements. + pub packet_acknowledgement_sequence_sets: + BTreeMap<(InnerPortId, InnerChannelId), Vec>, + /// The history of IBC events. + pub ibc_events_history: BTreeMap>, + pub trie: Option>, } pub trait SolanaIbcStorageHost { /// - fn get_solana_ibc_store(_account: Pubkey) -> SolanaIbcStorage { + fn get_solana_ibc_store( + _account: Pubkey, + ) -> SolanaIbcStorage<'static, 'static> { // Unpack the account todo!() } /// - fn set_solana_ibc_store(_store: &SolanaIbcStorage) { todo!() } + fn set_solana_ibc_store(_store: &SolanaIbcStorage) { + todo!() + } } -impl Router for SolanaIbcStorage { +impl Router for SolanaIbcStorage<'_, '_> { // fn get_route(&self, module_id: &ModuleId) -> Option<&dyn Module> { match module_id.to_string().as_str() { diff --git a/solana/solana-ibc/programs/solana-ibc/src/tests.rs b/solana/solana-ibc/programs/solana-ibc/src/tests.rs index 16ce4cff..5f25ea95 100644 --- a/solana/solana-ibc/programs/solana-ibc/src/tests.rs +++ b/solana/solana-ibc/programs/solana-ibc/src/tests.rs @@ -21,7 +21,7 @@ use ibc_proto::protobuf::Protobuf; use crate::{ accounts, instruction, AnyCheck, SolanaIbcStorage, ID, - SOLANA_IBC_STORAGE_SEED, + SOLANA_IBC_STORAGE_SEED, TEST_TRIE_SEED, }; const TYPE_URL: &str = "/ibc.core.client.v1.MsgCreateClient"; @@ -66,6 +66,8 @@ fn anchor_test_deliver() -> Result<()> { // Build, sign, and send program instruction let seeds = &[SOLANA_IBC_STORAGE_SEED]; let solana_ibc_storage = Pubkey::find_program_address(seeds, &crate::ID).0; + let trie_seeds = &[TEST_TRIE_SEED]; + let trie = Pubkey::find_program_address(trie_seeds, &crate::ID).0; let (mock_client_state, mock_cs_state) = create_mock_client_and_cs_state(); let _client_id = ClientId::new(mock_client_state.client_type(), 0).unwrap(); @@ -84,6 +86,7 @@ fn anchor_test_deliver() -> Result<()> { .accounts(accounts::Deliver { sender: authority.pubkey(), storage: solana_ibc_storage, + trie, system_program: system_program::ID, }) .args(instruction::Deliver { messages: all_messages }) @@ -96,11 +99,11 @@ fn anchor_test_deliver() -> Result<()> { println!("demo sig: {sig}"); - // Retrieve and validate state - let solana_ibc_storage_account: SolanaIbcStorage = - program.account(solana_ibc_storage).unwrap(); + // // Retrieve and validate state + // let solana_ibc_storage_account: SolanaIbcStorage = + // program.account(solana_ibc_storage).unwrap(); - println!("This is solana storage account {:?}", solana_ibc_storage_account); + // println!("This is solana storage account {:?}", solana_ibc_storage_account); Ok(()) } diff --git a/solana/solana-ibc/programs/solana-ibc/src/transfer/impls.rs b/solana/solana-ibc/programs/solana-ibc/src/transfer/impls.rs index 1c8176ee..a643a718 100644 --- a/solana/solana-ibc/programs/solana-ibc/src/transfer/impls.rs +++ b/solana/solana-ibc/programs/solana-ibc/src/transfer/impls.rs @@ -125,9 +125,9 @@ impl TokenTransferValidationContext for ModuleHolder { } impl SendPacketValidationContext for ModuleHolder { - type ClientValidationContext = SolanaIbcStorage; + type ClientValidationContext = SolanaIbcStorage<'static, 'static>; - type E = SolanaIbcStorage; + type E = SolanaIbcStorage<'static, 'static>; type AnyConsensusState = AnyConsensusState; diff --git a/solana/solana-ibc/programs/solana-ibc/src/trie.rs b/solana/solana-ibc/programs/solana-ibc/src/trie.rs new file mode 100644 index 00000000..2741af37 --- /dev/null +++ b/solana/solana-ibc/programs/solana-ibc/src/trie.rs @@ -0,0 +1,329 @@ +use anchor_lang::prelude::*; + +use core::cell::RefMut; +use core::mem::ManuallyDrop; + +use lib::hash::CryptoHash; +use memory::Ptr; + +use std::result::Result; + +use crate::magic; + +type DataRef<'a, 'b> = RefMut<'a, &'b mut [u8]>; + +const SZ: usize = sealable_trie::nodes::RawNode::SIZE; + +/// Trie stored in a Solana account. +// #[account] +pub struct AccountTrie<'a, 'b>( + core::mem::ManuallyDrop>>, +); + +impl<'a, 'b> AccountTrie<'a, 'b> { + /// Creates a new Trie from data in an account. + /// + /// If the data in the account isn’t initialised (i.e. has zero + /// discriminant) initialises a new empty trie. + pub(crate) fn new(data: DataRef<'a, 'b>) -> Option { + let (alloc, root) = Allocator::new(data)?; + let trie = sealable_trie::Trie::from_parts(alloc, root.0, root.1); + Some(Self(ManuallyDrop::new(trie))) + } +} + +impl<'a, 'b> core::ops::Drop for AccountTrie<'a, 'b> { + /// Updates the header in the Solana account. + fn drop(&mut self) { + // SAFETY: Once we’re done with self.0 we are dropped and no one else is + // going to have access to self.0. + let trie = unsafe { ManuallyDrop::take(&mut self.0) }; + let (mut alloc, root_ptr, root_hash) = trie.into_parts(); + let hdr = Header { + root_ptr, + root_hash, + next_block: alloc.next_block, + first_free: alloc.first_free.map_or(0, |ptr| ptr.get()), + }; + alloc + .data + .get_mut(..Header::ENCODED_SIZE) + .unwrap() + .copy_from_slice(&hdr.encode()); + } +} + +/// Data stored in the first 72-bytes of the account describing the trie. +#[derive(Clone, Debug, PartialEq)] +struct Header { + root_ptr: Option, + root_hash: CryptoHash, + next_block: u32, + first_free: u32, +} + +impl Header { + /// Size of the encoded header. + const ENCODED_SIZE: usize = 64; + + /// Decodes the header from given block of memory. + /// + /// Returns `None` if the block is shorter than length of encoded header or + /// encoded data is invalid. + // Encoding: + // magic: u32 + // version: u32 + // root_ptr: u32 + // root_hash: [u8; 32] + // next_block: u32 + // first_free: u32 + // padding: [u8; 12], + fn decode(data: &[u8]) -> Option { + let data = data.get(..Self::ENCODED_SIZE)?.try_into().unwrap(); + + // Check magic number. Zero means the account hasn’t been initialised + // so return default value, and anything other than magic::TRIE_ROOT + // means it’s an account storing data different than a trie root. + let (magic, data) = read::<4, 60, 64, _>(data, u32::from_ne_bytes); + if magic == magic::UNINITIALISED { + return Some(Self { + root_ptr: None, + root_hash: sealable_trie::trie::EMPTY_TRIE_ROOT, + next_block: Self::ENCODED_SIZE as u32, + first_free: 0, + }); + } else if magic != magic::TRIE_ROOT { + return None; + } + + // Check version. This is for future-proofing in case format of the + // encoding changes. + let (version, data) = read::<4, 56, 60, _>(data, u32::from_ne_bytes); + if version != 0 { + return None; + } + + let (root_ptr, data) = read::<4, 52, 56, _>(data, u32::from_ne_bytes); + let (root_hash, data) = read::<32, 20, 52, _>(data, CryptoHash); + let (next_block, data) = read::<4, 16, 20, _>(data, u32::from_ne_bytes); + let (first_free, _) = read::<4, 12, 16, _>(data, u32::from_ne_bytes); + + let root_ptr = Ptr::new(root_ptr).ok()?; + Some(Self { root_ptr, root_hash, next_block, first_free }) + } + + /// Returns encoded representation of values in the header. + fn encode(&self) -> [u8; Self::ENCODED_SIZE] { + let root_ptr = + self.root_ptr.map_or([0; 4], |ptr| ptr.get().to_ne_bytes()); + + let mut buf = [0; Self::ENCODED_SIZE]; + let data = &mut buf; + let data = write::<4, 60, 64>(data, magic::TRIE_ROOT.to_ne_bytes()); + let data = write::<4, 56, 60>(data, [0; 4]); + let data = write::<4, 52, 56>(data, root_ptr); + let data = write::<32, 20, 52>(data, self.root_hash.0); + let data = write::<4, 16, 20>(data, self.next_block.to_ne_bytes()); + write::<4, 12, 16>(data, self.first_free.to_ne_bytes()); + buf + } +} + +pub struct Allocator<'a, 'b> { + /// Pool of memory to allocate blocks in. + /// + /// The data is always at least long enough to fit encoded [`Header`]. + data: DataRef<'a, 'b>, + + /// Position of the next unallocated block. + /// + /// Blocks which were allocated and then freed don’t count as ‘unallocated’ + /// in this context. This is position of the next block to return if the + /// free list is empty. + next_block: u32, + + /// Pointer to the first freed block; `None` if there were no freed blocks + /// yet. + first_free: Option, +} + +impl<'a, 'b> Allocator<'a, 'b> { + /// Initialises the allocator with data in given account. + fn new(data: DataRef<'a, 'b>) -> Option<(Self, (Option, CryptoHash))> { + let hdr = Header::decode(*data)?; + let next_block = hdr.next_block; + let first_free = Ptr::new(hdr.first_free).ok()?; + let alloc = Self { data, next_block, first_free }; + let root = (hdr.root_ptr, hdr.root_hash); + Some((alloc, root)) + } + + /// Grabs a block from a free list. Returns `None` if free list is empty. + fn alloc_from_freelist(&mut self) -> Option { + let ptr = self.first_free.take()?; + let idx = ptr.get() as usize; + let next = self.data.get(idx..idx + 4).unwrap().try_into().unwrap(); + self.first_free = Ptr::new(u32::from_ne_bytes(next)).unwrap(); + Some(ptr) + } + + /// Grabs a next available block. Returns `None` if account run out of + /// space. + fn alloc_next_block(&mut self) -> Option { + let len = u32::try_from(self.data.len()).unwrap_or(u32::MAX); + let end = + self.next_block.checked_add(SZ as u32).filter(|&e| e <= len)?; + let ptr = Ptr::new(self.next_block).ok().flatten()?; + self.next_block = end; + Some(ptr) + } +} + +impl<'a, 'b> memory::Allocator for Allocator<'a, 'b> { + type Value = [u8; SZ]; + + fn alloc( + &mut self, + value: Self::Value, + ) -> Result { + let ptr = self + .alloc_from_freelist() + .or_else(|| self.alloc_next_block()) + .ok_or(memory::OutOfMemory)?; + self.set(ptr, value); + Ok(ptr) + } + + #[inline] + fn get(&self, ptr: Ptr) -> &Self::Value { + let idx = ptr.get() as usize; + self.data.get(idx..idx + SZ).unwrap().try_into().unwrap() + } + + #[inline] + fn get_mut(&mut self, ptr: Ptr) -> &mut Self::Value { + let idx = ptr.get() as usize; + self.data.get_mut(idx..idx + SZ).unwrap().try_into().unwrap() + } + + #[inline] + fn free(&mut self, ptr: Ptr) { + let next = + self.first_free.map_or([0; 4], |ptr| ptr.get().to_ne_bytes()); + let idx = ptr.get() as usize; + self.data.get_mut(idx..idx + 4).unwrap().copy_from_slice(&next); + self.first_free = Some(ptr); + } +} + + + +impl<'a, 'b> core::ops::Deref for AccountTrie<'a, 'b> { + type Target = sealable_trie::Trie>; + fn deref(&self) -> &Self::Target { &self.0 } +} + +impl<'a, 'b> core::ops::DerefMut for AccountTrie<'a, 'b> { + fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } +} + +/// Reads fixed-width value from start of the buffer and returns the value and +/// remaining portion of the buffer. +/// +/// By working on a fixed-size buffers, this avoids any run-time checks. Sizes +/// are verified at compile-time. +fn read( + buf: &[u8; N], + f: impl Fn([u8; L]) -> T, +) -> (T, &[u8; R]) { + let (left, right) = stdx::split_array_ref(buf); + (f(*left), right) +} + +/// Writes given fixed-width buffer at the start the buffer and returns the +/// remaining portion of the buffer. +/// +/// By working on a fixed-size buffers, this avoids any run-time checks. Sizes +/// are verified at compile-time. +fn write( + buf: &mut [u8; N], + data: [u8; L], +) -> &mut [u8; R] { + let (left, right) = stdx::split_array_mut(buf); + *left = data; + right +} + + +#[test] +fn test_header_encoding() { + const ONE: CryptoHash = CryptoHash([1; 32]); + + assert_eq!( + Some(Header { + root_ptr: None, + root_hash: sealable_trie::trie::EMPTY_TRIE_ROOT, + next_block: Header::ENCODED_SIZE as u32, + first_free: 0, + }), + Header::decode(&[0; 72]) + ); + + let hdr = Header { + root_ptr: Ptr::new(420).unwrap(), + root_hash: ONE.clone(), + next_block: 42, + first_free: 24, + }; + let got_bytes = hdr.encode(); + let got_hdr = Header::decode(&got_bytes); + + #[rustfmt::skip] + assert_eq!([ + /* magic: */ 1, 0, 0, 0, + /* version: */ 0, 0, 0, 0, + /* root_ptr: */ 164, 1, 0, 0, + /* root_hash: */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + /* next_block: */ 42, 0, 0, 0, + /* first_free: */ 24, 0, 0, 0, + /* tail: */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ], got_bytes); + assert_eq!(Some(hdr), got_hdr); +} + +// #[test] +// fn test_trie_sanity() { +// const ONE: CryptoHash = CryptoHash([1; 32]); + +// let key = solana_program::pubkey::Pubkey::new_unique(); +// let mut lamports: u64 = 10 * solana_program::native_token::LAMPORTS_PER_SOL; +// let mut data = [0; SZ * 1000]; +// let owner = solana_program::pubkey::Pubkey::new_unique(); +// let account = solana_program::account_info::AccountInfo::new( +// /* key: */ &key, +// /* is signer: */ false, +// /* is writable: */ true, +// /* lamports: */ &mut lamports, +// /* data: */ &mut data[..], +// /* owner: */ &owner, +// /* executable: */ false, +// /* rent_epoch: */ 42, +// ); + +// { +// let mut trie = AccountTrie::new(account.data.borrow_mut()).unwrap(); +// assert_eq!(Ok(None), trie.get(&[0])); + +// assert_eq!(Ok(()), trie.set(&[0], &ONE)); +// assert_eq!(Ok(Some(ONE.clone())), trie.get(&[0])); +// } + +// { +// let mut trie = AccountTrie::new(account.data.borrow_mut()).unwrap(); +// assert_eq!(Ok(Some(ONE.clone())), trie.get(&[0])); + +// assert_eq!(Ok(()), trie.seal(&[0])); +// assert_eq!(Err(sealable_trie::Error::Sealed), trie.get(&[0])); +// } +// } diff --git a/solana/solana-ibc/programs/solana-ibc/src/validation_context.rs b/solana/solana-ibc/programs/solana-ibc/src/validation_context.rs index db2e48ee..237290a3 100644 --- a/solana/solana-ibc/programs/solana-ibc/src/validation_context.rs +++ b/solana/solana-ibc/programs/solana-ibc/src/validation_context.rs @@ -25,7 +25,7 @@ use crate::client_state::AnyClientState; use crate::consensus_state::AnyConsensusState; use crate::SolanaIbcStorage; -impl ValidationContext for SolanaIbcStorage { +impl ValidationContext for SolanaIbcStorage<'_, '_> { type AnyConsensusState = AnyConsensusState; type AnyClientState = AnyClientState; type E = Self;