referrerpolicy=no-referrer-when-downgrade

sp_trie/
trie_codec.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
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 substrate 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/// The decoded root is always checked against `expected_root`.
57///
58/// Child trie are decoded in order of child trie root present
59/// in the top trie.
60pub fn decode_compact<'a, L, DB, I>(
61	db: &mut DB,
62	encoded: I,
63	expected_root: &TrieHash<L>,
64) -> Result<TrieHash<L>, Error<TrieHash<L>, CError<L>>>
65where
66	L: TrieConfiguration,
67	DB: HashDBT<L::Hash, trie_db::DBValue> + hash_db::HashDBRef<L::Hash, trie_db::DBValue>,
68	I: IntoIterator<Item = &'a [u8]>,
69{
70	let mut nodes_iter = encoded.into_iter();
71	let (top_root, _nb_used) = trie_db::decode_compact_from_iter::<L, _, _>(db, &mut nodes_iter)?;
72
73	if &top_root != expected_root {
74		return Err(Error::RootMismatch(top_root, *expected_root));
75	}
76
77	let mut child_tries = Vec::new();
78	{
79		// fetch child trie roots
80		let trie = crate::TrieDBBuilder::<L>::new(db, &top_root).build();
81
82		let mut iter = trie.iter()?;
83
84		let childtrie_roots = sp_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 seen = trie_db::SeenHashes::<L>::default();
160	let mut child_tries = Vec::new();
161	let mut compact_proof = {
162		let trie = crate::TrieDBBuilder::<L>::new(partial_db, root).build();
163
164		let mut iter = trie.iter()?;
165
166		let childtrie_roots = sp_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_skip_duplicates::<L>(&trie, &mut seen)?
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_skip_duplicates::<L>(&trie, &mut seen)?;
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 sp_core::{Blake2Hasher, H256};
216	use std::collections::HashSet;
217	use trie_db::{DBValue, Trie, TrieDBBuilder, TrieDBMutBuilder, TrieHash, TrieMut};
218
219	type MemoryDB = crate::MemoryDB<sp_core::Blake2Hasher>;
220	type Layout = crate::LayoutV1<sp_core::Blake2Hasher>;
221	type Recorder = crate::recorder::Recorder<sp_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	#[test]
242	fn values_shared_between_top_and_child_trie_are_deduplicated() {
243		let mut db = MemoryDB::default();
244
245		// A value above `TRIE_VALUE_NODE_THRESHOLD` that is stored as a separate value node
246		// referenced by hash. It is present in the top trie and in a child trie; the
247		// deduplication state must be shared across the per-trie encodings so it is only
248		// emitted once in the whole proof.
249		let shared_value = vec![42u8; 64];
250
251		let mut child_root = Default::default();
252		{
253			let mut trie = TrieDBMutBuilder::<Layout>::new(&mut db, &mut child_root).build();
254			trie.insert(b"child_key", &shared_value).expect("Inserts data");
255		}
256
257		let mut root = Default::default();
258		{
259			let mut trie = TrieDBMutBuilder::<Layout>::new(&mut db, &mut root).build();
260			trie.insert(b"top_key", &shared_value).expect("Inserts data");
261
262			let mut child_storage_key =
263				sp_core::storage::well_known_keys::DEFAULT_CHILD_STORAGE_KEY_PREFIX.to_vec();
264			child_storage_key.extend(b"child1");
265			trie.insert(&child_storage_key, child_root.as_ref()).expect("Inserts data");
266		}
267
268		let compact_proof = encode_compact::<Layout, _>(&db, &root).unwrap();
269
270		// The shared value must only be contained once in the compact proof, even though the
271		// top trie and the child trie are encoded separately.
272		let occurrences = compact_proof
273			.encoded_nodes
274			.iter()
275			.filter(|node| node.as_slice() == shared_value.as_slice())
276			.count();
277		assert_eq!(occurrences, 1);
278
279		// The proof still decodes and gives access to the value through both tries.
280		let mut res_db = MemoryDB::new(&[]);
281		decode_compact::<Layout, _, _>(
282			&mut res_db,
283			compact_proof.iter_compact_encoded_nodes(),
284			&root,
285		)
286		.unwrap();
287
288		let trie = TrieDBBuilder::<Layout>::new(&res_db, &root).build();
289		assert_eq!(trie.get(b"top_key").unwrap().unwrap(), shared_value);
290
291		let child_trie = TrieDBBuilder::<Layout>::new(&res_db, &child_root).build();
292		assert_eq!(child_trie.get(b"child_key").unwrap().unwrap(), shared_value);
293	}
294
295	struct Overlay<'a> {
296		db: &'a MemoryDB,
297		write: MemoryDB,
298	}
299
300	impl hash_db::HashDB<sp_core::Blake2Hasher, DBValue> for Overlay<'_> {
301		fn get(
302			&self,
303			key: &<sp_core::Blake2Hasher as hash_db::Hasher>::Out,
304			prefix: hash_db::Prefix,
305		) -> Option<DBValue> {
306			HashDB::get(self.db, key, prefix)
307		}
308
309		fn contains(
310			&self,
311			key: &<sp_core::Blake2Hasher as hash_db::Hasher>::Out,
312			prefix: hash_db::Prefix,
313		) -> bool {
314			HashDB::contains(self.db, key, prefix)
315		}
316
317		fn insert(
318			&mut self,
319			prefix: hash_db::Prefix,
320			value: &[u8],
321		) -> <sp_core::Blake2Hasher as hash_db::Hasher>::Out {
322			self.write.insert(prefix, value)
323		}
324
325		fn emplace(
326			&mut self,
327			key: <sp_core::Blake2Hasher as hash_db::Hasher>::Out,
328			prefix: hash_db::Prefix,
329			value: DBValue,
330		) {
331			self.write.emplace(key, prefix, value);
332		}
333
334		fn remove(
335			&mut self,
336			key: &<sp_core::Blake2Hasher as hash_db::Hasher>::Out,
337			prefix: hash_db::Prefix,
338		) {
339			self.write.remove(key, prefix);
340		}
341	}
342
343	impl AsHashDB<Blake2Hasher, DBValue> for Overlay<'_> {
344		fn as_hash_db(&self) -> &dyn HashDBT<Blake2Hasher, DBValue> {
345			self
346		}
347
348		fn as_hash_db_mut<'a>(&'a mut self) -> &'a mut (dyn HashDBT<Blake2Hasher, DBValue> + 'a) {
349			self
350		}
351	}
352
353	fn emulate_block_building(
354		state: &MemoryDB,
355		root: H256,
356		read_keys: &[u32],
357		write_keys: &[u32],
358		nodes_to_ignore: IgnoredNodes<H256>,
359	) -> (Recorder, MemoryDB, H256) {
360		let recorder = Recorder::with_ignored_nodes(nodes_to_ignore);
361
362		{
363			let mut trie_recorder = recorder.as_trie_recorder(root);
364			let trie = TrieDBBuilder::<Layout>::new(state, &root)
365				.with_recorder(&mut trie_recorder)
366				.build();
367
368			for key in read_keys {
369				trie.get(&key.encode()).unwrap().unwrap();
370			}
371		}
372
373		let mut overlay = Overlay { db: state, write: Default::default() };
374
375		let new_root = {
376			let mut trie_recorder = recorder.as_trie_recorder(root);
377			delta_trie_root::<Layout, _, _, _, _, _>(
378				&mut overlay,
379				root,
380				write_keys.iter().map(|k| {
381					(
382						k.encode(),
383						Some(vec![2u8; 64].into_iter().chain(k.encode()).collect::<Vec<_>>()),
384					)
385				}),
386				Some(&mut trie_recorder),
387				None,
388			)
389			.unwrap()
390		};
391
392		(recorder, overlay.write, new_root)
393	}
394
395	fn build_known_nodes_list(recorder: &Recorder, transaction: &MemoryDB) -> IgnoredNodes<H256> {
396		let mut ignored_nodes =
397			IgnoredNodes::from_storage_proof::<Blake2Hasher>(&recorder.to_storage_proof());
398
399		ignored_nodes.extend(IgnoredNodes::from_memory_db::<Blake2Hasher, _>(transaction.clone()));
400
401		ignored_nodes
402	}
403
404	#[test]
405	fn ensure_multiple_tries_encode_compact_works() {
406		let (mut db, root) = create_trie(100);
407
408		let mut nodes_to_ignore = IgnoredNodes::default();
409		let (recorder, transaction, root1) = emulate_block_building(
410			&db,
411			root,
412			&[2, 4, 5, 6, 7, 8],
413			&[9, 10, 11, 12, 13, 14],
414			nodes_to_ignore.clone(),
415		);
416
417		db.consolidate(transaction.clone());
418		nodes_to_ignore.extend(build_known_nodes_list(&recorder, &transaction));
419
420		let (recorder2, transaction, root2) = emulate_block_building(
421			&db,
422			root1,
423			&[9, 10, 11, 12, 13, 14],
424			&[15, 16, 17, 18, 19, 20],
425			nodes_to_ignore.clone(),
426		);
427
428		db.consolidate(transaction.clone());
429		nodes_to_ignore.extend(build_known_nodes_list(&recorder2, &transaction));
430
431		let (recorder3, _, root3) = emulate_block_building(
432			&db,
433			root2,
434			&[20, 30, 40, 41, 42],
435			&[80, 90, 91, 92, 93],
436			nodes_to_ignore,
437		);
438
439		let proof = recorder.to_storage_proof();
440		let proof2 = recorder2.to_storage_proof();
441		let proof3 = recorder3.to_storage_proof();
442
443		let mut combined = HashSet::<Vec<u8>>::from_iter(proof.into_iter_nodes());
444		proof2.iter_nodes().for_each(|n| assert!(combined.insert(n.clone())));
445		proof3.iter_nodes().for_each(|n| assert!(combined.insert(n.clone())));
446
447		let proof = StorageProof::new(combined.into_iter());
448
449		let compact_proof = encode_compact::<Layout, _>(&proof.to_memory_db(), &root).unwrap();
450
451		assert!(proof.encoded_size() > compact_proof.encoded_size());
452
453		let mut res_db = crate::MemoryDB::<Blake2Hasher>::new(&[]);
454		decode_compact::<Layout, _, _>(
455			&mut res_db,
456			compact_proof.iter_compact_encoded_nodes(),
457			&root,
458		)
459		.unwrap();
460
461		let (_, transaction, root1_proof) = emulate_block_building(
462			&res_db,
463			root,
464			&[2, 4, 5, 6, 7, 8],
465			&[9, 10, 11, 12, 13, 14],
466			Default::default(),
467		);
468
469		assert_eq!(root1, root1_proof);
470
471		res_db.consolidate(transaction);
472
473		let (_, transaction2, root2_proof) = emulate_block_building(
474			&res_db,
475			root1,
476			&[9, 10, 11, 12, 13, 14],
477			&[15, 16, 17, 18, 19, 20],
478			Default::default(),
479		);
480
481		assert_eq!(root2, root2_proof);
482
483		res_db.consolidate(transaction2);
484
485		let (_, _, root3_proof) = emulate_block_building(
486			&res_db,
487			root2,
488			&[20, 30, 40, 41, 42],
489			&[80, 90, 91, 92, 93],
490			Default::default(),
491		);
492
493		assert_eq!(root3, root3_proof);
494	}
495}