The Composable Foundation, which also oversees the development of Picasso Network, has developed a unique solution to integrate Solana with the Inter-Blockchain Communication (IBC) Protocol. The implementation of IBC on Solana employs an Actively Validated Service (AVS) from Picasso’s restaking layer to meet IBC’s technical requirements for implementation on Solana, thus enabling secure cross-chain communication between other IBC-enabled networks.
Solana's integration with IBC is facilitated through a guest blockchain (also known as AVS for IBC on Solana) to support the generation of state proofs and fulfill IBC's requirements. Similar to IBC connections between PoS chains, validators maintain the integrity and security of the guest blockchain’s operations.
Solana inherently lacks the technical capabilities to support IBC. Specifically, Solana lacks native state proofs, which are cryptographic proofs that a certain state or transaction is valid and committed on the blockchain in order to build a light client. This absence makes Solana incompatible with IBC out-of-the-box, as IBC relies on state proofs for light client verification. The guest blockchain solution addresses this by acting as a layer that provides state proofs and other necessary functionalities without requiring changes to the Solana blockchain itself.
The AVS functions by creating a provable key-value storage system using a Merkle trie structure. The storage commitment, included in each block as a hash of the root node of the Merkle trie, ensures data integrity and verifiability.
/// A Merkle Patricia Trie with sealing/pruning feature. /// /// The trie is designed to work in situations where space is constrained. To /// that effect, it imposes certain limitations and implements feature which /// help reduce its size. /// /// In the abstract, the trie is a regular Merkle Patricia Trie which allows /// storing arbitrary (key, value) pairs. However: /// /// 1. The trie doesn’t actually store values but only their hashes. (Another /// way to think about it is that all values are 32-byte long byte slices). /// It’s assumed that clients store values in another location and use this /// data structure only as a witness. Even though it doesn’t contain the /// values it can generate proof of existence or non-existence of keys. /// /// 2. The trie allows values to be sealed. A hash of a sealed value can no /// longer be accessed even though in abstract sense the value still resides /// in the trie. That is, sealing a value doesn’t affect the state root /// hash and old proofs for the value continue to be valid. /// /// Nodes of sealed values are removed from the trie to save storage. /// Furthermore, if a children of an internal node have been sealed, that /// node becomes sealed as well. For example, if keys `a` and `b` has /// both been sealed, than branch node above them becomes sealed as well. /// /// To take most benefits from sealing, it’s best to seal consecutive keys. /// For example, sealing keys `a`, `b`, `c` and `d` will seal their parents /// as well while sealing keys `a`, `c`, `e` and `g` will leave their parents /// unsealed and thus kept in the trie. /// /// 3. The trie is designed to work with a pool allocator and supports keeping /// at most 2³⁰-2 nodes. Sealed values don’t count towards this limit since /// they aren’t stored. In any case, this should be plenty since fully /// balanced binary tree with that many nodes allows storing 500K keys. /// /// 4. Keys are limited to 8191 bytes (technically 2¹⁶-1 bits but there’s no /// interface for keys which hold partial bytes). It would be possible to /// extend this limit but 8k bytes should be plenty for any reasonable usage. /// /// As an optimisation to take advantage of trie’s internal structure, it’s /// best to keep keys up to 36-byte long. Or at least, to keep common key /// prefixes to be at most 36-byte long. For example, a trie which has /// a single value at a key whose length is withing 36 bytes has a single /// node however if that key is longer than 36 bytes the trie needs at least /// two nodes. #[derive(Debug)] pub struct Trie<A> { /// Pointer to the root node. `None` if the trie is empty or the root node /// has been sealed. root_ptr: Option<Ptr>, /// Hash of the root node; [`EMPTY_TRIE_ROOT`] if trie is empty. root_hash: CryptoHash, /// Allocator used to access and allocate nodes. alloc: A, }
Blocks in the guest blockchain are grouped into epochs, with each epoch having a defined set of validators. Validators are selected based on their staked assets, with the set being updated at the beginning of each new epoch. Validators must commit to their role for the duration of an epoch to prevent any disruption in the validation process.
/// Generates a new epoch with the top validators from the candidates set /// if neccessary. /// /// Returns `None` if the current epoch is too short to change to new epoch /// or the validators set hasn’t changed. Otherwise constructs and returns /// a new epoch by picking top validators from `self.candidates` as the /// validators set in the new epoch. /// /// Panics if there are no candidates, i.e. will always return a valid /// epoch. However, it doesn’t check minimum number of validators (other /// than non-zero) or minimum quorum stake (again, other than non-zero). /// Those conditions are assumed to hold by construction of /// `self.candidates`. fn maybe_generate_next_epoch( &self, host_height: crate::HostHeight, ) -> Option<crate::Epoch<PK>> { if !host_height .check_delta_from(self.epoch_height, self.config.min_epoch_length) { return None; } crate::Epoch::new_with(self.candidates.maybe_get_head()?, |total| { let quorum = NonZeroU128::new(total.get() / 2 + 1).unwrap(); // min_quorum_stake may be greater than total_stake so we’re not // using .clamp to make sure we never return value higher than // total_stake. quorum.max(self.config.min_quorum_stake).min(total) }) }
Message Flow
The message flow between Solana and other IBC-enabled blockchains involves several key steps:
Message Initiation: A transaction is initiated on Solana.
Updating client on counterparty: The relayer sends the client update of the source chain to the destination chain before the packet can be received since the destination chain doesn’t have the state root in which the packet on source was sent.(*see the Epoch Change: Ensuring Consistency and Syncing section later in the blog for more information)
Relayer Operation: A relayer generates the proof for the packet and relays it to the target IBC-enabled blockchain.
Message Processing: The target blockchain verifies the state proof and processes the message, completing the cross-chain transaction.
The security of the Solana IBC integration is achieved through the Solana Restaking Layer, making it the first live AVS to benefit from this restaking mechanism on Solana. Restaking involves staking liquid staked assets with the blockchain’s validators, thereby enhancing both yield and security. Restakers in the restaking layer are incentivized to maintain network integrity, ensuring reliable and secure operations.
pub fn deposit<'a, 'info>( // Contains the accounts required for the deposit which includes // the token mint which is being restaked. ctx: Context<'a, 'a, 'a, 'info, Deposit<'info>>, // specifies the validator to which it is being restaked to service: Service, amount: u64, ) -> Result<()> { // The steps are as follows: // 1. Validates if the token which is being deposited is whitelisted. // 2. Transfers the token to the escrow // 3. Mints a receipt token // 4. delegates stake to the above mentioned validator } // Since there is unbonding period, users would have to request for withdrawal pub fn withdrawal_request(ctx: Context<WithdrawalRequest>) -> Result<()> { // The steps are as follows // 1. Transfers the receipt token to the escrow // 2. stores the request timestamp } // Can withdraw once unbonding period is elapsed pub fn withdraw(ctx: Context<Withdraw>) -> Result<()> { // The steps are as follows // 1. Checks if unbonding period has elapsed // 2. burns the receipt token // 3. undelegates the stake from the validator // 4. transfers the funds to the user }
This figure depicts the sequence of events when successfully sending a message from a blockchain using Picasso’s guest blockchain solution and an IBC-enabled counterparty blockchain. Like in any IBC example, anyone can run a relayer to pass messages between the host blockchain and the counterparty blockchain. However, to be able to provide a proof to the counterparty blockchain, a guest blockchain with provable storage is necessary.
Provable key-value storage for Solana IBC is crucial for providing proofs to the counterparty blockchain. To manage data generation and storage costs, this solution implements sealing or pruning of subtries, ensuring efficient use of on-chain storage. The commitment of storage is represented as a hash of the Merkle trie’s root node.
Blocks are organized into epochs with specified validators. Epoch changes on solana occur every 100k solana blocks ( with 400ms block time it is around 11.11 hours ) if there has been a change in candidate set. If a block is within the same epoch, the relayer simply sends the current block. However, if multiple epochs have been passed since the last client update, then the node sends the last block from the previous epochs followed by the latest block from the current epoch. This approach ensures that light clients can efficiently catch up to the latest state without needing to retrieve every individual block.
Validators run a sidecar mechanism when producing blocks to generate state proofs, relayed to the counterparty chain. Validator selection, set changes, and slashing mechanisms are implemented.
Joining as a validator requires a bonded stake to keep participation gated from malicious actors. The validator set will be able to utilize liquid staking derivatives (LSD) of SOL.
Slashing will be implemented as a means of disincentivizing malicious or erroneous behavior amongst the network of validators. Slashing parameters would be defined to penalize conditions such as downtime and signing incorrect transactions. Validators would also be subjected to jailing for severe violations such as double signing.
The guest blockchain system developed by the Composable Foundation / Picasso Network enables Solana to integrate with the IBC Protocol, overcoming significant technical challenges. This innovation expands the interoperability of Solana and Cosmos, allowing for secure interoperability between the ecosystems and all the chains therein. By providing light clients and state proofs and fulfilling all other IBC requirements, the guest blockchain solution paves the way for seamless cross-chain communication and collaboration between Solana and Cosmos. Notably, many other layer 1 blockchains face similar incompatibility with IBC due to the lack of state proofs, but this design can be implemented on those chains to address the issue.
This technical walkthrough, written by the core contributors from the Composable Foundation and Picasso Network, aims to provide a better technical understanding of the Solana-IBC AVS. Developers are encouraged to explore the provided references and engage with the community through Picasso's official channels.
Dhruv Jain ([email protected]) is a Core Engineering Contributor to the Composable Foundation and one of the lead engineers behind the Solana IBC implementation.
Patrick Kondek ([email protected]) is a Core Marketing Contributor to the Composable Foundation.