1#![cfg_attr(not(feature = "std"), no_std)]
19#![deny(missing_docs)]
20
21extern crate alloc;
22
23use alloc::{
24 collections::BTreeMap,
25 format,
26 string::{String, ToString},
27 vec::Vec,
28};
29use codec::{Decode, Encode};
30use core::{fmt::Debug, marker::PhantomData, time::Duration};
31use frame_support::traits::Get;
32use ismp::{
33 consensus::{StateCommitment, StateMachineClient},
34 error::Error,
35 host::{IsmpHost, StateMachine},
36 messaging::Proof,
37};
38use pallet_ismp::{
39 child_trie::{RequestCommitments, RequestReceipts},
40 ConsensusDigest, TimestampDigest, ISMP_ID, ISMP_TIMESTAMP_ID,
41};
42use polkadot_sdk::*;
43use primitive_types::H256;
44use sp_consensus_aura::{Slot, AURA_ENGINE_ID};
45use sp_consensus_babe::{digests::PreDigest, BABE_ENGINE_ID};
46use sp_runtime::{
47 traits::{BlakeTwo256, Keccak256},
48 Digest, DigestItem,
49};
50use sp_trie::{HashDBT, LayoutV0, StorageProof, Trie, TrieDBBuilder, EMPTY_PREFIX};
51use thiserror::Error as ThisError;
52use trie_db::TrieError;
53
54#[derive(Debug, ThisError)]
56pub enum SubstrateStateMachineError {
57 #[error("Failed to decode proof: {0:?}")]
59 ProofDecodeError(codec::Error),
60 #[error("Child trie root is not available for provided state commitment")]
63 MissingChildTrieRoot,
64 #[error("Trie error: {0}")]
66 TrieError(String),
67 #[error("Every key in a membership proof should have a value, found a key {0:?} with None")]
69 MissingMembershipValue(Vec<u8>),
70 #[error("Some Requests in the batch have been delivered")]
72 DeliveredRequestsInBatch,
73}
74
75impl From<SubstrateStateMachineError> for Error {
76 fn from(e: SubstrateStateMachineError) -> Error {
77 Error::AnyHow(anyhow::Error::new(e).into())
78 }
79}
80
81#[derive(
83 Debug, Encode, Decode, Clone, Copy, serde::Deserialize, serde::Serialize, PartialEq, Eq,
84)]
85pub enum HashAlgorithm {
86 Keccak,
88 Blake2,
90}
91
92#[derive(Debug, Encode, Decode, Clone)]
99pub struct StateMachineProof {
100 pub hasher: HashAlgorithm,
102 pub storage_proof: Vec<Vec<u8>>,
104}
105
106pub struct SubstrateStateMachine<T>(PhantomData<T>);
109
110impl<T> Default for SubstrateStateMachine<T> {
111 fn default() -> Self {
112 Self(PhantomData)
113 }
114}
115
116impl<T> From<StateMachine> for SubstrateStateMachine<T> {
117 fn from(_: StateMachine) -> Self {
118 Self::default()
119 }
120}
121
122impl<T> StateMachineClient for SubstrateStateMachine<T>
123where
124 T: pallet_ismp::Config,
125{
126 fn verify_membership(
127 &self,
128 _host: &dyn IsmpHost,
129 commitments: Vec<H256>,
130 state: StateCommitment,
131 proof: &Proof,
132 ) -> Result<(), Error> {
133 let StateMachineProof { hasher, storage_proof } =
134 codec::Decode::decode(&mut &*proof.proof)
135 .map_err(SubstrateStateMachineError::ProofDecodeError)?;
136
137 let root = match T::Coprocessor::get() {
141 Some(id) if id == proof.height.id.state_id => state.state_root,
142 _ => state.overlay_root.ok_or(SubstrateStateMachineError::MissingChildTrieRoot)?,
143 };
144
145 let keys = self.commitment_state_trie_key(commitments);
146 let read_value = |key: Vec<u8>, value: Option<Vec<u8>>| {
147 value
148 .ok_or_else(|| SubstrateStateMachineError::MissingMembershipValue(key.clone()))
149 .map(|v| (key, v))
150 };
151 match hasher {
152 HashAlgorithm::Keccak => {
153 let db = StorageProof::new(storage_proof).into_memory_db::<Keccak256>();
154 let trie = TrieDBBuilder::<LayoutV0<Keccak256>>::new(&db, &root).build();
155 for key in keys {
156 let value = trie
157 .get(&key)
158 .map_err(|e| SubstrateStateMachineError::TrieError(format!("{e:?}")))?;
159 read_value(key, value)?;
160 }
161 },
162 HashAlgorithm::Blake2 => {
163 let db = StorageProof::new(storage_proof).into_memory_db::<BlakeTwo256>();
164 let trie = TrieDBBuilder::<LayoutV0<BlakeTwo256>>::new(&db, &root).build();
165 for key in keys {
166 let value = trie
167 .get(&key)
168 .map_err(|e| SubstrateStateMachineError::TrieError(format!("{e:?}")))?;
169 read_value(key, value)?;
170 }
171 },
172 }
173
174 Ok(())
175 }
176
177 fn commitment_state_trie_key(&self, commitments: Vec<H256>) -> Vec<Vec<u8>> {
178 commitments.into_iter().map(RequestCommitments::<T>::storage_key).collect()
179 }
180
181 fn receipts_state_trie_key(&self, commitments: Vec<H256>) -> Vec<Vec<u8>> {
182 commitments.into_iter().map(RequestReceipts::<T>::storage_key).collect()
183 }
184
185 fn verify_non_membership(
186 &self,
187 _host: &dyn IsmpHost,
188 commitments: Vec<H256>,
189 root: StateCommitment,
190 proof: &Proof,
191 ) -> Result<(), Error> {
192 let StateMachineProof { hasher, storage_proof } =
193 codec::Decode::decode(&mut &*proof.proof)
194 .map_err(SubstrateStateMachineError::ProofDecodeError)?;
195
196 let root = match T::Coprocessor::get() {
200 Some(id) if id == proof.height.id.state_id => root.state_root,
201 _ => root.overlay_root.ok_or(SubstrateStateMachineError::MissingChildTrieRoot)?,
202 };
203
204 let keys = self.receipts_state_trie_key(commitments);
205
206 let check_absent = |value: Option<Vec<u8>>| -> Result<(), SubstrateStateMachineError> {
207 if value.is_some() {
208 Err(SubstrateStateMachineError::DeliveredRequestsInBatch)
209 } else {
210 Ok(())
211 }
212 };
213
214 match hasher {
215 HashAlgorithm::Keccak => {
216 let db = StorageProof::new(storage_proof).into_memory_db::<Keccak256>();
217 let trie = TrieDBBuilder::<LayoutV0<Keccak256>>::new(&db, &root).build();
218 for key in keys {
219 let value = trie
220 .get(&key)
221 .map_err(|e| SubstrateStateMachineError::TrieError(format!("{e:?}")))?;
222 check_absent(value)?;
223 }
224 },
225 HashAlgorithm::Blake2 => {
226 let db = StorageProof::new(storage_proof).into_memory_db::<BlakeTwo256>();
227 let trie = TrieDBBuilder::<LayoutV0<BlakeTwo256>>::new(&db, &root).build();
228 for key in keys {
229 let value = trie
230 .get(&key)
231 .map_err(|e| SubstrateStateMachineError::TrieError(format!("{e:?}")))?;
232 check_absent(value)?;
233 }
234 },
235 }
236
237 Ok(())
238 }
239
240 fn verify_state_proof(
241 &self,
242 _host: &dyn IsmpHost,
243 keys: Vec<Vec<u8>>,
244 root: H256,
245 proof: &Proof,
246 ) -> Result<BTreeMap<Vec<u8>, Option<Vec<u8>>>, Error> {
247 let StateMachineProof { hasher, storage_proof } =
250 codec::Decode::decode(&mut &*proof.proof)
251 .map_err(SubstrateStateMachineError::ProofDecodeError)?;
252 let data = match hasher {
253 HashAlgorithm::Keccak => {
254 let db = StorageProof::new(storage_proof).into_memory_db::<Keccak256>();
255 let trie = TrieDBBuilder::<LayoutV0<Keccak256>>::new(&db, &root).build();
256 keys.into_iter()
257 .map(|key| {
258 let value = trie
259 .get(&key)
260 .map_err(|e| SubstrateStateMachineError::TrieError(format!("{e:?}")))?;
261 Ok::<_, SubstrateStateMachineError>((key, value))
262 })
263 .collect::<Result<BTreeMap<_, _>, _>>()?
264 },
265 HashAlgorithm::Blake2 => {
266 let db = StorageProof::new(storage_proof).into_memory_db::<BlakeTwo256>();
267 let trie = TrieDBBuilder::<LayoutV0<BlakeTwo256>>::new(&db, &root).build();
268 keys.into_iter()
269 .map(|key| {
270 let value = trie
271 .get(&key)
272 .map_err(|e| SubstrateStateMachineError::TrieError(format!("{e:?}")))?;
273 Ok::<_, SubstrateStateMachineError>((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}