Skip to content

Commit

Permalink
solana-ibc: don’t fail maybe_generate_block on uninitialised blockcha…
Browse files Browse the repository at this point in the history
…in (#123)
  • Loading branch information
mina86 authored Nov 24, 2023
1 parent 9f569c9 commit f0027e1
Showing 1 changed file with 73 additions and 66 deletions.
139 changes: 73 additions & 66 deletions solana/solana-ibc/programs/solana-ibc/src/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,80 +57,34 @@ impl ChainData {

/// 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).
/// 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.
/// 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, None, true)
self.get_mut()?.generate_block(trie, None, true)
}

/// Generates a new guest block if possible.
///
/// Contrary to [`generate_block`] this function won’t fail if new block could
/// not be created.
/// Contrary to [`generate_block`] this function won’t fail if new block
/// could not be created (including because the blockchain hasn’t been
/// initialised yet).
///
/// This is intended to create a new block opportunistically at the beginning of
/// handling any smart contract request.
/// 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,
host_head: Option<crate::host::Head>,
) -> Result {
self.generate_block_impl(trie, host_head, 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,
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()?;

// 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 == host_head.height {
return if force {
Err(Error::GenerationAlreadyAttempted.into())
} else {
Ok(())
};
}
inner.last_check_height = host_head.height;
let res = inner.manager.generate_next(
host_head.height,
host_head.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)),
match self.get_mut() {
Ok(inner) => inner.generate_block(trie, host_head, false),
Err(_) => Ok(()),
}
}
Expand Down Expand Up @@ -182,10 +136,6 @@ impl ChainData {
}
}

fn into_error<E: Into<Error>>(err: E) -> anchor_lang::error::Error {
err.into().into()
}

/// The inner chain data
#[derive(Clone, Debug, borsh::BorshSerialize, borsh::BorshDeserialize)]
struct ChainInner {
Expand All @@ -196,3 +146,60 @@ struct ChainInner {
/// The guest blockchain manager handling generation of new guest blocks.
manager: Manager,
}

impl ChainInner {
/// Attempts generating a new guest block.
///
/// Implementation of [`ChainData::generate_block`] and
/// [`ChainData::maybe_generate_block`] methods. 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(
&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)?;

// 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 self.last_check_height == host_head.height {
return if force {
Err(Error::GenerationAlreadyAttempted.into())
} else {
Ok(())
};
}
self.last_check_height = host_head.height;
let res = self.manager.generate_next(
host_head.height,
host_head.timestamp,
trie.hash().clone(),
false,
);
match res {
Ok(()) => {
let (finalised, head) = self.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(()),
}
}
}

fn into_error<E: Into<Error>>(err: E) -> anchor_lang::error::Error {
err.into().into()
}

0 comments on commit f0027e1

Please sign in to comment.