pallet-ismp

This pallet is the ISMP implementation for substrate runtimes, it is responsible for managing consensus clients and their state machines, handling incoming requests and dispatching ismp messages to the appropriate ismp modules.

Adding the pallet to the runtime

Add the following crates to your Cargo.toml
toml
ismp = { git="https://github.com/polytope-labs/hyperbridge", branch="main", default-features = false } pallet-ismp = { git="https://github.com/polytope-labs/hyperbridge", branch="main", default-features = false } ismp-runtime-api = { git="https://github.com/polytope-labs/hyperbridge", branch="main", default-features = false }
In order to add the pallet-ismp to your substrate runtime, you’ll need to provide some configuration options. The pallet has the following config:
rust
pub trait Config: frame_system::Config { /// The overarching event type. type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>; /// Prefix for elements stored in the Off-chain DB via Indexing API. /// /// Each node of the MMR is inserted both on-chain and off-chain via Indexing API. /// The former does not store full leaf content, just its compact version (hash), /// and some of the inner mmr nodes might be pruned from on-chain storage. /// The latter will contain all the entries in their full form. /// /// Each node is stored in the Off-chain DB under key derived from the /// [`Self::INDEXING_PREFIX`] and its in-tree index (MMR position). const INDEXING_PREFIX: &'static [u8]; /// Admin origin for privileged actions type AdminOrigin: EnsureOrigin<Self::RuntimeOrigin>; /// Host state machine identifier type StateMachine: Get<StateMachine>; /// Timestamp provider type TimeProvider: UnixTime; /// Configurable router that provides access to module callback handlers type IsmpRouter: IsmpRouter + Default; /// Provides concrete implementations of consensus clients type ConsensusClientProvider: ConsensusClientProvider; /// Weight Info type WeightInfo: WeightInfo; /// Weight provider for consensus clients and module callbacks type WeightProvider: WeightProvider; }

Consensus Client

In order to communicate with other chains, ISMP requires an implementation of a consensus client. This consensus client is used to verify the consensus proofs and consensus state of the counterparty chain. This is the most secure approach to reading the counterparty blockchain as opposed to third parties who attest to the consensus state of the counter party.
The pallet runtime config expects an implementation of ConsensusClientProvider this trait should provide implementations for various consensus clients that you intend to support to the ismp host. If you’d like to implement your own consenus client. All you need to do is implement the ConsensusClient trait for verifying the consensus proofs of this new consensus system. The parachain consensus client has been implemented in ismp-parachain . While solo chains can use the ismp-grandpa consensus client.

StateMachine

In order for ISMP to correctly process messages intended for your chain, ISMP needs to know the state machine identifier for your chain. This identifier will also be used by other chains when addressing your chain in their requests. Use the StateMachine enum to describe your current state machine, whether it’s a parachain or standalone chain.
rust
pub struct StateMachineProvider; impl Get<StateMachine> for StateMachineProvider { fn get() -> StateMachine { // Add the parachain id here, you can use either // the Polkadot or Kusama variant of the StateMachine enum // depending on the relaychain the parachain is running on. StateMachine::Kusama(ParachainInfo::get().into()) } }

ISMP Router

The IsmpRouter is used by the framework to match the to field in the request struct to the correct IsmpModule on your chain. IsmpModule can either be a pallet, an evm contract or even an ink! contract.
rust
use pallet_ismp::primitives::ModuleId; pub struct ModuleRouter; impl IsmpRouter for ModuleRouter { fn module_for_id(bytes: Vec<u8>) -> Result<Box<dyn IsmpModule>, ismp::Error> { let module_id = ModuleId::from_bytes(&bytes)?; let handler = match module_id { // If Contracts need to be supported use a contract handler as decribed here ModuleId::Contract(_) => Box::new(ContractHandler::<Runtime>::default()), ModuleId::Pallet(_) => Box::new(Pallet::<Runtime>::default()), ModuleId::Evm(_) => Box::new(EvmHandler::<Runtime>::default()), _ => Err(ismp::Error::ModuleNotFound(bytes))?, }; Ok(handler) } }

ISMP Runtime API

pallet-ismp provides some runtime apis that it’s rpc subsystem needs in order to allow the pallet data to be queried by offchain components.
Find the impl_runtime_apis! line in your runtime’s lib.rs file and add the following code:
rust
impl ismp_runtime_api::IsmpRuntimeApi<Block, <Block as BlockT>::Hash> for Runtime { /// Return the number of MMR leaves. fn mmr_leaf_count() -> Result<LeafIndex, pallet_ismp::primitives::Error> { Ok(Ismp::mmr_leaf_count()) } /// Return the on-chain MMR root hash. fn mmr_root() -> Result<<Block as BlockT>::Hash, pallet_ismp::primitives::Error> { Ok(Ismp::mmr_root()) } /// Generate a proof for the provided leaf indices fn generate_proof( leaf_indices: Vec<LeafIndex>, ) -> Result<(Vec<Leaf>, Proof<<Block as BlockT>::Hash>), pallet_ismp::primitives::Error> { Ismp::generate_proof(leaf_indices) } /// Fetch all ISMP events fn block_events() -> Vec<pallet_ismp::events::Event> { let raw_events = frame_system::Pallet::<Self>::read_events_no_consensus().into_iter(); raw_events .filter_map(|e| { let frame_system::EventRecord { event, .. } = *e; match event { RuntimeEvent::Ismp(event) => pallet_ismp::events::to_core_protocol_event(event), _ => None, } }) .collect() } /// Return the scale encoded consensus state fn consensus_state(id: ConsensusClientId) -> Option<Vec<u8>> { Ismp::consensus_states(id) } /// Return the timestamp this client was last updated in seconds fn consensus_update_time(id: ConsensusClientId) -> Option<u64> { Ismp::consensus_update_time(id) } /// Return the latest height of the state machine fn latest_state_machine_height(id: StateMachineId) -> Option<u64> { Ismp::get_latest_state_machine_height(id) } /// Get Request Leaf Indices fn get_request_leaf_indices(leaf_queries: Vec<LeafIndexQuery>) -> Vec<LeafIndex> { Ismp::get_request_leaf_indices(leaf_queries) } /// Get Response Leaf Indices fn get_response_leaf_indices(leaf_queries: Vec<LeafIndexQuery>) -> Vec<LeafIndex> { Ismp::get_response_leaf_indices(leaf_queries) } /// Get actual requests fn get_requests(leaf_indices: Vec<LeafIndex>) -> Vec<Request> { Ismp::get_requests(leaf_indices) } /// Get actual responses fn get_responses(leaf_indices: Vec<LeafIndex>) -> Vec<Response> { Ismp::get_responses(leaf_indices) } fn pending_get_requests() -> Vec<ismp::router::Get> { Ismp::pending_get_requests() } }

ISMP RPC API

Add the following to your client’s Cargo.toml
toml
ismp-rpc = { git="https://github.com/polytope-labs/hyperbridge", branch="main" } ismp-runtime-api = { git="https://github.com/polytope-labs/hyperbridge", branch="main" }
ismp-rpc provides an RPC interface for querying data and proofs from pallet-ismp. Typically this api will be called by relayers and users who want to relay their packets from one chain to the other.
As noted earlier, the rpc interface is built on the ismp-runtime-api . To add the rpc to the node find the create_full function in your node’s rpc.rs file and add the following code:
rust
pub fn create_full<C, P>( deps: FullDeps<C, P>, ) -> Result<RpcExtension, Box<dyn std::error::Error + Send + Sync>> where // --snip-- C::Api: ismp_runtime_api::IsmpRuntimeApi<Block, H256>, { use ismp_rpc::{IsmpApiServer, IsmpRpcHandler}; // --snip-- module.merge(IsmpRpcHandler::new(client).into_rpc())?; Ok(module) }
Putting everything together:
rust
impl pallet_ismp::Config for Runtime { type RuntimeEvent = RuntimeEvent; const INDEXING_PREFIX: &'static [u8] = b"ISMP"; type AdminOrigin = EnsureRoot<AccountId>; type StateMachine = StateMachineProvider; // the timestamp pallet type TimeProvider = Timestamp; // the IsmpRouter implementation type IsmpRouter = ModuleRouter; // Add ConsensusProvider as described type ConsensusClientProvider = ConsensusProvider; // Add the pallet benchmarks weights here type WeightInfo = (); type WeightProvider = (); } // For a Sample runtime construct_runtime!( pub enum Runtime where Block = Block, NodeBlock = opaque::Block, UncheckedExtrinsic = UncheckedExtrinsic, { /* --snip-- */ Timestamp: pallet_timestamp, Ismp: pallet_ismp, } );

Pallet Calls

The pallet has three calls:
handle : This call handles incoming ismp messages. Ismp messages maybe submitted by a dedicated relayer or users who are self-relaying.
create_consensus_state : This call handles the creation of new consensus clients. It can only be executed by the pallet-ismp’s AdminOrigin.
update_consensus_state : This can be used to update the consensus metadata for a consensus state. eg Use this to update the unbonding period & challenge period of a consensus state. It can only be executed by the pallet-ismp’s AdminOrigin.
🏋️‍♂️Weights and Benchmarks🔧Consensus Clients💬Ismp Modules