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
41#[derive(Encode, sp_runtime::RuntimeDebug)]
43#[cfg_attr(feature = "std", derive(Decode))]
44pub enum InherentError {
45 InvalidProof,
46 TrieError,
47}
48
49impl IsFatalError for InherentError {
50 fn is_fatal_error(&self) -> bool {
51 true
52 }
53}
54
55#[derive(Encode, Decode, DecodeWithMemTracking, Clone, PartialEq, Debug, scale_info::TypeInfo)]
58pub struct TransactionStorageProof {
59 pub chunk: Vec<u8>,
61 pub proof: Vec<Vec<u8>>,
63}
64
65pub trait TransactionStorageProofInherentData {
67 fn storage_proof(&self) -> Result<Option<TransactionStorageProof>, Error>;
69}
70
71impl TransactionStorageProofInherentData for InherentData {
72 fn storage_proof(&self) -> Result<Option<TransactionStorageProof>, Error> {
73 self.get_data(&INHERENT_IDENTIFIER)
74 }
75}
76
77#[cfg(feature = "std")]
79pub struct InherentDataProvider {
80 proof: Option<TransactionStorageProof>,
81}
82
83#[cfg(feature = "std")]
84impl InherentDataProvider {
85 pub fn new(proof: Option<TransactionStorageProof>) -> Self {
86 InherentDataProvider { proof }
87 }
88}
89
90#[cfg(feature = "std")]
91#[async_trait::async_trait]
92impl sp_inherents::InherentDataProvider for InherentDataProvider {
93 async fn provide_inherent_data(&self, inherent_data: &mut InherentData) -> Result<(), Error> {
94 if let Some(proof) = &self.proof {
95 inherent_data.put_data(INHERENT_IDENTIFIER, proof)
96 } else {
97 Ok(())
98 }
99 }
100
101 async fn try_handle_error(
102 &self,
103 identifier: &InherentIdentifier,
104 mut error: &[u8],
105 ) -> Option<Result<(), Error>> {
106 if *identifier != INHERENT_IDENTIFIER {
107 return None
108 }
109
110 let error = InherentError::decode(&mut error).ok()?;
111
112 Some(Err(Error::Application(Box::from(format!("{:?}", error)))))
113 }
114}
115
116pub fn random_chunk(random_hash: &[u8], total_chunks: u32) -> u32 {
118 let mut buf = [0u8; 8];
119 buf.copy_from_slice(&random_hash[0..8]);
120 let random_u64 = u64::from_be_bytes(buf);
121 (random_u64 % total_chunks as u64) as u32
122}
123
124pub fn encode_index(input: u32) -> Vec<u8> {
126 codec::Encode::encode(&codec::Compact(input))
127}
128
129pub trait IndexedBody<B: BlockT> {
131 fn block_indexed_body(&self, number: NumberFor<B>) -> Result<Option<Vec<Vec<u8>>>, Error>;
137
138 fn number(&self, hash: B::Hash) -> Result<Option<NumberFor<B>>, Error>;
140}
141
142#[cfg(feature = "std")]
143pub mod registration {
144 use super::*;
145 use sp_runtime::traits::{Block as BlockT, One, Saturating, Zero};
146 use sp_trie::TrieMut;
147
148 type Hasher = sp_core::Blake2Hasher;
149 type TrieLayout = sp_trie::LayoutV1<Hasher>;
150
151 pub fn new_data_provider<B, C>(
153 client: &C,
154 parent: &B::Hash,
155 ) -> Result<InherentDataProvider, Error>
156 where
157 B: BlockT,
158 C: IndexedBody<B>,
159 {
160 let parent_number = client.number(*parent)?.unwrap_or(Zero::zero());
161 let number = parent_number
162 .saturating_add(One::one())
163 .saturating_sub(DEFAULT_STORAGE_PERIOD.into());
164 if number.is_zero() {
165 return Ok(InherentDataProvider::new(None))
167 }
168
169 let proof = match client.block_indexed_body(number)? {
170 Some(transactions) if !transactions.is_empty() =>
171 Some(build_proof(parent.as_ref(), transactions)?),
172 Some(_) | None => {
173 None
175 },
176 };
177 Ok(InherentDataProvider::new(proof))
178 }
179
180 pub fn build_proof(
182 random_hash: &[u8],
183 transactions: Vec<Vec<u8>>,
184 ) -> Result<TransactionStorageProof, Error> {
185 let mut db = sp_trie::MemoryDB::<Hasher>::default();
186
187 let mut target_chunk = None;
188 let mut target_root = Default::default();
189 let mut target_chunk_key = Default::default();
190 let mut chunk_proof = Default::default();
191
192 let total_chunks: u64 =
193 transactions.iter().map(|t| t.len().div_ceil(CHUNK_SIZE) as u64).sum();
194 let mut buf = [0u8; 8];
195 buf.copy_from_slice(&random_hash[0..8]);
196 let random_u64 = u64::from_be_bytes(buf);
197 let target_chunk_index = random_u64 % total_chunks;
198 let mut chunk_index = 0;
200 for transaction in transactions {
201 let mut transaction_root = sp_trie::empty_trie_root::<TrieLayout>();
202 {
203 let mut trie =
204 sp_trie::TrieDBMutBuilder::<TrieLayout>::new(&mut db, &mut transaction_root)
205 .build();
206 let chunks = transaction.chunks(CHUNK_SIZE).map(|c| c.to_vec());
207 for (index, chunk) in chunks.enumerate() {
208 let index = encode_index(index as u32);
209 trie.insert(&index, &chunk).map_err(|e| Error::Application(Box::new(e)))?;
210 if chunk_index == target_chunk_index {
211 target_chunk = Some(chunk);
212 target_chunk_key = index;
213 }
214 chunk_index += 1;
215 }
216 trie.commit();
217 }
218 if target_chunk.is_some() && target_root == Default::default() {
219 target_root = transaction_root;
220 chunk_proof = sp_trie::generate_trie_proof::<TrieLayout, _, _, _>(
221 &db,
222 transaction_root,
223 &[target_chunk_key.clone()],
224 )
225 .map_err(|e| Error::Application(Box::new(e)))?;
226 }
227 }
228
229 Ok(TransactionStorageProof { proof: chunk_proof, chunk: target_chunk.unwrap() })
230 }
231
232 #[test]
233 fn build_proof_check() {
234 use std::str::FromStr;
235 let random = [0u8; 32];
236 let proof = build_proof(&random, vec![vec![42]]).unwrap();
237 let root = sp_core::H256::from_str(
238 "0xff8611a4d212fc161dae19dd57f0f1ba9309f45d6207da13f2d3eab4c6839e91",
239 )
240 .unwrap();
241 sp_trie::verify_trie_proof::<TrieLayout, _, _, _>(
242 &root,
243 &proof.proof,
244 &[(encode_index(0), Some(proof.chunk))],
245 )
246 .unwrap();
247 }
248}