sp_transaction_storage_proof/
lib.rs1#![cfg_attr(not(feature = "std"), no_std)]
22
23extern crate alloc;
24
25use core::result::Result;
26
27use alloc::vec::Vec;
28use codec::{Decode, DecodeWithMemTracking, Encode};
29use sp_inherents::{InherentData, InherentIdentifier, IsFatalError};
30use sp_runtime::traits::{Block as BlockT, NumberFor};
31
32pub use sp_inherents::Error;
33
34pub const INHERENT_IDENTIFIER: InherentIdentifier = *b"tx_proof";
36pub const DEFAULT_STORAGE_PERIOD: u32 = 100800;
38pub const CHUNK_SIZE: usize = 256;
40
41pub type ChunkIndex = u32;
43
44#[derive(Encode, sp_runtime::RuntimeDebug)]
46#[cfg_attr(feature = "std", derive(Decode))]
47pub enum InherentError {
48 InvalidProof,
49 TrieError,
50}
51
52impl IsFatalError for InherentError {
53 fn is_fatal_error(&self) -> bool {
54 true
55 }
56}
57
58#[derive(Encode, Decode, DecodeWithMemTracking, Clone, PartialEq, Debug, scale_info::TypeInfo)]
61pub struct TransactionStorageProof {
62 pub chunk: Vec<u8>,
64 pub proof: Vec<Vec<u8>>,
66}
67
68pub trait TransactionStorageProofInherentData {
70 fn storage_proof(&self) -> Result<Option<TransactionStorageProof>, Error>;
72}
73
74impl TransactionStorageProofInherentData for InherentData {
75 fn storage_proof(&self) -> Result<Option<TransactionStorageProof>, Error> {
76 self.get_data(&INHERENT_IDENTIFIER)
77 }
78}
79
80#[cfg(feature = "std")]
82pub struct InherentDataProvider {
83 proof: Option<TransactionStorageProof>,
84}
85
86#[cfg(feature = "std")]
87impl InherentDataProvider {
88 pub fn new(proof: Option<TransactionStorageProof>) -> Self {
89 InherentDataProvider { proof }
90 }
91}
92
93#[cfg(feature = "std")]
94#[async_trait::async_trait]
95impl sp_inherents::InherentDataProvider for InherentDataProvider {
96 async fn provide_inherent_data(&self, inherent_data: &mut InherentData) -> Result<(), Error> {
97 if let Some(proof) = &self.proof {
98 inherent_data.put_data(INHERENT_IDENTIFIER, proof)
99 } else {
100 Ok(())
101 }
102 }
103
104 async fn try_handle_error(
105 &self,
106 identifier: &InherentIdentifier,
107 mut error: &[u8],
108 ) -> Option<Result<(), Error>> {
109 if *identifier != INHERENT_IDENTIFIER {
110 return None;
111 }
112
113 let error = InherentError::decode(&mut error).ok()?;
114
115 Some(Err(Error::Application(Box::from(format!("{:?}", error)))))
116 }
117}
118
119pub fn random_chunk(random_hash: &[u8], total_chunks: ChunkIndex) -> ChunkIndex {
125 let mut buf = [0u8; 8];
126 buf.copy_from_slice(&random_hash[0..8]);
127 let random_u64 = u64::from_be_bytes(buf);
128 (random_u64 % total_chunks as u64) as u32
129}
130
131pub fn num_chunks(bytes: u32) -> ChunkIndex {
135 (bytes as u64).div_ceil(CHUNK_SIZE as u64) as u32
136}
137
138pub fn encode_index(index: ChunkIndex) -> Vec<u8> {
142 codec::Encode::encode(&codec::Compact(index))
143}
144
145pub trait IndexedBody<B: BlockT> {
147 fn block_indexed_body(&self, number: NumberFor<B>) -> Result<Option<Vec<Vec<u8>>>, Error>;
153
154 fn number(&self, hash: B::Hash) -> Result<Option<NumberFor<B>>, Error>;
156}
157
158#[cfg(feature = "std")]
159pub mod registration {
160 use super::*;
161 use sp_runtime::traits::{Block as BlockT, One, Saturating, Zero};
162 use sp_trie::TrieMut;
163
164 type Hasher = sp_core::Blake2Hasher;
165 type TrieLayout = sp_trie::LayoutV1<Hasher>;
166
167 pub fn new_data_provider<B, C>(
169 client: &C,
170 parent: &B::Hash,
171 ) -> Result<InherentDataProvider, Error>
172 where
173 B: BlockT,
174 C: IndexedBody<B>,
175 {
176 let parent_number = client.number(*parent)?.unwrap_or(Zero::zero());
177 let number = parent_number
178 .saturating_add(One::one())
179 .saturating_sub(DEFAULT_STORAGE_PERIOD.into());
180 if number.is_zero() {
181 return Ok(InherentDataProvider::new(None));
183 }
184
185 let proof = match client.block_indexed_body(number)? {
186 Some(transactions) => build_proof(parent.as_ref(), transactions)?,
187 None => {
188 None
190 },
191 };
192 Ok(InherentDataProvider::new(proof))
193 }
194
195 pub fn build_proof(
197 random_hash: &[u8],
198 transactions: Vec<Vec<u8>>,
199 ) -> Result<Option<TransactionStorageProof>, Error> {
200 let total_chunks: ChunkIndex =
202 transactions.iter().map(|t| num_chunks(t.len() as u32)).sum();
203 if total_chunks.is_zero() {
204 return Ok(None);
205 }
206 let selected_chunk_index = random_chunk(random_hash, total_chunks);
207
208 let mut chunk_index = 0;
210 for transaction in transactions {
211 let mut selected_chunk_and_key = None;
212 let mut db = sp_trie::MemoryDB::<Hasher>::default();
213 let mut transaction_root = sp_trie::empty_trie_root::<TrieLayout>();
214 {
215 let mut trie =
216 sp_trie::TrieDBMutBuilder::<TrieLayout>::new(&mut db, &mut transaction_root)
217 .build();
218 let chunks = transaction.chunks(CHUNK_SIZE).map(|c| c.to_vec());
219 for (index, chunk) in chunks.enumerate() {
220 let index = encode_index(index as u32);
221 trie.insert(&index, &chunk).map_err(|e| Error::Application(Box::new(e)))?;
222 if chunk_index == selected_chunk_index {
223 selected_chunk_and_key = Some((chunk, index));
224 }
225 chunk_index += 1;
226 }
227 trie.commit();
228 }
229 if let Some((target_chunk, target_chunk_key)) = selected_chunk_and_key {
230 let chunk_proof = sp_trie::generate_trie_proof::<TrieLayout, _, _, _>(
231 &db,
232 transaction_root,
233 &[target_chunk_key],
234 )
235 .map_err(|e| Error::Application(Box::new(e)))?;
236
237 return Ok(Some(TransactionStorageProof {
240 proof: chunk_proof,
241 chunk: target_chunk,
242 }));
243 }
244 }
245
246 Err(Error::Application(Box::from(format!("No chunk (total_chunks: {total_chunks}) matched the selected_chunk_index: {selected_chunk_index}; logic error!"))))
247 }
248
249 #[test]
250 fn build_proof_check() {
251 use std::str::FromStr;
252 let random = [0u8; 32];
253 let proof = build_proof(&random, vec![vec![42]]).unwrap().unwrap();
254 let root = sp_core::H256::from_str(
255 "0xff8611a4d212fc161dae19dd57f0f1ba9309f45d6207da13f2d3eab4c6839e91",
256 )
257 .unwrap();
258 sp_trie::verify_trie_proof::<TrieLayout, _, _, _>(
259 &root,
260 &proof.proof,
261 &[(encode_index(0), Some(proof.chunk))],
262 )
263 .unwrap();
264
265 assert!(build_proof(&random, vec![]).unwrap().is_none());
267 assert!(build_proof(&random, vec![vec![]]).unwrap().is_none());
268 }
269}