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/// 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 = sp_core::storage::well_known_keys::DEFAULT_CHILD_STORAGE_KEY_PREFIX;
84		if iter.seek(childtrie_roots).is_ok() {
85			loop {
86				match iter.next() {
87					Some(Ok((key, value))) if key.starts_with(childtrie_roots) => {
88						// we expect all default child trie root to be correctly encoded.
89						// see other child trie functions.
90						let mut root = TrieHash::<L>::default();
91						// still in a proof so prevent panic
92						if root.as_mut().len() != value.as_slice().len() {
93							return Err(Error::InvalidChildRoot(key, value))
94						}
95						root.as_mut().copy_from_slice(value.as_ref());
96						child_tries.push(root);
97					},
98					// allow incomplete database error: we only
99					// require access to data in the proof.
100					Some(Err(error)) => match *error {
101						trie_db::TrieError::IncompleteDatabase(..) => (),
102						e => return Err(Box::new(e).into()),
103					},
104					_ => break,
105				}
106			}
107		}
108	}
109
110	if !HashDBT::<L::Hash, _>::contains(db, &top_root, EMPTY_PREFIX) {
111		return Err(Error::IncompleteProof)
112	}
113
114	let mut previous_extracted_child_trie = None;
115	let mut nodes_iter = nodes_iter.peekable();
116	for child_root in child_tries.into_iter() {
117		if previous_extracted_child_trie.is_none() && nodes_iter.peek().is_some() {
118			let (top_root, _) = trie_db::decode_compact_from_iter::<L, _, _>(db, &mut nodes_iter)?;
119			previous_extracted_child_trie = Some(top_root);
120		}
121
122		// we do not early exit on root mismatch but try the
123		// other read from proof (some child root may be
124		// in proof without actual child content).
125		if Some(child_root) == previous_extracted_child_trie {
126			previous_extracted_child_trie = None;
127		}
128	}
129
130	if let Some(child_root) = previous_extracted_child_trie {
131		// A child root was read from proof but is not present
132		// in top trie.
133		return Err(Error::ExtraneousChildProof(child_root))
134	}
135
136	if nodes_iter.next().is_some() {
137		return Err(Error::ExtraneousChildNode)
138	}
139
140	Ok(top_root)
141}
142
143/// Encode a compact proof.
144///
145/// Takes as input all full encoded node from the proof, and
146/// the root.
147/// Then parse all child trie root and compress main trie content first
148/// then all child trie contents.
149/// Child trie are ordered by the order of their roots in the top trie.
150pub fn encode_compact<L, DB>(
151	partial_db: &DB,
152	root: &TrieHash<L>,
153) -> Result<CompactProof, Error<TrieHash<L>, CError<L>>>
154where
155	L: TrieConfiguration,
156	DB: HashDBT<L::Hash, trie_db::DBValue> + hash_db::HashDBRef<L::Hash, trie_db::DBValue>,
157{
158	let mut child_tries = Vec::new();
159	let mut compact_proof = {
160		let trie = crate::TrieDBBuilder::<L>::new(partial_db, root).build();
161
162		let mut iter = trie.iter()?;
163
164		let childtrie_roots = sp_core::storage::well_known_keys::DEFAULT_CHILD_STORAGE_KEY_PREFIX;
165		if iter.seek(childtrie_roots).is_ok() {
166			loop {
167				match iter.next() {
168					Some(Ok((key, value))) if key.starts_with(childtrie_roots) => {
169						let mut root = TrieHash::<L>::default();
170						if root.as_mut().len() != value.as_slice().len() {
171							// some child trie root in top trie are not an encoded hash.
172							return Err(Error::InvalidChildRoot(key.to_vec(), value.to_vec()))
173						}
174						root.as_mut().copy_from_slice(value.as_ref());
175						child_tries.push(root);
176					},
177					// allow incomplete database error: we only
178					// require access to data in the proof.
179					Some(Err(error)) => match *error {
180						trie_db::TrieError::IncompleteDatabase(..) => (),
181						e => return Err(Box::new(e).into()),
182					},
183					_ => break,
184				}
185			}
186		}
187
188		trie_db::encode_compact::<L>(&trie)?
189	};
190
191	for child_root in child_tries {
192		if !HashDBT::<L::Hash, _>::contains(partial_db, &child_root, EMPTY_PREFIX) {
193			// child proof are allowed to be missing (unused root can be included
194			// due to trie structure modification).
195			continue
196		}
197
198		let trie = crate::TrieDBBuilder::<L>::new(partial_db, &child_root).build();
199		let child_proof = trie_db::encode_compact::<L>(&trie)?;
200
201		compact_proof.extend(child_proof);
202	}
203
204	Ok(CompactProof { encoded_nodes: compact_proof })
205}
206
207#[cfg(test)]
208mod tests {
209	use super::*;
210	use crate::{delta_trie_root, recorder::IgnoredNodes, HashDB, StorageProof};
211	use codec::Encode;
212	use hash_db::AsHashDB;
213	use sp_core::{Blake2Hasher, H256};
214	use std::collections::HashSet;
215	use trie_db::{DBValue, Trie, TrieDBBuilder, TrieDBMutBuilder, TrieHash, TrieMut};
216
217	type MemoryDB = crate::MemoryDB<sp_core::Blake2Hasher>;
218	type Layout = crate::LayoutV1<sp_core::Blake2Hasher>;
219	type Recorder = crate::recorder::Recorder<sp_core::Blake2Hasher>;
220
221	fn create_trie(num_keys: u32) -> (MemoryDB, TrieHash<Layout>) {
222		let mut db = MemoryDB::default();
223		let mut root = Default::default();
224
225		{
226			let mut trie = TrieDBMutBuilder::<Layout>::new(&mut db, &mut root).build();
227			for i in 0..num_keys {
228				trie.insert(
229					&i.encode(),
230					&vec![1u8; 64].into_iter().chain(i.encode()).collect::<Vec<_>>(),
231				)
232				.expect("Inserts data");
233			}
234		}
235
236		(db, root)
237	}
238
239	struct Overlay<'a> {
240		db: &'a MemoryDB,
241		write: MemoryDB,
242	}
243
244	impl hash_db::HashDB<sp_core::Blake2Hasher, DBValue> for Overlay<'_> {
245		fn get(
246			&self,
247			key: &<sp_core::Blake2Hasher as hash_db::Hasher>::Out,
248			prefix: hash_db::Prefix,
249		) -> Option<DBValue> {
250			HashDB::get(self.db, key, prefix)
251		}
252
253		fn contains(
254			&self,
255			key: &<sp_core::Blake2Hasher as hash_db::Hasher>::Out,
256			prefix: hash_db::Prefix,
257		) -> bool {
258			HashDB::contains(self.db, key, prefix)
259		}
260
261		fn insert(
262			&mut self,
263			prefix: hash_db::Prefix,
264			value: &[u8],
265		) -> <sp_core::Blake2Hasher as hash_db::Hasher>::Out {
266			self.write.insert(prefix, value)
267		}
268
269		fn emplace(
270			&mut self,
271			key: <sp_core::Blake2Hasher as hash_db::Hasher>::Out,
272			prefix: hash_db::Prefix,
273			value: DBValue,
274		) {
275			self.write.emplace(key, prefix, value);
276		}
277
278		fn remove(
279			&mut self,
280			key: &<sp_core::Blake2Hasher as hash_db::Hasher>::Out,
281			prefix: hash_db::Prefix,
282		) {
283			self.write.remove(key, prefix);
284		}
285	}
286
287	impl AsHashDB<Blake2Hasher, DBValue> for Overlay<'_> {
288		fn as_hash_db(&self) -> &dyn HashDBT<Blake2Hasher, DBValue> {
289			self
290		}
291
292		fn as_hash_db_mut<'a>(&'a mut self) -> &'a mut (dyn HashDBT<Blake2Hasher, DBValue> + 'a) {
293			self
294		}
295	}
296
297	fn emulate_block_building(
298		state: &MemoryDB,
299		root: H256,
300		read_keys: &[u32],
301		write_keys: &[u32],
302		nodes_to_ignore: IgnoredNodes<H256>,
303	) -> (Recorder, MemoryDB, H256) {
304		let recorder = Recorder::with_ignored_nodes(nodes_to_ignore);
305
306		{
307			let mut trie_recorder = recorder.as_trie_recorder(root);
308			let trie = TrieDBBuilder::<Layout>::new(state, &root)
309				.with_recorder(&mut trie_recorder)
310				.build();
311
312			for key in read_keys {
313				trie.get(&key.encode()).unwrap().unwrap();
314			}
315		}
316
317		let mut overlay = Overlay { db: state, write: Default::default() };
318
319		let new_root = {
320			let mut trie_recorder = recorder.as_trie_recorder(root);
321			delta_trie_root::<Layout, _, _, _, _, _>(
322				&mut overlay,
323				root,
324				write_keys.iter().map(|k| {
325					(
326						k.encode(),
327						Some(vec![2u8; 64].into_iter().chain(k.encode()).collect::<Vec<_>>()),
328					)
329				}),
330				Some(&mut trie_recorder),
331				None,
332			)
333			.unwrap()
334		};
335
336		(recorder, overlay.write, new_root)
337	}
338
339	fn build_known_nodes_list(recorder: &Recorder, transaction: &MemoryDB) -> IgnoredNodes<H256> {
340		let mut ignored_nodes =
341			IgnoredNodes::from_storage_proof::<Blake2Hasher>(&recorder.to_storage_proof());
342
343		ignored_nodes.extend(IgnoredNodes::from_memory_db::<Blake2Hasher, _>(transaction.clone()));
344
345		ignored_nodes
346	}
347
348	#[test]
349	fn ensure_multiple_tries_encode_compact_works() {
350		let (mut db, root) = create_trie(100);
351
352		let mut nodes_to_ignore = IgnoredNodes::default();
353		let (recorder, transaction, root1) = emulate_block_building(
354			&db,
355			root,
356			&[2, 4, 5, 6, 7, 8],
357			&[9, 10, 11, 12, 13, 14],
358			nodes_to_ignore.clone(),
359		);
360
361		db.consolidate(transaction.clone());
362		nodes_to_ignore.extend(build_known_nodes_list(&recorder, &transaction));
363
364		let (recorder2, transaction, root2) = emulate_block_building(
365			&db,
366			root1,
367			&[9, 10, 11, 12, 13, 14],
368			&[15, 16, 17, 18, 19, 20],
369			nodes_to_ignore.clone(),
370		);
371
372		db.consolidate(transaction.clone());
373		nodes_to_ignore.extend(build_known_nodes_list(&recorder2, &transaction));
374
375		let (recorder3, _, root3) = emulate_block_building(
376			&db,
377			root2,
378			&[20, 30, 40, 41, 42],
379			&[80, 90, 91, 92, 93],
380			nodes_to_ignore,
381		);
382
383		let proof = recorder.to_storage_proof();
384		let proof2 = recorder2.to_storage_proof();
385		let proof3 = recorder3.to_storage_proof();
386
387		let mut combined = HashSet::<Vec<u8>>::from_iter(proof.into_iter_nodes());
388		proof2.iter_nodes().for_each(|n| assert!(combined.insert(n.clone())));
389		proof3.iter_nodes().for_each(|n| assert!(combined.insert(n.clone())));
390
391		let proof = StorageProof::new(combined.into_iter());
392
393		let compact_proof = encode_compact::<Layout, _>(&proof.to_memory_db(), &root).unwrap();
394
395		assert!(proof.encoded_size() > compact_proof.encoded_size());
396
397		let mut res_db = crate::MemoryDB::<Blake2Hasher>::new(&[]);
398		decode_compact::<Layout, _, _>(
399			&mut res_db,
400			compact_proof.iter_compact_encoded_nodes(),
401			Some(&root),
402		)
403		.unwrap();
404
405		let (_, transaction, root1_proof) = emulate_block_building(
406			&res_db,
407			root,
408			&[2, 4, 5, 6, 7, 8],
409			&[9, 10, 11, 12, 13, 14],
410			Default::default(),
411		);
412
413		assert_eq!(root1, root1_proof);
414
415		res_db.consolidate(transaction);
416
417		let (_, transaction2, root2_proof) = emulate_block_building(
418			&res_db,
419			root1,
420			&[9, 10, 11, 12, 13, 14],
421			&[15, 16, 17, 18, 19, 20],
422			Default::default(),
423		);
424
425		assert_eq!(root2, root2_proof);
426
427		res_db.consolidate(transaction2);
428
429		let (_, _, root3_proof) = emulate_block_building(
430			&res_db,
431			root2,
432			&[20, 30, 40, 41, 42],
433			&[80, 90, 91, 92, 93],
434			Default::default(),
435		);
436
437		assert_eq!(root3, root3_proof);
438	}
439}