1#![cfg_attr(not(feature = "std"), no_std)]
19#![deny(missing_docs)]
20
21extern crate alloc;
22
23use alloc::{collections::BTreeMap, format, string::ToString, vec, vec::Vec};
24use codec::{Decode, Encode};
25use core::{fmt::Debug, marker::PhantomData, time::Duration};
26use frame_support::{ensure, traits::Get};
27use ismp::{
28 consensus::{StateCommitment, StateMachineClient},
29 error::Error,
30 host::{IsmpHost, StateMachine},
31 messaging::{hash_post_response, hash_request, hash_response, Proof},
32 router::{Request, RequestResponse, Response},
33};
34use pallet_ismp::{
35 child_trie::{RequestCommitments, RequestReceipts, ResponseCommitments, ResponseReceipts},
36 ConsensusDigest, ISMP_ID,
37};
38use polkadot_sdk::*;
39use sp_consensus_aura::{Slot, AURA_ENGINE_ID};
40use sp_consensus_babe::{digests::PreDigest, BABE_ENGINE_ID};
41use sp_runtime::{
42 traits::{BlakeTwo256, Keccak256},
43 Digest, DigestItem,
44};
45use sp_trie::{HashDBT, LayoutV0, StorageProof, Trie, TrieDBBuilder, EMPTY_PREFIX};
46
47#[derive(
49 Debug, Encode, Decode, Clone, Copy, serde::Deserialize, serde::Serialize, PartialEq, Eq,
50)]
51pub enum HashAlgorithm {
52 Keccak,
54 Blake2,
56}
57
58#[derive(Debug, Encode, Decode, Clone)]
61pub struct StateMachineProof {
62 pub hasher: HashAlgorithm,
64 pub storage_proof: Vec<Vec<u8>>,
66}
67
68#[derive(Debug, Encode, Decode, Clone)]
70pub enum SubstrateStateProof {
71 OverlayProof(StateMachineProof),
73 StateProof(StateMachineProof),
75}
76
77impl SubstrateStateProof {
78 pub fn hasher(&self) -> HashAlgorithm {
80 match self {
81 Self::OverlayProof(proof) => proof.hasher,
82 Self::StateProof(proof) => proof.hasher,
83 }
84 }
85
86 pub fn storage_proof(self) -> Vec<Vec<u8>> {
88 match self {
89 Self::OverlayProof(proof) => proof.storage_proof,
90 Self::StateProof(proof) => proof.storage_proof,
91 }
92 }
93}
94
95pub struct SubstrateStateMachine<T>(PhantomData<T>);
98
99impl<T> Default for SubstrateStateMachine<T> {
100 fn default() -> Self {
101 Self(PhantomData)
102 }
103}
104
105impl<T> From<StateMachine> for SubstrateStateMachine<T> {
106 fn from(_: StateMachine) -> Self {
107 Self::default()
108 }
109}
110
111impl<T> StateMachineClient for SubstrateStateMachine<T>
112where
113 T: pallet_ismp::Config,
114{
115 fn verify_membership(
116 &self,
117 _host: &dyn IsmpHost,
118 item: RequestResponse,
119 state: StateCommitment,
120 proof: &Proof,
121 ) -> Result<(), Error> {
122 let state_proof: SubstrateStateProof = codec::Decode::decode(&mut &*proof.proof)
123 .map_err(|e| Error::Custom(format!("failed to decode proof: {e:?}")))?;
124 ensure!(
125 matches!(state_proof, SubstrateStateProof::OverlayProof { .. }),
126 Error::Custom("Expected Overlay Proof".to_string())
127 );
128
129 let root = match T::Coprocessor::get() {
130 Some(id) if id == proof.height.id.state_id => state.state_root,
131 _ => state.overlay_root.ok_or_else(|| {
132 Error::Custom(
133 "Child trie root is not available for provided state commitment".into(),
134 )
135 })?,
136 };
137
138 let keys = match item {
139 RequestResponse::Request(requests) => requests
140 .into_iter()
141 .map(|request| {
142 let commitment = hash_request::<pallet_ismp::Pallet<T>>(&request);
143 RequestCommitments::<T>::storage_key(commitment)
144 })
145 .collect::<Vec<Vec<u8>>>(),
146 RequestResponse::Response(responses) => responses
147 .into_iter()
148 .map(|response| {
149 let commitment = hash_response::<pallet_ismp::Pallet<T>>(&response);
150 ResponseCommitments::<T>::storage_key(commitment)
151 })
152 .collect::<Vec<Vec<u8>>>(),
153 };
154 let _ = match state_proof.hasher() {
155 HashAlgorithm::Keccak => {
156 let db =
157 StorageProof::new(state_proof.storage_proof()).into_memory_db::<Keccak256>();
158 let trie = TrieDBBuilder::<LayoutV0<Keccak256>>::new(&db, &root).build();
159 keys.into_iter()
160 .map(|key| {
161 let value = trie.get(&key).map_err(|e| {
162 Error::Custom(format!(
163 "SubstrateStateMachine: Error reading Keccak state proof: {e:?}"
164 ))
165 })?.ok_or_else(|| Error::Custom(format!(
166 "Every key in a membership proof should have a value, found a key {:?} with None", key
167 )))?;
168 Ok((key, value))
169 })
170 .collect::<Result<BTreeMap<_, _>, _>>()?
171 },
172 HashAlgorithm::Blake2 => {
173 let db =
174 StorageProof::new(state_proof.storage_proof()).into_memory_db::<BlakeTwo256>();
175
176 let trie = TrieDBBuilder::<LayoutV0<BlakeTwo256>>::new(&db, &root).build();
177 keys.into_iter()
178 .map(|key| {
179 let value = trie.get(&key).map_err(|e| {
180 Error::Custom(format!(
181 "SubstrateStateMachine: Error reading Blake2 state proof: {e:?}"
182 ))
183 })?.ok_or_else(|| Error::Custom(format!(
184 "Every key in a membership proof should have a value, found a key {:?} with None", key
185 )))?;
186 Ok((key, value))
187 })
188 .collect::<Result<BTreeMap<_, _>, _>>()?
189 },
190 };
191
192 Ok(())
193 }
194
195 fn receipts_state_trie_key(&self, items: RequestResponse) -> Vec<Vec<u8>> {
196 let mut keys = vec![];
197 match items {
198 RequestResponse::Request(requests) =>
199 for req in requests {
200 match req {
201 Request::Post(post) => {
202 let request = Request::Post(post);
203 let commitment = hash_request::<pallet_ismp::Pallet<T>>(&request);
204 keys.push(RequestReceipts::<T>::storage_key(commitment));
205 },
206 Request::Get(_) => continue,
207 }
208 },
209 RequestResponse::Response(responses) =>
210 for res in responses {
211 match res {
212 Response::Post(post_response) => {
213 let commitment =
214 hash_post_response::<pallet_ismp::Pallet<T>>(&post_response);
215 keys.push(ResponseReceipts::<T>::storage_key(commitment));
216 },
217 Response::Get(_) => continue,
218 }
219 },
220 };
221
222 keys
223 }
224
225 fn verify_state_proof(
226 &self,
227 _host: &dyn IsmpHost,
228 keys: Vec<Vec<u8>>,
229 root: StateCommitment,
230 proof: &Proof,
231 ) -> Result<BTreeMap<Vec<u8>, Option<Vec<u8>>>, Error> {
232 let state_proof: SubstrateStateProof = codec::Decode::decode(&mut &*proof.proof)
233 .map_err(|e| Error::Custom(format!("failed to decode proof: {e:?}")))?;
234 let root = match &state_proof {
235 SubstrateStateProof::OverlayProof { .. } => {
236 match T::Coprocessor::get() {
237 Some(id) if id == proof.height.id.state_id => root.state_root,
238 _ => root.overlay_root.ok_or_else(|| {
240 Error::Custom(
241 "Child trie root is not available for provided state commitment".into(),
242 )
243 })?,
244 }
245 },
246 SubstrateStateProof::StateProof { .. } => root.state_root,
247 };
248 let data = match state_proof.hasher() {
249 HashAlgorithm::Keccak => {
250 let db =
251 StorageProof::new(state_proof.storage_proof()).into_memory_db::<Keccak256>();
252 let trie = TrieDBBuilder::<LayoutV0<Keccak256>>::new(&db, &root).build();
253 keys.into_iter()
254 .map(|key| {
255 let value = trie.get(&key).map_err(|e| {
256 Error::Custom(format!("Error reading state proof: {e:?}"))
257 })?;
258 Ok((key, value))
259 })
260 .collect::<Result<BTreeMap<_, _>, _>>()?
261 },
262 HashAlgorithm::Blake2 => {
263 let db =
264 StorageProof::new(state_proof.storage_proof()).into_memory_db::<BlakeTwo256>();
265
266 let trie = TrieDBBuilder::<LayoutV0<BlakeTwo256>>::new(&db, &root).build();
267 keys.into_iter()
268 .map(|key| {
269 let value = trie.get(&key).map_err(|e| {
270 Error::Custom(format!("Error reading state proof: {e:?}"))
271 })?;
272 Ok((key, value))
273 })
274 .collect::<Result<BTreeMap<_, _>, _>>()?
275 },
276 };
277
278 Ok(data)
279 }
280}
281
282pub fn read_proof_check<H, I>(
284 root: &H::Out,
285 proof: StorageProof,
286 keys: I,
287) -> Result<BTreeMap<Vec<u8>, Option<Vec<u8>>>, Error>
288where
289 H: hash_db::Hasher,
290 H::Out: Debug,
291 I: IntoIterator,
292 I::Item: AsRef<[u8]>,
293{
294 let db = proof.into_memory_db();
295
296 if !db.contains(root, EMPTY_PREFIX) {
297 Err(Error::Custom("Invalid Proof".into()))?
298 }
299
300 let trie = TrieDBBuilder::<LayoutV0<H>>::new(&db, root).build();
301 let mut result = BTreeMap::new();
302
303 for key in keys.into_iter() {
304 let value = trie
305 .get(key.as_ref())
306 .map_err(|e| Error::Custom(format!("Error reading from trie: {e:?}")))?
307 .and_then(|val| Decode::decode(&mut &val[..]).ok());
308 result.insert(key.as_ref().to_vec(), value);
309 }
310
311 Ok(result)
312}
313
314#[derive(Default)]
316pub struct DigestResult {
317 pub timestamp: u64,
319 pub ismp_digest: ConsensusDigest,
321}
322
323pub fn fetch_overlay_root_and_timestamp(
325 digest: &Digest,
326 slot_duration: u64,
327) -> Result<DigestResult, Error> {
328 let mut digest_result = DigestResult::default();
329
330 for digest in digest.logs.iter() {
331 match digest {
332 DigestItem::PreRuntime(consensus_engine_id, value)
333 if *consensus_engine_id == AURA_ENGINE_ID =>
334 {
335 let slot = Slot::decode(&mut &value[..])
336 .map_err(|e| Error::Custom(format!("Cannot slot: {e:?}")))?;
337 digest_result.timestamp = Duration::from_millis(*slot * slot_duration).as_secs();
338 },
339 DigestItem::PreRuntime(consensus_engine_id, value)
340 if *consensus_engine_id == BABE_ENGINE_ID =>
341 {
342 let slot = PreDigest::decode(&mut &value[..])
343 .map_err(|e| Error::Custom(format!("Cannot slot: {e:?}")))?
344 .slot();
345 digest_result.timestamp = Duration::from_millis(*slot * slot_duration).as_secs();
346 },
347 DigestItem::Consensus(consensus_engine_id, value)
348 if *consensus_engine_id == ISMP_ID =>
349 {
350 let digest = ConsensusDigest::decode(&mut &value[..])
351 .map_err(|e| Error::Custom(format!("Failed to decode digest: {e:?}")))?;
352
353 digest_result.ismp_digest = digest
354 },
355 _ => {},
357 };
358 }
359
360 Ok(digest_result)
361}