Skip to main content

pallet_ismp/
offchain.rs

1// Copyright (c) 2025 Polytope Labs.
2// SPDX-License-Identifier: Apache-2.0
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// 	http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16//! Offchain DB interfaces and utilities
17use polkadot_sdk::*;
18
19use codec::{Decode, Encode};
20use ismp::router::{GetResponse, Request};
21use scale_info::TypeInfo;
22use sp_core::{RuntimeDebug, H256};
23use sp_mmr_primitives::NodeIndex;
24use sp_std::prelude::*;
25
26/// Queries a request leaf in the mmr
27#[derive(codec::Encode, codec::Decode, scale_info::TypeInfo)]
28#[cfg_attr(feature = "std", derive(serde::Deserialize, serde::Serialize))]
29pub struct LeafIndexQuery {
30	/// Request or response commitment
31	pub commitment: H256,
32}
33
34/// Leaf index and position
35#[derive(
36	codec::Encode,
37	codec::Decode,
38	scale_info::TypeInfo,
39	Ord,
40	PartialOrd,
41	Eq,
42	PartialEq,
43	Clone,
44	Copy,
45	RuntimeDebug,
46)]
47#[cfg_attr(feature = "std", derive(serde::Deserialize, serde::Serialize))]
48pub struct LeafIndexAndPos {
49	/// Leaf index
50	pub leaf_index: u64,
51	/// Leaf position
52	pub pos: u64,
53}
54
55/// A concrete Leaf for the offchain DB
56#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq, scale_info::TypeInfo)]
57pub enum Leaf {
58	/// A request variant
59	Request(Request),
60	/// A response variant
61	GetResponse(GetResponse),
62}
63
64impl FullLeaf for Leaf {
65	fn preimage(&self) -> Vec<u8> {
66		match self {
67			Leaf::Request(req) => req.encode(),
68			Leaf::GetResponse(res) => res.encode(),
69		}
70	}
71}
72
73/// Leaf index and position
74#[derive(
75	codec::Encode,
76	codec::Decode,
77	scale_info::TypeInfo,
78	Ord,
79	PartialOrd,
80	Eq,
81	PartialEq,
82	Clone,
83	Copy,
84	RuntimeDebug,
85	Default,
86)]
87#[cfg_attr(feature = "std", derive(serde::Deserialize, serde::Serialize))]
88pub struct LeafMetadata {
89	/// Leaf index in the tree
90	pub index: u64,
91	/// Leaf node position in the tree
92	pub position: u64,
93}
94
95/// The pallet-ismp will use this interface to insert leaves into the offchain db.
96/// This allows for batch insertions and asychronous root hash computation, so that
97/// the root is only computed once per block.
98pub trait OffchainDBProvider {
99	/// Concrete leaf type used by the implementation.
100	type Leaf;
101
102	/// Returns the total number of leaves that have been persisted to the db.
103	fn count() -> u64;
104
105	/// Push a new leaf into the offchain db.
106	fn push(leaf: Self::Leaf) -> LeafMetadata;
107
108	/// Merkelize the offchain db and compute it's new root hash. This should only be called once a
109	/// block. This should pull the leaves from the buffer and commit them.
110	fn finalize() -> Result<H256, sp_mmr_primitives::Error>;
111
112	/// Given the leaf position, return the leaf from the offchain db
113	fn leaf(pos: NodeIndex) -> Result<Option<Self::Leaf>, sp_mmr_primitives::Error>;
114
115	/// Generate a proof for the given leaf indices. The implementation should provide
116	/// a proof for the leaves at the current block height.
117	fn proof(
118		indices: Vec<NodeIndex>,
119	) -> Result<(Vec<Self::Leaf>, sp_mmr_primitives::LeafProof<H256>), sp_mmr_primitives::Error>;
120}
121
122/// Offchain key for storing requests using the commitment as identifiers
123pub fn leaf_default_key(commitment: H256) -> Vec<u8> {
124	let prefix = b"no_op";
125	(prefix, commitment).encode()
126}
127
128impl OffchainDBProvider for () {
129	type Leaf = Leaf;
130
131	fn count() -> u64 {
132		0
133	}
134
135	fn proof(
136		_indices: Vec<u64>,
137	) -> Result<(Vec<Self::Leaf>, sp_mmr_primitives::LeafProof<H256>), sp_mmr_primitives::Error> {
138		Err(sp_mmr_primitives::Error::GenerateProof)?
139	}
140
141	fn push(leaf: Self::Leaf) -> LeafMetadata {
142		let encoded = leaf.preimage();
143		let commitment = sp_io::hashing::keccak_256(&encoded);
144		let offchain_key = leaf_default_key(commitment.into());
145		sp_io::offchain_index::set(&offchain_key, &leaf.encode());
146		Default::default()
147	}
148
149	fn finalize() -> Result<H256, sp_mmr_primitives::Error> {
150		Ok(H256::default())
151	}
152
153	fn leaf(_pos: NodeIndex) -> Result<Option<Self::Leaf>, sp_mmr_primitives::Error> {
154		Ok(None)
155	}
156}
157
158/// A full leaf content stored in the offchain-db.
159pub trait FullLeaf: Clone + PartialEq + core::fmt::Debug + codec::FullCodec {
160	/// Compute the leaf preimage to be hashed.
161	fn preimage(&self) -> Vec<u8>;
162}
163
164/// This trait should provide a hash that is unique to each block
165/// This hash will be used as an identifier when creating the non canonical offchain key
166pub trait ForkIdentifier<T: frame_system::Config> {
167	/// Returns a unique identifier for the current block
168	fn identifier() -> T::Hash;
169}
170
171/// Distinguish between requests and responses
172#[derive(TypeInfo, Encode, Decode, serde::Deserialize, serde::Serialize)]
173pub enum ProofKeys {
174	/// Request commitments
175	Requests(Vec<H256>),
176	/// GetResponse commitments
177	Responses(Vec<H256>),
178}
179
180/// An MMR proof data for a group of leaves.
181#[derive(codec::Encode, codec::Decode, RuntimeDebug, Clone, PartialEq, Eq, TypeInfo)]
182#[cfg_attr(feature = "std", derive(serde::Deserialize, serde::Serialize))]
183pub struct Proof<Hash> {
184	/// The indices and positions of the leaves in the proof.
185	pub leaf_indices_and_pos: Vec<LeafIndexAndPos>,
186	/// Number of leaves in MMR, when the proof was generated.
187	pub leaf_count: NodeIndex,
188	/// Proof elements (hashes of siblings of inner nodes on the path to the leaf).
189	pub items: Vec<Hash>,
190}