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, TimestampDigest, ISMP_ID, ISMP_TIMESTAMP_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};
46use trie_db::TrieError;
47
48#[derive(
50 Debug, Encode, Decode, Clone, Copy, serde::Deserialize, serde::Serialize, PartialEq, Eq,
51)]
52pub enum HashAlgorithm {
53 Keccak,
55 Blake2,
57}
58
59#[derive(Debug, Encode, Decode, Clone)]
62pub struct StateMachineProof {
63 pub hasher: HashAlgorithm,
65 pub storage_proof: Vec<Vec<u8>>,
67}
68
69#[derive(Debug, Encode, Decode, Clone)]
71pub enum SubstrateStateProof {
72 OverlayProof(StateMachineProof),
74 StateProof(StateMachineProof),
76}
77
78impl SubstrateStateProof {
79 pub fn hasher(&self) -> HashAlgorithm {
81 match self {
82 Self::OverlayProof(proof) => proof.hasher,
83 Self::StateProof(proof) => proof.hasher,
84 }
85 }
86
87 pub fn storage_proof(self) -> Vec<Vec<u8>> {
89 match self {
90 Self::OverlayProof(proof) => proof.storage_proof,
91 Self::StateProof(proof) => proof.storage_proof,
92 }
93 }
94}
95
96pub struct SubstrateStateMachine<T>(PhantomData<T>);
99
100impl<T> Default for SubstrateStateMachine<T> {
101 fn default() -> Self {
102 Self(PhantomData)
103 }
104}
105
106impl<T> From<StateMachine> for SubstrateStateMachine<T> {
107 fn from(_: StateMachine) -> Self {
108 Self::default()
109 }
110}
111
112impl<T> StateMachineClient for SubstrateStateMachine<T>
113where
114 T: pallet_ismp::Config,
115{
116 fn verify_membership(
117 &self,
118 _host: &dyn IsmpHost,
119 item: RequestResponse,
120 state: StateCommitment,
121 proof: &Proof,
122 ) -> Result<(), Error> {
123 let state_proof: SubstrateStateProof = codec::Decode::decode(&mut &*proof.proof)
124 .map_err(|e| Error::Custom(format!("failed to decode proof: {e:?}")))?;
125 ensure!(
126 matches!(state_proof, SubstrateStateProof::OverlayProof { .. }),
127 Error::Custom("Expected Overlay Proof".to_string())
128 );
129
130 let root = match T::Coprocessor::get() {
131 Some(id) if id == proof.height.id.state_id => state.state_root,
132 _ => state.overlay_root.ok_or_else(|| {
133 Error::Custom(
134 "Child trie root is not available for provided state commitment".into(),
135 )
136 })?,
137 };
138
139 let keys = match item {
140 RequestResponse::Request(requests) => requests
141 .into_iter()
142 .map(|request| {
143 let commitment = hash_request::<pallet_ismp::Pallet<T>>(&request);
144 RequestCommitments::<T>::storage_key(commitment)
145 })
146 .collect::<Vec<Vec<u8>>>(),
147 RequestResponse::Response(responses) => responses
148 .into_iter()
149 .map(|response| {
150 let commitment = hash_response::<pallet_ismp::Pallet<T>>(&response);
151 ResponseCommitments::<T>::storage_key(commitment)
152 })
153 .collect::<Vec<Vec<u8>>>(),
154 };
155 let _ = match state_proof.hasher() {
156 HashAlgorithm::Keccak => {
157 let db =
158 StorageProof::new(state_proof.storage_proof()).into_memory_db::<Keccak256>();
159 let trie = TrieDBBuilder::<LayoutV0<Keccak256>>::new(&db, &root).build();
160 keys.into_iter()
161 .map(|key| {
162 let value = trie.get(&key).map_err(|e| {
163 Error::Custom(format!(
164 "SubstrateStateMachine: Error reading Keccak state proof: {e:?}"
165 ))
166 })?.ok_or_else(|| Error::Custom(format!(
167 "Every key in a membership proof should have a value, found a key {:?} with None", key
168 )))?;
169 Ok((key, value))
170 })
171 .collect::<Result<BTreeMap<_, _>, _>>()?
172 },
173 HashAlgorithm::Blake2 => {
174 let db =
175 StorageProof::new(state_proof.storage_proof()).into_memory_db::<BlakeTwo256>();
176
177 let trie = TrieDBBuilder::<LayoutV0<BlakeTwo256>>::new(&db, &root).build();
178 keys.into_iter()
179 .map(|key| {
180 let value = trie.get(&key).map_err(|e| {
181 Error::Custom(format!(
182 "SubstrateStateMachine: Error reading Blake2 state proof: {e:?}"
183 ))
184 })?.ok_or_else(|| Error::Custom(format!(
185 "Every key in a membership proof should have a value, found a key {:?} with None", key
186 )))?;
187 Ok((key, value))
188 })
189 .collect::<Result<BTreeMap<_, _>, _>>()?
190 },
191 };
192
193 Ok(())
194 }
195
196 fn receipts_state_trie_key(&self, items: RequestResponse) -> Vec<Vec<u8>> {
197 let mut keys = vec![];
198 match items {
199 RequestResponse::Request(requests) =>
200 for req in requests {
201 match req {
202 Request::Post(post) => {
203 let request = Request::Post(post);
204 let commitment = hash_request::<pallet_ismp::Pallet<T>>(&request);
205 keys.push(RequestReceipts::<T>::storage_key(commitment));
206 },
207 Request::Get(_) => continue,
208 }
209 },
210 RequestResponse::Response(responses) =>
211 for res in responses {
212 match res {
213 Response::Post(post_response) => {
214 let commitment =
215 hash_post_response::<pallet_ismp::Pallet<T>>(&post_response);
216 keys.push(ResponseReceipts::<T>::storage_key(commitment));
217 },
218 Response::Get(_) => continue,
219 }
220 },
221 };
222
223 keys
224 }
225
226 fn verify_state_proof(
227 &self,
228 _host: &dyn IsmpHost,
229 keys: Vec<Vec<u8>>,
230 root: StateCommitment,
231 proof: &Proof,
232 ) -> Result<BTreeMap<Vec<u8>, Option<Vec<u8>>>, Error> {
233 let state_proof: SubstrateStateProof = codec::Decode::decode(&mut &*proof.proof)
234 .map_err(|e| Error::Custom(format!("failed to decode proof: {e:?}")))?;
235 let root = match &state_proof {
236 SubstrateStateProof::OverlayProof { .. } => {
237 match T::Coprocessor::get() {
238 Some(id) if id == proof.height.id.state_id => root.state_root,
239 _ => root.overlay_root.ok_or_else(|| {
241 Error::Custom(
242 "Child trie root is not available for provided state commitment".into(),
243 )
244 })?,
245 }
246 },
247 SubstrateStateProof::StateProof { .. } => root.state_root,
248 };
249 let data = match state_proof.hasher() {
250 HashAlgorithm::Keccak => {
251 let db =
252 StorageProof::new(state_proof.storage_proof()).into_memory_db::<Keccak256>();
253 let trie = TrieDBBuilder::<LayoutV0<Keccak256>>::new(&db, &root).build();
254 keys.into_iter()
255 .map(|key| {
256 let value = trie.get(&key).map_err(|e| {
257 Error::Custom(format!("Error reading state proof: {e:?}"))
258 })?;
259 Ok((key, value))
260 })
261 .collect::<Result<BTreeMap<_, _>, _>>()?
262 },
263 HashAlgorithm::Blake2 => {
264 let db =
265 StorageProof::new(state_proof.storage_proof()).into_memory_db::<BlakeTwo256>();
266
267 let trie = TrieDBBuilder::<LayoutV0<BlakeTwo256>>::new(&db, &root).build();
268 keys.into_iter()
269 .map(|key| {
270 let value = trie.get(&key).map_err(|e| {
271 Error::Custom(format!("Error reading state proof: {e:?}"))
272 })?;
273 Ok((key, value))
274 })
275 .collect::<Result<BTreeMap<_, _>, _>>()?
276 },
277 };
278
279 Ok(data)
280 }
281}
282
283pub fn read_proof_check<H, I>(
285 root: &H::Out,
286 proof: StorageProof,
287 keys: I,
288) -> Result<BTreeMap<Vec<u8>, Option<Vec<u8>>>, Error>
289where
290 H: hash_db::Hasher,
291 H::Out: Debug,
292 I: IntoIterator,
293 I::Item: AsRef<[u8]>,
294{
295 let db = proof.into_memory_db();
296
297 if !db.contains(root, EMPTY_PREFIX) {
298 Err(Error::Custom("Invalid Proof".into()))?
299 }
300
301 let trie = TrieDBBuilder::<LayoutV0<H>>::new(&db, root).build();
302 let mut result = BTreeMap::new();
303
304 for key in keys.into_iter() {
305 let value = trie
306 .get(key.as_ref())
307 .map_err(|e| Error::Custom(format!("Error reading from trie: {e:?}")))?
308 .and_then(|val| Decode::decode(&mut &val[..]).ok());
309 result.insert(key.as_ref().to_vec(), value);
310 }
311
312 Ok(result)
313}
314
315pub fn read_proof_check_for_parachain<H, I>(
317 root: &H::Out,
318 proof: StorageProof,
319 keys: I,
320) -> Result<BTreeMap<Vec<u8>, Option<Vec<u8>>>, Error>
321where
322 H: hash_db::Hasher,
323 H::Out: Debug,
324 I: IntoIterator,
325 I::Item: AsRef<[u8]>,
326{
327 let db = proof.into_memory_db();
328
329 if !db.contains(root, EMPTY_PREFIX) {
330 Err(Error::Custom("Invalid Proof".into()))?
331 }
332
333 let trie = TrieDBBuilder::<LayoutV0<H>>::new(&db, root).build();
334 let mut result = BTreeMap::new();
335
336 for key in keys {
337 let raw_key = key.as_ref();
338
339 match trie.get(raw_key) {
340 Ok(Some(val)) => {
341 let decoded = Decode::decode(&mut &val[..])
342 .map_err(|e| Error::Custom(format!("Decode error: {e:?}")))?;
343 result.insert(raw_key.to_vec(), Some(decoded));
344 },
345 Ok(None) => {
346 result.insert(raw_key.to_vec(), None);
347 },
348 Err(e) =>
349 if let TrieError::IncompleteDatabase(_) = *e {
350 continue;
351 } else {
352 return Err(Error::Custom(format!("Trie fetch error: {e:?}",)));
353 },
354 }
355 }
356
357 Ok(result)
358}
359
360#[derive(Default)]
362pub struct DigestResult {
363 pub timestamp: u64,
365 pub ismp_digest: ConsensusDigest,
367}
368
369pub fn fetch_overlay_root_and_timestamp(
371 digest: &Digest,
372 slot_duration: u64,
373) -> Result<DigestResult, Error> {
374 let mut digest_result = DigestResult::default();
375
376 for digest in digest.logs.iter() {
377 match digest {
378 DigestItem::Consensus(consensus_engine_id, value)
379 if *consensus_engine_id == ISMP_TIMESTAMP_ID =>
380 {
381 let timestamp_digest = TimestampDigest::decode(&mut &value[..]).map_err(|e| {
382 Error::Custom(format!("Failed to decode timestamp digest: {e:?}"))
383 })?;
384 digest_result.timestamp = timestamp_digest.timestamp;
385 },
386 DigestItem::PreRuntime(consensus_engine_id, value)
387 if *consensus_engine_id == AURA_ENGINE_ID =>
388 {
389 let slot = Slot::decode(&mut &value[..])
390 .map_err(|e| Error::Custom(format!("Cannot slot: {e:?}")))?;
391 digest_result.timestamp = Duration::from_millis(*slot * slot_duration).as_secs();
392 },
393 DigestItem::PreRuntime(consensus_engine_id, value)
394 if *consensus_engine_id == BABE_ENGINE_ID =>
395 {
396 let slot = PreDigest::decode(&mut &value[..])
397 .map_err(|e| Error::Custom(format!("Cannot slot: {e:?}")))?
398 .slot();
399 digest_result.timestamp = Duration::from_millis(*slot * slot_duration).as_secs();
400 },
401 DigestItem::Consensus(consensus_engine_id, value)
402 if *consensus_engine_id == ISMP_ID =>
403 {
404 let digest = ConsensusDigest::decode(&mut &value[..])
405 .map_err(|e| Error::Custom(format!("Failed to decode digest: {e:?}")))?;
406
407 digest_result.ismp_digest = digest
408 },
409 _ => {},
411 };
412 }
413
414 Ok(digest_result)
415}