diff --git a/common/blockchain/src/candidates.rs b/common/blockchain/src/candidates.rs index ad0dd03a..1772e8af 100644 --- a/common/blockchain/src/candidates.rs +++ b/common/blockchain/src/candidates.rs @@ -8,7 +8,9 @@ mod tests; /// /// Whenever epoch changes, candidates with most stake are included in /// validators set. -#[derive(Clone, PartialEq, Eq)] +#[derive( + Clone, PartialEq, Eq, borsh::BorshSerialize, borsh::BorshDeserialize, +)] pub struct Candidates { /// Maximum number of validators in a validator set. max_validators: NonZeroU16, @@ -29,7 +31,9 @@ pub struct Candidates { } /// A candidate to become a validator. -#[derive(Clone, PartialEq, Eq)] +#[derive( + Clone, PartialEq, Eq, borsh::BorshSerialize, borsh::BorshDeserialize, +)] struct Candidate { /// Public key of the candidate. pubkey: PK, diff --git a/common/blockchain/src/config.rs b/common/blockchain/src/config.rs index a25e263a..6e7348ec 100644 --- a/common/blockchain/src/config.rs +++ b/common/blockchain/src/config.rs @@ -4,7 +4,7 @@ use core::num::{NonZeroU128, NonZeroU16}; /// /// Those are not encoded within a blockchain and only matter when generating /// a new block. -// TODO(mina86): Do those configuration options make sense? +#[derive(Clone, Debug, borsh::BorshSerialize, borsh::BorshDeserialize)] pub struct Config { /// Minimum number of validators allowed in an epoch. /// diff --git a/common/blockchain/src/manager.rs b/common/blockchain/src/manager.rs index e2be70c4..99400dd9 100644 --- a/common/blockchain/src/manager.rs +++ b/common/blockchain/src/manager.rs @@ -8,6 +8,7 @@ use lib::hash::CryptoHash; pub use crate::candidates::UpdateCandidateError; +#[derive(Clone, Debug, borsh::BorshSerialize, borsh::BorshDeserialize)] pub struct ChainManager { /// Configuration specifying limits for block generation. config: crate::Config, @@ -35,6 +36,7 @@ pub struct ChainManager { /// Pending block waiting for signatures. /// /// Once quorum of validators sign the block it’s promoted to the current block. +#[derive(Clone, Debug, borsh::BorshSerialize, borsh::BorshDeserialize)] struct PendingBlock { /// The block that waits for signatures. next_block: crate::Block, diff --git a/solana/solana-ibc/programs/solana-ibc/src/chain.rs b/solana/solana-ibc/programs/solana-ibc/src/chain.rs new file mode 100644 index 00000000..ada7255f --- /dev/null +++ b/solana/solana-ibc/programs/solana-ibc/src/chain.rs @@ -0,0 +1,203 @@ +use anchor_lang::prelude::*; +use anchor_lang::solana_program; +pub use blockchain::Config; + +use crate::error::Error; +use crate::{events, storage}; + +type Result = core::result::Result; + +pub type Epoch = blockchain::Epoch; +pub type Block = blockchain::Block; +pub type Manager = blockchain::ChainManager; +pub use crate::ed25519::{PubKey, Signature, Verifier}; + +/// Guest blockchain data held in Solana account. +#[account] +pub struct ChainData { + inner: Option, +} + +impl ChainData { + /// Initialises a new guest blockchain with given configuration and genesis + /// epoch. + /// + /// Fails if the chain is already initialised. + pub fn initialise( + &mut self, + trie: &mut storage::AccountTrie, + config: Config, + genesis_epoch: Epoch, + ) -> Result { + let (height, timestamp) = host_head()?; + let genesis = Block::generate_genesis( + 1.into(), + height, + timestamp, + trie.hash().clone(), + genesis_epoch, + ) + .map_err(|err| Error::Internal(err.into()))?; + let manager = + Manager::new(config, genesis.clone()).map_err(Error::from)?; + if self.inner.is_some() { + return Err(Error::ChainAlreadyInitialised.into()); + } + let last_check_height = manager.head().1.host_height; + let inner = + self.inner.insert(ChainInner { last_check_height, manager }); + + let (finalised, head) = inner.manager.head(); + assert!(finalised); + events::emit(events::Initialised { + genesis: events::NewBlock { hash: &head.calc_hash(), block: head }, + }) + .map_err(ProgramError::BorshIoError)?; + Ok(()) + } + + /// Generates a new guest block. + /// + /// Fails if a new block couldn’t be created. This can happen if head of the + /// guest blockchain is pending (not signed by quorum of validators) or criteria + /// for creating a new block haven’t been met (e.g. state hasn’t changed). + /// + /// This is intended as handling an explicit contract call for generating a new + /// block. In contrast, [`maybe_generate_block`] is intended to create a new + /// 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) + } + + /// Generates a new guest block if possible. + /// + /// Contrary to [`generate_block`] this function won’t fail if new block could + /// not be created. + /// + /// This is intended to create a new block opportunistically at the beginning of + /// handling any smart contract request. + pub fn maybe_generate_block( + &mut self, + trie: &storage::AccountTrie, + ) -> Result { + self.generate_block_impl(trie, false) + } + + /// Attempts generating a new guest block. + /// + /// Implementation of [`generate_block`] and [`maybe_generate_block`] functions. + /// If `force` is `true` and new block is not generated, returns an error. + /// Otherwise, failure to generate a new block (e.g. because there’s one pending + /// or state hasn’t changed) is silently ignored. + fn generate_block_impl( + &mut self, + trie: &storage::AccountTrie, + force: bool, + ) -> Result { + 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 { + return if force { + Err(Error::GenerationAlreadyAttempted.into()) + } else { + Ok(()) + }; + } + inner.last_check_height = height; + let res = inner.manager.generate_next( + height, + timestamp, + trie.hash().clone(), + false, + ); + match res { + Ok(()) => { + let (finalised, head) = inner.manager.head(); + assert!(!finalised); + events::emit(events::NewBlock { + hash: &head.calc_hash(), + block: head, + }) + .map_err(ProgramError::BorshIoError)?; + Ok(()) + } + Err(err) if force => Err(into_error(err)), + Err(_) => Ok(()), + } + } + + /// Submits a signature for the pending block. + /// + /// If quorum of signatures has been reached returns `true`. Otherwise + /// returns `false`. This operation is idempotent. Submitting the same + /// signature multiple times has no effect (other than wasting gas). + pub fn sign_block( + &mut self, + pubkey: PubKey, + signature: &Signature, + verifier: &Verifier, + ) -> Result { + let manager = &mut self.get_mut()?.manager; + let res = manager + .add_signature(pubkey.clone(), signature, verifier) + .map_err(into_error)?; + + let mut hash = None; + if res.got_new_signature() { + let hash = hash.get_or_insert_with(|| manager.head().1.calc_hash()); + events::emit(events::BlockSigned { + block_hash: hash, + pubkey: &pubkey, + }) + .map_err(ProgramError::BorshIoError)?; + } + if res.got_quorum() { + let hash = hash.get_or_insert_with(|| manager.head().1.calc_hash()); + events::emit(events::BlockFinalised { block_hash: hash }) + .map_err(ProgramError::BorshIoError)?; + } + Ok(res.got_quorum()) + } + + /// Updates validator’s stake. + pub fn set_stake(&mut self, pubkey: PubKey, amount: u128) -> Result<()> { + self.get_mut()? + .manager + .update_candidate(pubkey, amount) + .map_err(into_error) + } + + /// Returns mutable the inner chain data if it has been initialised. + fn get_mut(&mut self) -> Result<&mut ChainInner> { + self.inner.as_mut().ok_or_else(|| Error::ChainNotInitialised.into()) + } +} + +fn into_error>(err: E) -> anchor_lang::error::Error { + err.into().into() +} + +/// The inner chain data +#[derive(Clone, Debug, borsh::BorshSerialize, borsh::BorshDeserialize)] +struct ChainInner { + /// Last Solana block at which last check for new guest block generation was + /// performed. + last_check_height: blockchain::HostHeight, + + /// 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())) +} diff --git a/solana/solana-ibc/programs/solana-ibc/src/ed25519.rs b/solana/solana-ibc/programs/solana-ibc/src/ed25519.rs index efdc4902..281aca40 100644 --- a/solana/solana-ibc/programs/solana-ibc/src/ed25519.rs +++ b/solana/solana-ibc/programs/solana-ibc/src/ed25519.rs @@ -1,6 +1,7 @@ use anchor_lang::prelude::{borsh, err}; -use anchor_lang::solana_program::account_info::AccountInfo; -use anchor_lang::solana_program::{ed25519_program, sysvar}; +use anchor_lang::solana_program; +use solana_program::account_info::AccountInfo; +use solana_program::{ed25519_program, sysvar}; /// An Ed25519 public key used by guest validators to sign guest blocks. #[derive( @@ -12,13 +13,25 @@ use anchor_lang::solana_program::{ed25519_program, sysvar}; Hash, borsh::BorshSerialize, borsh::BorshDeserialize, + derive_more::From, + derive_more::Into, )] -pub struct PubKey([u8; Self::LENGTH]); +pub struct PubKey([u8; 32]); impl PubKey { pub const LENGTH: usize = 32; } +impl From for PubKey { + fn from(pubkey: solana_program::pubkey::Pubkey) -> Self { + Self(pubkey.to_bytes()) + } +} + +impl From for solana_program::pubkey::Pubkey { + fn from(pubkey: PubKey) -> Self { Self::from(pubkey.0) } +} + impl blockchain::PubKey for PubKey { type Signature = Signature; } @@ -33,8 +46,10 @@ impl blockchain::PubKey for PubKey { Hash, borsh::BorshSerialize, borsh::BorshDeserialize, + derive_more::From, + derive_more::Into, )] -pub struct Signature([u8; Self::LENGTH]); +pub struct Signature([u8; 64]); impl Signature { pub const LENGTH: usize = 64; @@ -61,7 +76,6 @@ impl Verifier { /// Returns error if `ix_sysver` is not `AccountInfo` for the Instruction /// sysvar, there was no instruction prior to the current on or the previous /// instruction was not a call to the Ed25519 native program. - #[allow(dead_code)] pub fn new(ix_sysvar: &AccountInfo<'_>) -> anchor_lang::Result { let ix = sysvar::instructions::get_instruction_relative(-1, ix_sysvar)?; if ed25519_program::check_id(&ix.program_id) { diff --git a/solana/solana-ibc/programs/solana-ibc/src/error.rs b/solana/solana-ibc/programs/solana-ibc/src/error.rs index b4c439f9..c62d4231 100644 --- a/solana/solana-ibc/programs/solana-ibc/src/error.rs +++ b/solana/solana-ibc/programs/solana-ibc/src/error.rs @@ -1,37 +1,136 @@ +use blockchain::manager; + /// Error returned when handling a request. // Note: When changing variants in the enum, try to preserve indexes of each // variant. The position is translated into error code returned by Anchor and // keeping them consistent makes things easier. #[derive(strum::EnumDiscriminants, strum::IntoStaticStr)] #[strum_discriminants(repr(u32))] -#[allow(clippy::enum_variant_names)] pub(crate) enum Error { + /// Internal error which ‘should never happen’. + Internal(&'static str), + /// Error handling an IBC request. RouterError(ibc::core::RouterError), + + /// Guest block hasn’t been initialised yet. + ChainNotInitialised, + + /// Guest block has already been initialised. + ChainAlreadyInitialised, + + /// Guest block generation has already been attempted this Solana block. + /// The guest block can be generated only once per host block. + GenerationAlreadyAttempted, + + /// Unable to generate a new guest block because there’s already a pending + /// guest block. + HasPendingBlock, + + /// Unable to generate a new guest block because the current head is too + /// young. + HeadBlockTooYoung, + + /// Unable to generate a new guest block because the state hasn’t changed. + UnchangedGuestState, + + /// Could not identify block. + UnknownBlock, + + /// The signature is invalid. + BadSignature, + + /// The signer is not a validator for the given block. + BadValidator, + + /// Candidate’s stake is below required minimum. + NotEnoughValidatorStake, + + /// After removing a candidate or reducing candidate’s stake, the total + /// stake would fall below required minimum. + NotEnoughTotalStake, + + /// After removing a candidate, the total number of validators would fall + /// below required minimum. + NotEnoughValidators, } impl Error { pub fn name(&self) -> String { <&'static str>::from(self).into() } + pub fn code(&self) -> u32 { + anchor_lang::error::ERROR_CODE_OFFSET + + ErrorDiscriminants::from(self) as u32 + } } impl core::fmt::Display for Error { fn fmt(&self, fmtr: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { + Self::Internal(msg) => fmtr.write_str(msg.as_ref()), Self::RouterError(err) => err.fmt(fmtr), + err => fmtr.write_str(&err.name()), } } } impl From for u32 { - fn from(err: Error) -> u32 { - let code = ErrorDiscriminants::from(err) as u32; - anchor_lang::error::ERROR_CODE_OFFSET + code - } + fn from(err: Error) -> u32 { err.code() } } impl From<&Error> for u32 { - fn from(err: &Error) -> u32 { - let code = ErrorDiscriminants::from(err) as u32; - anchor_lang::error::ERROR_CODE_OFFSET + code + fn from(err: &Error) -> u32 { err.code() } +} + +impl From for Error { + fn from(_: manager::BadGenesis) -> Self { Self::Internal("BadGenesis") } +} + +impl From for Error { + fn from(err: manager::GenerateError) -> Self { + match err { + manager::GenerateError::HasPendingBlock => Self::HasPendingBlock, + manager::GenerateError::BlockTooYoung => Self::HeadBlockTooYoung, + manager::GenerateError::UnchangedState => Self::UnchangedGuestState, + manager::GenerateError::Inner(err) => Self::Internal(err.into()), + } + } +} + +impl From for Error { + fn from(err: manager::AddSignatureError) -> Self { + match err { + manager::AddSignatureError::NoPendingBlock => Self::UnknownBlock, + manager::AddSignatureError::BadSignature => Self::BadSignature, + manager::AddSignatureError::BadValidator => Self::BadValidator, + } + } +} + +impl From for Error { + fn from(err: manager::UpdateCandidateError) -> Self { + use manager::UpdateCandidateError as Err; + match err { + Err::NotEnoughValidatorStake => Self::NotEnoughValidatorStake, + Err::NotEnoughTotalStake => Self::NotEnoughTotalStake, + Err::NotEnoughValidators => Self::NotEnoughValidators, + } + } +} + +impl From for anchor_lang::error::AnchorError { + fn from(err: Error) -> Self { + Self { + error_name: err.name(), + error_code_number: err.code(), + error_msg: err.to_string(), + error_origin: None, + compared_values: None, + } + } +} + +impl From for anchor_lang::error::Error { + fn from(err: Error) -> Self { + Self::from(anchor_lang::error::AnchorError::from(err)) } } diff --git a/solana/solana-ibc/programs/solana-ibc/src/events.rs b/solana/solana-ibc/programs/solana-ibc/src/events.rs index 2e87e1f1..5b845a97 100644 --- a/solana/solana-ibc/programs/solana-ibc/src/events.rs +++ b/solana/solana-ibc/programs/solana-ibc/src/events.rs @@ -1,22 +1,52 @@ use anchor_lang::prelude::borsh; use anchor_lang::solana_program; +use lib::hash::CryptoHash; /// Possible events emitted by the smart contract. /// /// The events are logged in their borsh-serialised form. -#[derive( - Clone, - PartialEq, - Eq, - borsh::BorshSerialize, - borsh::BorshDeserialize, - derive_more::From, -)] -pub enum Event { +#[derive(Clone, PartialEq, Eq, borsh::BorshSerialize, derive_more::From)] +pub enum Event<'a> { IbcEvent(ibc::core::events::IbcEvent), + Initialised(Initialised<'a>), + NewBlock(NewBlock<'a>), + BlockSigned(BlockSigned<'a>), + BlockFinalised(BlockFinalised<'a>), } -impl Event { +/// Event emitted once blockchain is implemented. +#[derive(Clone, PartialEq, Eq, borsh::BorshSerialize, derive_more::From)] +pub struct Initialised<'a> { + /// Genesis block of the chain. + pub genesis: NewBlock<'a>, +} + +/// Event emitted once a new block is generated. +#[derive(Clone, PartialEq, Eq, borsh::BorshSerialize, derive_more::From)] +pub struct NewBlock<'a> { + /// Hash of the new block. + pub hash: &'a CryptoHash, + /// The new block. + pub block: &'a crate::chain::Block, +} + +/// Event emitted once a new block is generated. +#[derive(Clone, PartialEq, Eq, borsh::BorshSerialize, derive_more::From)] +pub struct BlockSigned<'a> { + /// Hash of the block to which signature was added. + pub block_hash: &'a CryptoHash, + /// Public key of the validator whose signature was added. + pub pubkey: &'a crate::chain::PubKey, +} + +/// Event emitted once a block is finalised. +#[derive(Clone, PartialEq, Eq, borsh::BorshSerialize, derive_more::From)] +pub struct BlockFinalised<'a> { + /// Hash of the block to which signature was added. + pub block_hash: &'a CryptoHash, +} + +impl Event<'_> { pub fn emit(&self) -> Result<(), String> { borsh::BorshSerialize::try_to_vec(self) .map(|data| solana_program::log::sol_log_data(&[data.as_slice()])) @@ -24,6 +54,6 @@ impl Event { } } -pub fn emit(event: impl Into) -> Result<(), String> { +pub fn emit<'a>(event: impl Into>) -> Result<(), String> { event.into().emit() } diff --git a/solana/solana-ibc/programs/solana-ibc/src/lib.rs b/solana/solana-ibc/programs/solana-ibc/src/lib.rs index 1d437942..f47a8f33 100644 --- a/solana/solana-ibc/programs/solana-ibc/src/lib.rs +++ b/solana/solana-ibc/programs/solana-ibc/src/lib.rs @@ -1,24 +1,29 @@ +#![allow(clippy::enum_variant_names)] // anchor_lang::error::Error and anchor_lang::Result is ≥ 160 bytes and there’s // not much we can do about it. #![allow(clippy::result_large_err)] + extern crate alloc; use anchor_lang::prelude::*; +use anchor_lang::solana_program; use ibc::core::ics24_host::identifier::PortId; use ibc::core::router::{Module, ModuleId, Router}; +use ibc::core::MsgEnvelope; -const SOLANA_IBC_STORAGE_SEED: &[u8] = b"solana_ibc_storage"; -const TRIE_SEED: &[u8] = b"trie"; +const CHAIN_SEED: &[u8] = b"chain"; const PACKET_SEED: &[u8] = b"packet"; +const SOLANA_IBC_STORAGE_SEED: &[u8] = b"private"; +const TRIE_SEED: &[u8] = b"trie"; const CONNECTION_ID_PREFIX: &str = "connection-"; const CHANNEL_ID_PREFIX: &str = "channel-"; -use ibc::core::MsgEnvelope; use crate::storage::IBCPackets; declare_id!("EnfDJsAK7BGgetnmKzBx86CsgC5kfSPcsktFCQ4YLC81"); +mod chain; mod client_state; mod consensus_state; mod ed25519; @@ -38,6 +43,74 @@ mod validation_context; pub mod solana_ibc { use super::*; + /// Initialises the guest blockchain with given configuration and genesis + /// epoch. + pub fn initialise( + ctx: Context, + config: chain::Config, + genesis_epoch: chain::Epoch, + ) -> Result<()> { + let mut provable = + storage::get_provable_from(&ctx.accounts.trie, "trie")?; + ctx.accounts.chain.initialise(&mut provable, config, genesis_epoch) + } + + /// Attempts to generate a new guest block. + /// + /// The request fails if there’s a pending guest block or conditions for + /// creating a new block haven’t been met. + /// + /// TODO(mina86): Per the guest blockchain paper, generating a guest block + /// should offer rewards to account making the generate block call. This is + /// currently not implemented and will be added at a later time. + pub fn generate_block(ctx: Context) -> Result<()> { + let provable = storage::get_provable_from(&ctx.accounts.trie, "trie")?; + ctx.accounts.chain.generate_block(&provable) + } + + /// Accepts pending block’s signature from the validator. + /// + /// Sender of the transaction is the validator of the guest blockchain. + /// Their Solana key is used as the key in the guest blockchain. + /// + /// `signature` is signature of the pending guest block made with private + /// key corresponding to the sender account’s public key. + /// + /// TODO(mina86): At the moment the call doesn’t provide rewards and doesn’t + /// allow to submit signatures for finalised guest blocks. Those features + /// will be added at a later time. + pub fn sign_block( + ctx: Context, + signature: [u8; ed25519::Signature::LENGTH], + ) -> Result<()> { + let provable = storage::get_provable_from(&ctx.accounts.trie, "trie")?; + let verifier = ed25519::Verifier::new(&ctx.accounts.ix_sysvar)?; + if ctx.accounts.chain.sign_block( + (*ctx.accounts.sender.key).into(), + &signature.into(), + &verifier, + )? { + ctx.accounts.chain.maybe_generate_block(&provable)?; + } + Ok(()) + } + + /// Changes stake of a guest validator. + /// + /// Sender’s stake will be set to the given amount. Note that if sender is + /// a validator in current epoch, their stake in current epoch won’t change. + /// This also means that reducing stake takes effect only after the epoch + /// changes. + /// + /// TODO(mina86): At the moment we’re operating on pretend tokens and each + /// validator can set whatever stake they want. This is purely for testing + /// and not intended for production use. + pub fn set_stake(ctx: Context, amount: u128) -> Result<()> { + let provable = storage::get_provable_from(&ctx.accounts.trie, "trie")?; + ctx.accounts.chain.maybe_generate_block(&provable)?; + ctx.accounts.chain.set_stake((*ctx.accounts.sender.key).into(), amount) + } + pub fn deliver( ctx: Context, message: ibc::core::MsgEnvelope, @@ -50,6 +123,11 @@ pub mod solana_ibc { let provable = storage::get_provable_from(&ctx.accounts.trie, "trie")?; let packets: &mut IBCPackets = &mut ctx.accounts.packets; + // 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)?; + let mut store = storage::IbcStorage::new(storage::IbcStorageInner { private, provable, @@ -84,6 +162,49 @@ pub mod solana_ibc { } } +#[derive(Accounts)] +pub struct Chain<'info> { + #[account(mut)] + sender: Signer<'info>, + + /// The guest blockchain data. + #[account(init_if_needed, payer = sender, seeds = [CHAIN_SEED], bump, space = 10000)] + chain: Account<'info, chain::ChainData>, + + /// The account holding the trie which corresponds to guest blockchain’s + /// state root. + /// + /// CHECK: Account’s owner is checked by [`storage::get_provable_from`] + /// function. + #[account(init_if_needed, payer = sender, seeds = [TRIE_SEED], bump, space = 1000)] + trie: UncheckedAccount<'info>, + + system_program: Program<'info, System>, +} + +#[derive(Accounts)] +pub struct ChainWithVerifier<'info> { + #[account(mut)] + sender: Signer<'info>, + + /// The guest blockchain data. + #[account(init_if_needed, payer = sender, seeds = [CHAIN_SEED], bump, space = 10000)] + chain: Account<'info, chain::ChainData>, + + /// The account holding the trie which corresponds to guest blockchain’s + /// state root. + /// + /// CHECK: Account’s owner is checked by [`storage::get_provable_from`] + /// function. + #[account(init_if_needed, payer = sender, seeds = [TRIE_SEED], bump, space = 1000)] + trie: UncheckedAccount<'info>, + + #[account(address = solana_program::sysvar::instructions::ID)] + ix_sysvar: AccountInfo<'info>, + + system_program: Program<'info, System>, +} + #[derive(Accounts)] pub struct Deliver<'info> { #[account(mut)] @@ -104,6 +225,10 @@ pub struct Deliver<'info> { #[account(init_if_needed, payer = sender, seeds = [PACKET_SEED], bump, space = 1000)] packets: Account<'info, IBCPackets>, + /// The guest blockchain data. + #[account(init_if_needed, payer = sender, seeds = [CHAIN_SEED], bump, space = 10000)] + chain: Account<'info, chain::ChainData>, + system_program: Program<'info, System>, } diff --git a/solana/solana-ibc/programs/solana-ibc/src/tests.rs b/solana/solana-ibc/programs/solana-ibc/src/tests.rs index 26d00ee7..53a439d2 100644 --- a/solana/solana-ibc/programs/solana-ibc/src/tests.rs +++ b/solana/solana-ibc/programs/solana-ibc/src/tests.rs @@ -23,9 +23,7 @@ use ibc::mock::header::MockHeader; use ibc_proto::google::protobuf::Any; use crate::storage::PrivateStorage; -use crate::{ - accounts, instruction, ID, PACKET_SEED, SOLANA_IBC_STORAGE_SEED, TRIE_SEED, -}; +use crate::{accounts, instruction}; const IBC_TRIE_PREFIX: &[u8] = b"ibc/"; @@ -68,19 +66,23 @@ fn anchor_test_deliver() -> Result<()> { authority.clone(), CommitmentConfig::processed(), ); - let program = client.program(ID).unwrap(); + let program = client.program(crate::ID).unwrap(); let sol_rpc_client = program.rpc(); let _airdrop_signature = airdrop(&sol_rpc_client, authority.pubkey(), lamports); // 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 = &[TRIE_SEED]; - let trie = Pubkey::find_program_address(trie_seeds, &crate::ID).0; - let packet_seeds = &[PACKET_SEED]; - let packets = Pubkey::find_program_address(packet_seeds, &crate::ID).0; + let storage = Pubkey::find_program_address( + &[crate::SOLANA_IBC_STORAGE_SEED], + &crate::ID, + ) + .0; + let trie = Pubkey::find_program_address(&[crate::TRIE_SEED], &crate::ID).0; + let packets = + Pubkey::find_program_address(&[crate::PACKET_SEED], &crate::ID).0; + let chain = + Pubkey::find_program_address(&[crate::CHAIN_SEED], &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(); @@ -98,10 +100,11 @@ fn anchor_test_deliver() -> Result<()> { .request() .accounts(accounts::Deliver { sender: authority.pubkey(), - storage: solana_ibc_storage, + storage, trie, - system_program: system_program::ID, packets, + chain, + system_program: system_program::ID, }) .args(instruction::Deliver { message }) .payer(authority.clone()) @@ -115,7 +118,7 @@ fn anchor_test_deliver() -> Result<()> { // Retrieve and validate state let solana_ibc_storage_account: PrivateStorage = - program.account(solana_ibc_storage).unwrap(); + program.account(storage).unwrap(); println!("This is solana storage account {:?}", solana_ibc_storage_account); @@ -146,10 +149,11 @@ fn anchor_test_deliver() -> Result<()> { .request() .accounts(accounts::Deliver { sender: authority.pubkey(), - storage: solana_ibc_storage, + storage, trie, - system_program: system_program::ID, packets, + chain, + system_program: system_program::ID, }) .args(instruction::Deliver { message }) .payer(authority.clone())