pezsp_trie/
trie_codec.rs

1// This file is part of Bizinikiwi.
2
3// Copyright (C) Parity Technologies (UK) Ltd. and Dijital Kurdistan Tech Institute
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! Compact proof support.
19//!
20//! This uses compact proof from trie crate and extends
21//! it to bizinikiwi specific layout and child trie system.
22
23use crate::{CompactProof, HashDBT, TrieConfiguration, TrieHash, EMPTY_PREFIX};
24use alloc::{boxed::Box, vec::Vec};
25use trie_db::{CError, Trie};
26
27/// Error for trie node decoding.
28#[derive(Debug)]
29#[cfg_attr(feature = "std", derive(thiserror::Error))]
30pub enum Error<H, CodecError> {
31	#[cfg_attr(feature = "std", error("Invalid root {0:x?}, expected {1:x?}"))]
32	RootMismatch(H, H),
33	#[cfg_attr(feature = "std", error("Missing nodes in the proof"))]
34	IncompleteProof,
35	#[cfg_attr(feature = "std", error("Child node content with no root in proof"))]
36	ExtraneousChildNode,
37	#[cfg_attr(feature = "std", error("Proof of child trie {0:x?} not in parent proof"))]
38	ExtraneousChildProof(H),
39	#[cfg_attr(feature = "std", error("Invalid root {0:x?}, expected {1:x?}"))]
40	InvalidChildRoot(Vec<u8>, Vec<u8>),
41	#[cfg_attr(feature = "std", error("Trie error: {0:?}"))]
42	TrieError(Box<trie_db::TrieError<H, CodecError>>),
43}
44
45impl<H, CodecError> From<Box<trie_db::TrieError<H, CodecError>>> for Error<H, CodecError> {
46	fn from(error: Box<trie_db::TrieError<H, CodecError>>) -> Self {
47		Error::TrieError(error)
48	}
49}
50
51/// Decode a compact proof.
52///
53/// Takes as input a destination `db` for decoded node and `encoded`
54/// an iterator of compact encoded nodes.
55///
56/// Child trie are decoded in order of child trie root present
57/// in the top trie.
58pub fn decode_compact<'a, L, DB, I>(
59	db: &mut DB,
60	encoded: I,
61	expected_root: Option<&TrieHash<L>>,
62) -> Result<TrieHash<L>, Error<TrieHash<L>, CError<L>>>
63where
64	L: TrieConfiguration,
65	DB: HashDBT<L::Hash, trie_db::DBValue> + hash_db::HashDBRef<L::Hash, trie_db::DBValue>,
66	I: IntoIterator<Item = &'a [u8]>,
67{
68	let mut nodes_iter = encoded.into_iter();
69	let (top_root, _nb_used) = trie_db::decode_compact_from_iter::<L, _, _>(db, &mut nodes_iter)?;
70
71	// Only check root if expected root is passed as argument.
72	if let Some(expected_root) = expected_root.filter(|expected| *expected != &top_root) {
73		return Err(Error::RootMismatch(top_root, *expected_root));
74	}
75
76	let mut child_tries = Vec::new();
77	{
78		// fetch child trie roots
79		let trie = crate::TrieDBBuilder::<L>::new(db, &top_root).build();
80
81		let mut iter = trie.iter()?;
82
83		let childtrie_roots =
84			pezsp_core::storage::well_known_keys::DEFAULT_CHILD_STORAGE_KEY_PREFIX;
85		if iter.seek(childtrie_roots).is_ok() {
86			loop {
87				match iter.next() {
88					Some(Ok((key, value))) if key.starts_with(childtrie_roots) => {
89						// we expect all default child trie root to be correctly encoded.
90						// see other child trie functions.
91						let mut root = TrieHash::<L>::default();
92						// still in a proof so prevent panic
93						if root.as_mut().len() != value.as_slice().len() {
94							return Err(Error::InvalidChildRoot(key, value));
95						}
96						root.as_mut().copy_from_slice(value.as_ref());
97						child_tries.push(root);
98					},
99					// allow incomplete database error: we only
100					// require access to data in the proof.
101					Some(Err(error)) => match *error {
102						trie_db::TrieError::IncompleteDatabase(..) => (),
103						e => return Err(Box::new(e).into()),
104					},
105					_ => break,
106				}
107			}
108		}
109	}
110
111	if !HashDBT::<L::Hash, _>::contains(db, &top_root, EMPTY_PREFIX) {
112		return Err(Error::IncompleteProof);
113	}
114
115	let mut previous_extracted_child_trie = None;
116	let mut nodes_iter = nodes_iter.peekable();
117	for child_root in child_tries.into_iter() {
118		if previous_extracted_child_trie.is_none() && nodes_iter.peek().is_some() {
119			let (top_root, _) = trie_db::decode_compact_from_iter::<L, _, _>(db, &mut nodes_iter)?;
120			previous_extracted_child_trie = Some(top_root);
121		}
122
123		// we do not early exit on root mismatch but try the
124		// other read from proof (some child root may be
125		// in proof without actual child content).
126		if Some(child_root) == previous_extracted_child_trie {
127			previous_extracted_child_trie = None;
128		}
129	}
130
131	if let Some(child_root) = previous_extracted_child_trie {
132		// A child root was read from proof but is not present
133		// in top trie.
134		return Err(Error::ExtraneousChildProof(child_root));
135	}
136
137	if nodes_iter.next().is_some() {
138		return Err(Error::ExtraneousChildNode);
139	}
140
141	Ok(top_root)
142}
143
144/// Encode a compact proof.
145///
146/// Takes as input all full encoded node from the proof, and
147/// the root.
148/// Then parse all child trie root and compress main trie content first
149/// then all child trie contents.
150/// Child trie are ordered by the order of their roots in the top trie.
151pub fn encode_compact<L, DB>(
152	partial_db: &DB,
153	root: &TrieHash<L>,
154) -> Result<CompactProof, Error<TrieHash<L>, CError<L>>>
155where
156	L: TrieConfiguration,
157	DB: HashDBT<L::Hash, trie_db::DBValue> + hash_db::HashDBRef<L::Hash, trie_db::DBValue>,
158{
159	let mut child_tries = Vec::new();
160	let mut compact_proof = {
161		let trie = crate::TrieDBBuilder::<L>::new(partial_db, root).build();
162
163		let mut iter = trie.iter()?;
164
165		let childtrie_roots =
166			pezsp_core::storage::well_known_keys::DEFAULT_CHILD_STORAGE_KEY_PREFIX;
167		if iter.seek(childtrie_roots).is_ok() {
168			loop {
169				match iter.next() {
170					Some(Ok((key, value))) if key.starts_with(childtrie_roots) => {
171						let mut root = TrieHash::<L>::default();
172						if root.as_mut().len() != value.as_slice().len() {
173							// some child trie root in top trie are not an encoded hash.
174							return Err(Error::InvalidChildRoot(key.to_vec(), value.to_vec()));
175						}
176						root.as_mut().copy_from_slice(value.as_ref());
177						child_tries.push(root);
178					},
179					// allow incomplete database error: we only
180					// require access to data in the proof.
181					Some(Err(error)) => match *error {
182						trie_db::TrieError::IncompleteDatabase(..) => (),
183						e => return Err(Box::new(e).into()),
184					},
185					_ => break,
186				}
187			}
188		}
189
190		trie_db::encode_compact::<L>(&trie)?
191	};
192
193	for child_root in child_tries {
194		if !HashDBT::<L::Hash, _>::contains(partial_db, &child_root, EMPTY_PREFIX) {
195			// child proof are allowed to be missing (unused root can be included
196			// due to trie structure modification).
197			continue;
198		}
199
200		let trie = crate::TrieDBBuilder::<L>::new(partial_db, &child_root).build();
201		let child_proof = trie_db::encode_compact::<L>(&trie)?;
202
203		compact_proof.extend(child_proof);
204	}
205
206	Ok(CompactProof { encoded_nodes: compact_proof })
207}
208
209#[cfg(test)]
210mod tests {
211	use super::*;
212	use crate::{delta_trie_root, recorder::IgnoredNodes, HashDB, StorageProof};
213	use codec::Encode;
214	use hash_db::AsHashDB;
215	use pezsp_core::{Blake2Hasher, H256};
216	use std::collections::HashSet;
217	use trie_db::{DBValue, Trie, TrieDBBuilder, TrieDBMutBuilder, TrieHash, TrieMut};
218
219	type MemoryDB = crate::MemoryDB<pezsp_core::Blake2Hasher>;
220	type Layout = crate::LayoutV1<pezsp_core::Blake2Hasher>;
221	type Recorder = crate::recorder::Recorder<pezsp_core::Blake2Hasher>;
222
223	fn create_trie(num_keys: u32) -> (MemoryDB, TrieHash<Layout>) {
224		let mut db = MemoryDB::default();
225		let mut root = Default::default();
226
227		{
228			let mut trie = TrieDBMutBuilder::<Layout>::new(&mut db, &mut root).build();
229			for i in 0..num_keys {
230				trie.insert(
231					&i.encode(),
232					&vec![1u8; 64].into_iter().chain(i.encode()).collect::<Vec<_>>(),
233				)
234				.expect("Inserts data");
235			}
236		}
237
238		(db, root)
239	}
240
241	struct Overlay<'a> {
242		db: &'a MemoryDB,
243		write: MemoryDB,
244	}
245
246	impl hash_db::HashDB<pezsp_core::Blake2Hasher, DBValue> for Overlay<'_> {
247		fn get(
248			&self,
249			key: &<pezsp_core::Blake2Hasher as hash_db::Hasher>::Out,
250			prefix: hash_db::Prefix,
251		) -> Option<DBValue> {
252			HashDB::get(self.db, key, prefix)
253		}
254
255		fn contains(
256			&self,
257			key: &<pezsp_core::Blake2Hasher as hash_db::Hasher>::Out,
258			prefix: hash_db::Prefix,
259		) -> bool {
260			HashDB::contains(self.db, key, prefix)
261		}
262
263		fn insert(
264			&mut self,
265			prefix: hash_db::Prefix,
266			value: &[u8],
267		) -> <pezsp_core::Blake2Hasher as hash_db::Hasher>::Out {
268			self.write.insert(prefix, value)
269		}
270
271		fn emplace(
272			&mut self,
273			key: <pezsp_core::Blake2Hasher as hash_db::Hasher>::Out,
274			prefix: hash_db::Prefix,
275			value: DBValue,
276		) {
277			self.write.emplace(key, prefix, value);
278		}
279
280		fn remove(
281			&mut self,
282			key: &<pezsp_core::Blake2Hasher as hash_db::Hasher>::Out,
283			prefix: hash_db::Prefix,
284		) {
285			self.write.remove(key, prefix);
286		}
287	}
288
289	impl AsHashDB<Blake2Hasher, DBValue> for Overlay<'_> {
290		fn as_hash_db(&self) -> &dyn HashDBT<Blake2Hasher, DBValue> {
291			self
292		}
293
294		fn as_hash_db_mut<'a>(&'a mut self) -> &'a mut (dyn HashDBT<Blake2Hasher, DBValue> + 'a) {
295			self
296		}
297	}
298
299	fn emulate_block_building(
300		state: &MemoryDB,
301		root: H256,
302		read_keys: &[u32],
303		write_keys: &[u32],
304		nodes_to_ignore: IgnoredNodes<H256>,
305	) -> (Recorder, MemoryDB, H256) {
306		let recorder = Recorder::with_ignored_nodes(nodes_to_ignore);
307
308		{
309			let mut trie_recorder = recorder.as_trie_recorder(root);
310			let trie = TrieDBBuilder::<Layout>::new(state, &root)
311				.with_recorder(&mut trie_recorder)
312				.build();
313
314			for key in read_keys {
315				trie.get(&key.encode()).unwrap().unwrap();
316			}
317		}
318
319		let mut overlay = Overlay { db: state, write: Default::default() };
320
321		let new_root = {
322			let mut trie_recorder = recorder.as_trie_recorder(root);
323			delta_trie_root::<Layout, _, _, _, _, _>(
324				&mut overlay,
325				root,
326				write_keys.iter().map(|k| {
327					(
328						k.encode(),
329						Some(vec![2u8; 64].into_iter().chain(k.encode()).collect::<Vec<_>>()),
330					)
331				}),
332				Some(&mut trie_recorder),
333				None,
334			)
335			.unwrap()
336		};
337
338		(recorder, overlay.write, new_root)
339	}
340
341	fn build_known_nodes_list(recorder: &Recorder, transaction: &MemoryDB) -> IgnoredNodes<H256> {
342		let mut ignored_nodes =
343			IgnoredNodes::from_storage_proof::<Blake2Hasher>(&recorder.to_storage_proof());
344
345		ignored_nodes.extend(IgnoredNodes::from_memory_db::<Blake2Hasher, _>(transaction.clone()));
346
347		ignored_nodes
348	}
349
350	#[test]
351	fn ensure_multiple_tries_encode_compact_works() {
352		let (mut db, root) = create_trie(100);
353
354		let mut nodes_to_ignore = IgnoredNodes::default();
355		let (recorder, transaction, root1) = emulate_block_building(
356			&db,
357			root,
358			&[2, 4, 5, 6, 7, 8],
359			&[9, 10, 11, 12, 13, 14],
360			nodes_to_ignore.clone(),
361		);
362
363		db.consolidate(transaction.clone());
364		nodes_to_ignore.extend(build_known_nodes_list(&recorder, &transaction));
365
366		let (recorder2, transaction, root2) = emulate_block_building(
367			&db,
368			root1,
369			&[9, 10, 11, 12, 13, 14],
370			&[15, 16, 17, 18, 19, 20],
371			nodes_to_ignore.clone(),
372		);
373
374		db.consolidate(transaction.clone());
375		nodes_to_ignore.extend(build_known_nodes_list(&recorder2, &transaction));
376
377		let (recorder3, _, root3) = emulate_block_building(
378			&db,
379			root2,
380			&[20, 30, 40, 41, 42],
381			&[80, 90, 91, 92, 93],
382			nodes_to_ignore,
383		);
384
385		let proof = recorder.to_storage_proof();
386		let proof2 = recorder2.to_storage_proof();
387		let proof3 = recorder3.to_storage_proof();
388
389		let mut combined = HashSet::<Vec<u8>>::from_iter(proof.into_iter_nodes());
390		proof2.iter_nodes().for_each(|n| assert!(combined.insert(n.clone())));
391		proof3.iter_nodes().for_each(|n| assert!(combined.insert(n.clone())));
392
393		let proof = StorageProof::new(combined.into_iter());
394
395		let compact_proof = encode_compact::<Layout, _>(&proof.to_memory_db(), &root).unwrap();
396
397		assert!(proof.encoded_size() > compact_proof.encoded_size());
398
399		let mut res_db = crate::MemoryDB::<Blake2Hasher>::new(&[]);
400		decode_compact::<Layout, _, _>(
401			&mut res_db,
402			compact_proof.iter_compact_encoded_nodes(),
403			Some(&root),
404		)
405		.unwrap();
406
407		let (_, transaction, root1_proof) = emulate_block_building(
408			&res_db,
409			root,
410			&[2, 4, 5, 6, 7, 8],
411			&[9, 10, 11, 12, 13, 14],
412			Default::default(),
413		);
414
415		assert_eq!(root1, root1_proof);
416
417		res_db.consolidate(transaction);
418
419		let (_, transaction2, root2_proof) = emulate_block_building(
420			&res_db,
421			root1,
422			&[9, 10, 11, 12, 13, 14],
423			&[15, 16, 17, 18, 19, 20],
424			Default::default(),
425		);
426
427		assert_eq!(root2, root2_proof);
428
429		res_db.consolidate(transaction2);
430
431		let (_, _, root3_proof) = emulate_block_building(
432			&res_db,
433			root2,
434			&[20, 30, 40, 41, 42],
435			&[80, 90, 91, 92, 93],
436			Default::default(),
437		);
438
439		assert_eq!(root3, root3_proof);
440	}
441}