diff --git a/solana/solana-ibc/programs/solana-ibc/src/ibc.rs b/solana/solana-ibc/programs/solana-ibc/src/ibc.rs index 1598e8cc..131d4234 100644 --- a/solana/solana-ibc/programs/solana-ibc/src/ibc.rs +++ b/solana/solana-ibc/programs/solana-ibc/src/ibc.rs @@ -18,7 +18,9 @@ pub use ibc::core::client::context::client_state::{ pub use ibc::core::client::context::consensus_state::ConsensusState; pub use ibc::core::client::context::types::error::ClientError; #[cfg(test)] -pub use ibc::core::client::context::types::msgs::{ClientMsg, MsgCreateClient}; +pub use ibc::core::client::context::types::msgs::{ + ClientMsg, MsgCreateClient, MsgUpdateClient, +}; pub use ibc::core::client::context::{ ClientExecutionContext, ClientValidationContext, }; diff --git a/solana/solana-ibc/programs/solana-ibc/src/lib.rs b/solana/solana-ibc/programs/solana-ibc/src/lib.rs index d422951e..48db2cc4 100644 --- a/solana/solana-ibc/programs/solana-ibc/src/lib.rs +++ b/solana/solana-ibc/programs/solana-ibc/src/lib.rs @@ -13,13 +13,16 @@ use borsh::BorshDeserialize; use storage::TransferAccounts; use trie_ids::PortChannelPK; -use crate::ibc::{ClientStateValidation, SendPacketValidationContext}; +use crate::ibc::{ + ClientError, ClientStateValidation, SendPacketValidationContext, +}; pub const CHAIN_SEED: &[u8] = b"chain"; pub const PACKET_SEED: &[u8] = b"packet"; pub const SOLANA_IBC_STORAGE_SEED: &[u8] = b"private"; pub const TRIE_SEED: &[u8] = b"trie"; pub const MINT_ESCROW_SEED: &[u8] = b"mint_escrow"; +pub const MSG_CHUNKS: &[u8] = b"msg_chunks"; declare_id!("EnfDJsAK7BGgetnmKzBx86CsgC5kfSPcsktFCQ4YLC81"); @@ -42,9 +45,8 @@ mod validation_context; #[anchor_lang::program] pub mod solana_ibc { - use ::ibc::core::client::types::error::ClientError; - use super::*; + use crate::storage::MsgChunks; /// Initialises the guest blockchain with given configuration and genesis /// epoch. @@ -144,6 +146,23 @@ pub mod solana_ibc { .map_err(move |err| error!((&err))) } + pub fn deliver_with_chunks<'a, 'info>( + ctx: Context<'a, 'a, 'a, 'info, DeliverWithChunks<'info>>, + ) -> Result<()> { + let msg_chunks = &ctx.accounts.msg_chunks; + let cloned_msg_chunks: &mut MsgChunks = &mut msg_chunks.clone(); + let mut store = storage::from_ctx!(ctx, with accounts); + let mut router = store.clone(); + + let message = ibc::MsgEnvelope::try_from(ibc::Any::from( + cloned_msg_chunks.clone(), + )) + .unwrap(); + ::ibc::core::entrypoint::dispatch(&mut store, &mut router, message) + .map_err(error::Error::ContextError) + .map_err(move |err| error!((&err))) + } + /// Called to set up escrow and mint accounts for given channel and denom. /// Panics if called without `mocks` feature. pub fn mock_init_escrow<'a, 'info>( @@ -263,6 +282,27 @@ pub mod solana_ibc { .map_err(error::Error::ContextError) .map_err(|err| error!((&err))) } + + /// Store messages which are divided into chunk in an account which can be accessed later + /// from the deliver method. + /// + /// Since solana programs have an instruction limit of 1232 bytes, we cannot send arguments + /// with large data. So we divide the data into chunks, call the method below and add it to + /// the account at the specified offset. + pub fn form_msg_chunks( + ctx: Context, + type_url: String, + total_len: u32, + offset: u32, + bytes: Vec, + ) -> Result<()> { + let store = &mut ctx.accounts.msg_chunks; + if store.value.is_empty() { + store.new_alloc(total_len as usize, type_url); + } + store.copy_into(offset.try_into().unwrap(), &bytes); + Ok(()) + } } /// All the storage accounts are initialized here since it is only called once @@ -408,6 +448,49 @@ pub struct Deliver<'info> { system_program: Program<'info, System>, } +#[derive(Accounts, Clone)] +pub struct DeliverWithChunks<'info> { + #[account(mut)] + sender: Signer<'info>, + + receiver: Option>, + + /// The account holding private IBC storage. + #[account(mut,seeds = [SOLANA_IBC_STORAGE_SEED], + bump)] + storage: Account<'info, storage::PrivateStorage>, + + #[account(mut, close = sender)] + msg_chunks: Box>, + + /// The account holding provable IBC storage, i.e. the trie. + /// + /// CHECK: Account’s owner is checked by [`storage::get_provable_from`] + /// function. + #[account(mut, seeds = [TRIE_SEED], + bump)] + trie: UncheckedAccount<'info>, + + /// The guest blockchain data. + #[account(mut, seeds = [CHAIN_SEED], bump)] + chain: Box>, + #[account(mut, seeds = [MINT_ESCROW_SEED], bump)] + /// CHECK: + mint_authority: Option>, + #[account(mut, mint::decimals = 6, mint::authority = mint_authority)] + token_mint: Option>>, + #[account(mut, token::mint = token_mint, token::authority = mint_authority)] + escrow_account: Option>>, + #[account(init_if_needed, payer = sender, + associated_token::mint = token_mint, + associated_token::authority = receiver)] + receiver_token_account: Option>>, + + associated_token_program: Option>, + token_program: Option>, + system_program: Program<'info, System>, +} + #[derive(Accounts)] #[instruction(port_id: ibc::PortId, channel_id_on_b: ibc::ChannelId, base_denom: String)] pub struct MockInitEscrow<'info> { @@ -505,6 +588,19 @@ pub struct SendPacket<'info> { system_program: Program<'info, System>, } +#[derive(Accounts)] +pub struct FormMessageChunks<'info> { + #[account(mut)] + sender: Signer<'info>, + + #[account(init_if_needed, payer = sender, seeds = [MSG_CHUNKS], bump, space = 10240)] + pub msg_chunks: Account<'info, storage::MsgChunks>, + + pub system_program: Program<'info, System>, +} + + + impl ibc::Router for storage::IbcStorage<'_, '_> { // fn get_route(&self, module_id: &ibc::ModuleId) -> Option<&dyn ibc::Module> { diff --git a/solana/solana-ibc/programs/solana-ibc/src/storage.rs b/solana/solana-ibc/programs/solana-ibc/src/storage.rs index dc56126d..bdbe8611 100644 --- a/solana/solana-ibc/programs/solana-ibc/src/storage.rs +++ b/solana/solana-ibc/programs/solana-ibc/src/storage.rs @@ -379,6 +379,40 @@ pub(crate) struct IbcStorageInner<'a, 'b> { pub chain: &'a mut crate::chain::ChainData, } +/// Struct containing message chunks +/// +/// The struct consists of chunks of message data having the first 4 bytes +/// indicating the total size of the message +#[account] +#[derive(Debug)] +pub struct MsgChunks { + pub type_url: String, + pub value: Vec, +} + +impl MsgChunks { + /// Creates a new msg vector of size `total_length + 4` with 0s where the + /// first 4 bytes are allocated for the total size of the message + pub fn new_alloc(&mut self, total_len: usize, type_url: String) { + let msg = vec![0; total_len + 4]; + self.value = msg; + self.type_url = type_url; + let total_len_in_bytes = (total_len as u32).to_be_bytes(); + self.copy_into(0, &total_len_in_bytes); + } + + pub fn copy_into(&mut self, position: usize, data: &[u8]) { + msg!("data size -> {} {}", data.len(), self.value.len()); + self.value[position..position + data.len()].copy_from_slice(data); + } +} + +impl From for ibc::Any { + fn from(value: MsgChunks) -> Self { + ibc::Any { type_url: value.type_url, value: value.value[4..].to_vec() } + } +} + /// A reference-counted reference to the IBC storage. /// /// Uses inner-mutability via [`RefCell`] to allow modifications to the storage. diff --git a/solana/solana-ibc/programs/solana-ibc/src/tests.rs b/solana/solana-ibc/programs/solana-ibc/src/tests.rs index 8294f26d..c0e5980a 100644 --- a/solana/solana-ibc/programs/solana-ibc/src/tests.rs +++ b/solana/solana-ibc/programs/solana-ibc/src/tests.rs @@ -4,6 +4,8 @@ use std::str::FromStr; use std::thread::sleep; use std::time::Duration; +use ::ibc::primitives::proto::Protobuf; +use ::ibc::primitives::Msg; use anchor_client::anchor_lang::system_program; use anchor_client::solana_client::rpc_client::RpcClient; use anchor_client::solana_client::rpc_config::RpcSendTransactionConfig; @@ -12,10 +14,12 @@ use anchor_client::solana_sdk::compute_budget::ComputeBudgetInstruction; use anchor_client::solana_sdk::pubkey::Pubkey; use anchor_client::solana_sdk::signature::{Keypair, Signature, Signer}; use anchor_client::{Client, Cluster}; +use anchor_lang::prelude::borsh; use anchor_lang::solana_program::instruction::AccountMeta; -use anchor_lang::ToAccountMetas; +use anchor_lang::{AnchorDeserialize, ToAccountMetas}; use anchor_spl::associated_token::get_associated_token_address; use anyhow::Result; +use ibc::{ClientId, MsgEnvelope, MsgUpdateClient}; use crate::ibc::ClientStateCommon; use crate::storage::PrivateStorage; @@ -110,20 +114,14 @@ impl ToAccountMetas for DeliverWithRemainingAccounts { #[test] #[ignore = "Requires local validator to run"] fn anchor_test_deliver() -> Result<()> { - let authority = Rc::new(Keypair::new()); - println!("This is pubkey {}", authority.pubkey().to_string()); - let lamports = 2_000_000_000; - - let client = Client::new_with_options( - Cluster::Localnet, - authority.clone(), - CommitmentConfig::processed(), - ); - let program = client.program(crate::ID).unwrap(); - + let (authority, _client, program, _airdrop_signature) = + setup_client_program( + Keypair::new(), + Cluster::Localnet, + CommitmentConfig::processed(), + true, + ); let sol_rpc_client = program.rpc(); - let _airdrop_signature = - airdrop(&sol_rpc_client, authority.pubkey(), lamports); // Build, sign, and send program instruction let storage = Pubkey::find_program_address( @@ -134,6 +132,8 @@ fn anchor_test_deliver() -> Result<()> { let trie = Pubkey::find_program_address(&[crate::TRIE_SEED], &crate::ID).0; let chain = Pubkey::find_program_address(&[crate::CHAIN_SEED], &crate::ID).0; + let msg_chunks = + Pubkey::find_program_address(&[crate::MSG_CHUNKS], &crate::ID).0; /* * Initialise chain @@ -182,18 +182,55 @@ fn anchor_test_deliver() -> Result<()> { println!("\nCreating Mock Client"); let (mock_client_state, mock_cs_state) = create_mock_client_and_cs_state(); - let message = make_message!( - ibc::MsgCreateClient::new( - ibc::Any::from(mock_client_state), - ibc::Any::from(mock_cs_state), - ibc::Signer::from(authority.pubkey().to_string()), - ), - ibc::ClientMsg::CreateClient, - ibc::MsgEnvelope::Client, + // let message = make_message!( + // ibc::MsgCreateClient::new( + // ibc::Any::from(mock_client_state), + // ibc::Any::from(mock_cs_state.clone()), + // ibc::Signer::from(authority.pubkey().to_string()), + // ), + // ibc::ClientMsg::CreateClient, + // ibc::MsgEnvelope::Client, + // ); + + let test_msg = ibc::MsgCreateClient::new( + ibc::Any::from(mock_client_state), + ibc::Any::from(mock_cs_state), + ibc::Signer::from(authority.pubkey().to_string()), ); + + let serialized_message = test_msg.clone().encode_vec(); + + let length = serialized_message.len(); + let chunk_size = 100; + let mut offset = 4; + + for i in serialized_message.chunks(chunk_size) { + let sig = program + .request() + .accounts(accounts::FormMessageChunks { + sender: authority.pubkey(), + msg_chunks, + system_program: system_program::ID, + }) + .args(instruction::FormMsgChunks { + total_len: length as u32, + offset: offset as u32, + bytes: i.to_vec(), + type_url: test_msg.type_url(), + }) + .payer(authority.clone()) + .signer(&*authority) + .send_with_spinner_and_config(RpcSendTransactionConfig { + skip_preflight: true, + ..RpcSendTransactionConfig::default() + })?; + println!(" Signature for message chunks : {sig}"); + offset += chunk_size; + } + let sig = program .request() - .accounts(accounts::Deliver { + .accounts(accounts::DeliverWithChunks { sender: authority.pubkey(), receiver: None, storage, @@ -206,8 +243,9 @@ fn anchor_test_deliver() -> Result<()> { receiver_token_account: None, associated_token_program: None, token_program: None, + msg_chunks, }) - .args(instruction::Deliver { message }) + .args(instruction::DeliverWithChunks {}) .payer(authority.clone()) .signer(&*authority) .send_with_spinner_and_config(RpcSendTransactionConfig { @@ -677,6 +715,173 @@ fn anchor_test_deliver() -> Result<()> { Ok(()) } +#[test] +#[ignore = "Requires local validator to run"] +fn test_deliver_chunks() -> Result<()> { + let (authority, _client, program, _airdrop_signature) = + setup_client_program( + Keypair::new(), + Cluster::Localnet, + CommitmentConfig::processed(), + true, + ); + + let msg_chunks = + Pubkey::find_program_address(&[crate::MSG_CHUNKS], &crate::ID).0; + + let msg = MsgUpdateClient { + client_id: ClientId::from_str("07-tendermint-1").unwrap(), + client_message: ::ibc::primitives::proto::Any { + type_url: "/ibc.lightclients.tendermint.v1.ClientMessage" + .to_owned(), + value: vec![ + 10, 38, 47, 105, 98, 99, 46, 108, 105, 103, 104, 116, 99, 108, + 105, 101, 110, 116, 115, 46, 116, 101, 110, 100, 101, 114, 109, + 105, 110, 116, 46, 118, 49, 46, 72, 101, 97, 100, 101, 114, 18, + 238, 6, 10, 202, 4, 10, 141, 3, 10, 2, 8, 11, 18, 6, 116, 101, + 115, 116, 45, 49, 24, 228, 1, 34, 12, 8, 166, 239, 150, 172, 6, + 16, 248, 214, 168, 175, 3, 42, 72, 10, 32, 163, 207, 132, 246, + 46, 57, 175, 243, 154, 230, 28, 49, 166, 80, 47, 101, 26, 25, + 167, 48, 251, 79, 183, 120, 220, 249, 104, 20, 75, 18, 121, + 220, 18, 36, 8, 1, 18, 32, 190, 87, 215, 130, 108, 157, 149, + 10, 117, 231, 205, 219, 12, 175, 3, 76, 11, 17, 138, 9, 28, 37, + 199, 131, 252, 206, 185, 173, 193, 143, 227, 33, 50, 32, 132, + 165, 67, 180, 168, 210, 149, 49, 160, 147, 126, 116, 112, 232, + 205, 149, 243, 130, 193, 222, 122, 12, 27, 84, 242, 5, 161, + 200, 150, 96, 209, 60, 58, 32, 227, 176, 196, 66, 152, 252, 28, + 20, 154, 251, 244, 200, 153, 111, 185, 36, 39, 174, 65, 228, + 100, 155, 147, 76, 164, 149, 153, 27, 120, 82, 184, 85, 66, 32, + 119, 230, 213, 242, 99, 59, 194, 128, 185, 41, 83, 174, 149, + 43, 248, 129, 25, 232, 178, 199, 110, 149, 126, 23, 45, 95, 54, + 23, 64, 17, 145, 181, 74, 32, 119, 230, 213, 242, 99, 59, 194, + 128, 185, 41, 83, 174, 149, 43, 248, 129, 25, 232, 178, 199, + 110, 149, 126, 23, 45, 95, 54, 23, 64, 17, 145, 181, 82, 32, 4, + 128, 145, 188, 125, 220, 40, 63, 119, 191, 191, 145, 215, 60, + 68, 218, 88, 195, 223, 138, 156, 188, 134, 116, 5, 216, 183, + 243, 218, 173, 162, 47, 90, 32, 255, 183, 136, 77, 148, 106, + 121, 179, 78, 128, 220, 94, 169, 3, 40, 24, 46, 145, 149, 126, + 249, 194, 220, 159, 9, 22, 55, 92, 227, 111, 193, 135, 98, 32, + 227, 176, 196, 66, 152, 252, 28, 20, 154, 251, 244, 200, 153, + 111, 185, 36, 39, 174, 65, 228, 100, 155, 147, 76, 164, 149, + 153, 27, 120, 82, 184, 85, 106, 32, 227, 176, 196, 66, 152, + 252, 28, 20, 154, 251, 244, 200, 153, 111, 185, 36, 39, 174, + 65, 228, 100, 155, 147, 76, 164, 149, 153, 27, 120, 82, 184, + 85, 114, 20, 197, 236, 6, 68, 250, 32, 151, 158, 18, 66, 74, + 86, 41, 57, 249, 233, 235, 109, 26, 215, 18, 183, 1, 8, 228, 1, + 26, 72, 10, 32, 77, 231, 232, 136, 53, 222, 130, 207, 199, 138, + 166, 59, 173, 215, 106, 153, 129, 106, 241, 53, 113, 77, 188, + 80, 79, 25, 76, 28, 48, 21, 125, 71, 18, 36, 8, 1, 18, 32, 93, + 4, 216, 112, 164, 60, 48, 184, 86, 132, 54, 104, 213, 52, 99, + 155, 105, 155, 7, 110, 132, 153, 225, 219, 245, 33, 115, 154, + 148, 30, 120, 13, 34, 104, 8, 2, 18, 20, 197, 236, 6, 68, 250, + 32, 151, 158, 18, 66, 74, 86, 41, 57, 249, 233, 235, 109, 26, + 215, 26, 12, 8, 171, 239, 150, 172, 6, 16, 176, 140, 197, 204, + 3, 34, 64, 166, 133, 186, 198, 251, 171, 42, 171, 175, 37, 139, + 233, 142, 183, 17, 66, 52, 228, 35, 153, 94, 79, 215, 205, 45, + 8, 192, 196, 246, 8, 156, 34, 160, 115, 245, 111, 188, 42, 99, + 214, 237, 255, 230, 133, 201, 191, 218, 222, 141, 250, 160, + 225, 206, 45, 4, 194, 219, 47, 194, 171, 62, 67, 117, 6, 18, + 138, 1, 10, 64, 10, 20, 197, 236, 6, 68, 250, 32, 151, 158, 18, + 66, 74, 86, 41, 57, 249, 233, 235, 109, 26, 215, 18, 34, 10, + 32, 11, 93, 18, 110, 141, 126, 60, 32, 236, 136, 158, 223, 95, + 73, 175, 130, 55, 184, 247, 241, 143, 50, 115, 96, 210, 46, + 135, 104, 119, 246, 35, 194, 24, 128, 148, 235, 220, 3, 18, 64, + 10, 20, 197, 236, 6, 68, 250, 32, 151, 158, 18, 66, 74, 86, 41, + 57, 249, 233, 235, 109, 26, 215, 18, 34, 10, 32, 11, 93, 18, + 110, 141, 126, 60, 32, 236, 136, 158, 223, 95, 73, 175, 130, + 55, 184, 247, 241, 143, 50, 115, 96, 210, 46, 135, 104, 119, + 246, 35, 194, 24, 128, 148, 235, 220, 3, 24, 128, 148, 235, + 220, 3, 26, 5, 8, 1, 16, 228, 1, 34, 138, 1, 10, 64, 10, 20, + 197, 236, 6, 68, 250, 32, 151, 158, 18, 66, 74, 86, 41, 57, + 249, 233, 235, 109, 26, 215, 18, 34, 10, 32, 11, 93, 18, 110, + 141, 126, 60, 32, 236, 136, 158, 223, 95, 73, 175, 130, 55, + 184, 247, 241, 143, 50, 115, 96, 210, 46, 135, 104, 119, 246, + 35, 194, 24, 128, 148, 235, 220, 3, 18, 64, 10, 20, 197, 236, + 6, 68, 250, 32, 151, 158, 18, 66, 74, 86, 41, 57, 249, 233, + 235, 109, 26, 215, 18, 34, 10, 32, 11, 93, 18, 110, 141, 126, + 60, 32, 236, 136, 158, 223, 95, 73, 175, 130, 55, 184, 247, + 241, 143, 50, 115, 96, 210, 46, 135, 104, 119, 246, 35, 194, + 24, 128, 148, 235, 220, 3, 24, 128, 148, 235, 220, 3, + ], + }, + signer: String::from("oxyzEsUj9CV6HsqPCUZqVwrFJJvpd9iCBrPdzTBWLBb") + .into(), + }; + + let msg_envelope = + MsgEnvelope::Client(ibc::ClientMsg::UpdateClient(msg.clone())); + + let serialized_message = borsh::to_vec(&msg_envelope).unwrap(); + + println!("This is serialized message length {}", serialized_message.len()); + + let length = serialized_message.len(); + let chunk_size = 100; + let mut offset = 4; + + for i in serialized_message.chunks(chunk_size) { + let sig = program + .request() + .accounts(accounts::FormMessageChunks { + sender: authority.pubkey(), + msg_chunks, + system_program: system_program::ID, + }) + .args(instruction::FormMsgChunks { + total_len: length as u32, + offset: offset as u32, + bytes: i.to_vec(), + type_url: msg.type_url(), + }) + .payer(authority.clone()) + .signer(&*authority) + .send_with_spinner_and_config(RpcSendTransactionConfig { + skip_preflight: true, + ..RpcSendTransactionConfig::default() + })?; + println!(" Signature for message chunks : {sig}"); + offset += chunk_size; + } + + let final_msg: crate::storage::MsgChunks = + program.account(msg_chunks).unwrap(); + + let serialized_msg_envelope = &final_msg.value[4..]; + let unserialized_msg = + MsgEnvelope::try_from_slice(serialized_msg_envelope).unwrap(); + assert_eq!(unserialized_msg, msg_envelope); + Ok(()) +} + +fn setup_client_program( + authority: Keypair, + cluster: Cluster, + commitment_config: CommitmentConfig, + with_airdrop: bool, +) -> ( + Rc, + Client>, + anchor_client::Program>, + Option, +) { + let authority = Rc::new(authority); + println!("This is pubkey {}", authority.pubkey().to_string()); + let lamports = 2_000_000_000; + + let client = + Client::new_with_options(cluster, authority.clone(), commitment_config); + let program = client.program(crate::ID).unwrap(); + + if with_airdrop { + let sol_rpc_client = program.rpc(); + let airdrop_signature = + airdrop(&sol_rpc_client, authority.pubkey(), lamports); + return (authority, client, program, Some(airdrop_signature)); + } + + (authority, client, program, None) +} + fn construct_packet_from_denom( port_id: ibc::PortId, // Channel id used to define if its source chain or destination chain (in