trie_db/proof/
generate.rs

1// Copyright 2019, 2021 Parity Technologies
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Generation of compact proofs for Merkle-Patricia tries.
16
17use crate::rstd::{boxed::Box, convert::TryInto, marker::PhantomData, vec, vec::Vec};
18
19use hash_db::{HashDBRef, Hasher};
20
21use crate::{
22	nibble::LeftNibbleSlice,
23	nibble_ops::NIBBLE_LENGTH,
24	node::{NodeHandle, NodeHandlePlan, NodePlan, OwnedNode, Value, ValuePlan},
25	recorder::Record,
26	CError, ChildReference, DBValue, NibbleSlice, NodeCodec, Recorder, Result as TrieResult, Trie,
27	TrieDBBuilder, TrieError, TrieHash, TrieLayout,
28};
29
30struct StackEntry<'a, C: NodeCodec> {
31	/// The prefix is the nibble path to the node in the trie.
32	prefix: LeftNibbleSlice<'a>,
33	/// Stacked node.
34	node: OwnedNode<Vec<u8>>,
35	/// The hash of the node or None if it is referenced inline.
36	node_hash: Option<C::HashOut>,
37	/// Whether the value should be omitted in the generated proof.
38	omit_value: bool,
39	/// The next entry in the stack is a child of the preceding entry at this index. For branch
40	/// nodes, the index is in [0, NIBBLE_LENGTH] and for extension nodes, the index is in [0, 1].
41	child_index: usize,
42	/// The child references to use in constructing the proof nodes.
43	children: Vec<Option<ChildReference<C::HashOut>>>,
44	/// The index into the proof vector that the encoding of this entry should be placed at.
45	output_index: Option<usize>,
46	_marker: PhantomData<C>,
47}
48
49impl<'a, C: NodeCodec> StackEntry<'a, C> {
50	fn new(
51		prefix: LeftNibbleSlice<'a>,
52		node_data: Vec<u8>,
53		node_hash: Option<C::HashOut>,
54		output_index: Option<usize>,
55	) -> TrieResult<Self, C::HashOut, C::Error> {
56		let node = OwnedNode::new::<C>(node_data)
57			.map_err(|err| Box::new(TrieError::DecoderError(node_hash.unwrap_or_default(), err)))?;
58		let children_len = match node.node_plan() {
59			NodePlan::Empty | NodePlan::Leaf { .. } => 0,
60			NodePlan::Extension { .. } => 1,
61			NodePlan::Branch { .. } | NodePlan::NibbledBranch { .. } => NIBBLE_LENGTH,
62		};
63		Ok(StackEntry {
64			prefix,
65			node,
66			node_hash,
67			omit_value: false,
68			child_index: 0,
69			children: vec![None; children_len],
70			output_index,
71			_marker: PhantomData::default(),
72		})
73	}
74
75	/// Encode this entry to an encoded trie node with data properly omitted.
76	fn encode_node(mut self) -> TrieResult<Vec<u8>, C::HashOut, C::Error> {
77		let omit_value = self.omit_value;
78		let node_data = self.node.data();
79		let value_with_omission = |value_range: ValuePlan| -> Option<Value> {
80			if !omit_value {
81				Some(value_range.build(&node_data))
82			} else {
83				None
84			}
85		};
86		let encoded = match self.node.node_plan() {
87			NodePlan::Empty => node_data.to_vec(),
88			NodePlan::Leaf { .. } if !omit_value => node_data.to_vec(),
89			NodePlan::Leaf { partial, value: _ } => {
90				let partial = partial.build(node_data);
91				C::leaf_node(partial.right_iter(), partial.len(), Value::Inline(&[]))
92			},
93			NodePlan::Extension { .. } if self.child_index == 0 => node_data.to_vec(),
94			NodePlan::Extension { partial: partial_plan, child: _ } => {
95				let partial = partial_plan.build(node_data);
96				let child = self.children[0].expect(
97					"for extension nodes, children[0] is guaranteed to be Some when \
98						child_index > 0; \
99						the branch guard guarantees that child_index > 0",
100				);
101				C::extension_node(partial.right_iter(), partial.len(), child)
102			},
103			NodePlan::Branch { value, children } => {
104				Self::complete_branch_children(
105					node_data,
106					children,
107					self.child_index,
108					&mut self.children,
109				)?;
110				C::branch_node(
111					self.children.into_iter(),
112					value.clone().map(value_with_omission).flatten(),
113				)
114			},
115			NodePlan::NibbledBranch { partial: partial_plan, value, children } => {
116				let partial = partial_plan.build(node_data);
117				Self::complete_branch_children(
118					node_data,
119					children,
120					self.child_index,
121					&mut self.children,
122				)?;
123				C::branch_node_nibbled(
124					partial.right_iter(),
125					partial.len(),
126					self.children.into_iter(),
127					value.clone().map(value_with_omission).flatten(),
128				)
129			},
130		};
131		Ok(encoded)
132	}
133
134	/// Populate the remaining references in `children` with references copied from
135	/// `child_handles`.
136	///
137	/// Preconditions:
138	/// - children has size NIBBLE_LENGTH.
139	fn complete_branch_children(
140		node_data: &[u8],
141		child_handles: &[Option<NodeHandlePlan>; NIBBLE_LENGTH],
142		child_index: usize,
143		children: &mut [Option<ChildReference<C::HashOut>>],
144	) -> TrieResult<(), C::HashOut, C::Error> {
145		for i in child_index..NIBBLE_LENGTH {
146			children[i] = child_handles[i]
147				.as_ref()
148				.map(|child_plan| {
149					child_plan.build(node_data).try_into().map_err(|hash| {
150						Box::new(TrieError::InvalidHash(C::HashOut::default(), hash))
151					})
152				})
153				.transpose()?;
154		}
155		Ok(())
156	}
157
158	/// Sets the reference for the child at index `child_index`. If the child is hash-referenced in
159	/// the trie, the proof node reference will be an omitted child. If the child is
160	/// inline-referenced in the trie, the proof node reference will also be inline.
161	fn set_child(&mut self, encoded_child: &[u8]) {
162		let child_ref = match self.node.node_plan() {
163			NodePlan::Empty | NodePlan::Leaf { .. } => panic!(
164				"empty and leaf nodes have no children; \
165				thus they are never descended into; \
166				thus set_child will not be called on an entry with one of these types"
167			),
168			NodePlan::Extension { child, .. } => {
169				assert_eq!(
170					self.child_index, 0,
171					"extension nodes only have one child; \
172					set_child is called when the only child is popped from the stack; \
173					child_index is 0 before child is pushed to the stack; qed"
174				);
175				Some(Self::replacement_child_ref(encoded_child, child))
176			},
177			NodePlan::Branch { children, .. } | NodePlan::NibbledBranch { children, .. } => {
178				assert!(
179					self.child_index < NIBBLE_LENGTH,
180					"extension nodes have at most NIBBLE_LENGTH children; \
181					set_child is called when the only child is popped from the stack; \
182					child_index is <NIBBLE_LENGTH before child is pushed to the stack; qed"
183				);
184				children[self.child_index]
185					.as_ref()
186					.map(|child| Self::replacement_child_ref(encoded_child, child))
187			},
188		};
189		self.children[self.child_index] = child_ref;
190		self.child_index += 1;
191	}
192
193	/// Build a proof node child reference. If the child is hash-referenced in the trie, the proof
194	/// node reference will be an omitted child. If the child is inline-referenced in the trie, the
195	/// proof node reference will also be inline.
196	fn replacement_child_ref(
197		encoded_child: &[u8],
198		child: &NodeHandlePlan,
199	) -> ChildReference<C::HashOut> {
200		match child {
201			NodeHandlePlan::Hash(_) => ChildReference::Inline(C::HashOut::default(), 0),
202			NodeHandlePlan::Inline(_) => {
203				let mut hash = C::HashOut::default();
204				assert!(
205					encoded_child.len() <= hash.as_ref().len(),
206					"the encoding of the raw inline node is checked to be at most the hash length
207					before descending; \
208					the encoding of the proof node is always smaller than the raw node as data is \
209					only stripped"
210				);
211				hash.as_mut()[..encoded_child.len()].copy_from_slice(encoded_child);
212				ChildReference::Inline(hash, encoded_child.len())
213			},
214		}
215	}
216}
217
218/// Generate a compact proof for key-value pairs in a trie given a set of keys.
219///
220/// Assumes inline nodes have only inline children.
221pub fn generate_proof<'a, D, L, I, K>(
222	db: &D,
223	root: &TrieHash<L>,
224	keys: I,
225) -> TrieResult<Vec<Vec<u8>>, TrieHash<L>, CError<L>>
226where
227	D: HashDBRef<L::Hash, DBValue>,
228	L: TrieLayout,
229	I: IntoIterator<Item = &'a K>,
230	K: 'a + AsRef<[u8]>,
231{
232	// Sort and de-duplicate keys.
233	let mut keys = keys.into_iter().map(|key| key.as_ref()).collect::<Vec<_>>();
234	keys.sort();
235	keys.dedup();
236
237	// The stack of nodes through a path in the trie. Each entry is a child node of the preceding
238	// entry.
239	let mut stack = <Vec<StackEntry<L::Codec>>>::new();
240
241	// The mutated trie nodes comprising the final proof.
242	let mut proof_nodes = Vec::new();
243
244	for key_bytes in keys {
245		let key = LeftNibbleSlice::new(key_bytes);
246
247		// Unwind the stack until the new entry is a child of the last entry on the stack.
248		unwind_stack(&mut stack, &mut proof_nodes, Some(&key))?;
249
250		// Perform the trie lookup for the next key, recording the sequence of nodes traversed.
251		let mut recorder = Recorder::<L>::new();
252		let expected_value = {
253			let trie = TrieDBBuilder::<L>::new(db, root).with_recorder(&mut recorder).build();
254			trie.get(key_bytes)?
255		};
256
257		let mut recorded_nodes = recorder.drain().into_iter().peekable();
258
259		// Skip over recorded nodes already on the stack. Their indexes into the respective vector
260		// (either `stack` or `recorded_nodes`) match under the assumption that inline nodes have
261		// only inline children.
262		{
263			let mut stack_iter = stack.iter().peekable();
264			while let (Some(next_record), Some(next_entry)) =
265				(recorded_nodes.peek(), stack_iter.peek())
266			{
267				if next_entry.node_hash != Some(next_record.hash) {
268					break
269				}
270				recorded_nodes.next();
271				stack_iter.next();
272			}
273		}
274
275		loop {
276			let step = match stack.last_mut() {
277				Some(entry) => match_key_to_node::<L::Codec>(
278					entry.node.data(),
279					entry.node.node_plan(),
280					&mut entry.omit_value,
281					&mut entry.child_index,
282					&mut entry.children,
283					&key,
284					entry.prefix.len(),
285					&mut recorded_nodes,
286				)?,
287				// If stack is empty, descend into the root node.
288				None =>
289					Step::Descend { child_prefix_len: 0, child: NodeHandle::Hash(root.as_ref()) },
290			};
291
292			match step {
293				Step::Descend { child_prefix_len, child } => {
294					let child_prefix = key.truncate(child_prefix_len);
295					let child_entry = match child {
296						NodeHandle::Hash(hash) => {
297							let child_record = recorded_nodes.next().expect(
298								"this function's trie traversal logic mirrors that of Lookup; \
299									thus the sequence of traversed nodes must be the same; \
300									so the next child node must have been recorded and must have \
301									the expected hash",
302							);
303							// Proof for `assert_eq` is in the `expect` proof above.
304							assert_eq!(child_record.hash.as_ref(), hash);
305
306							let output_index = proof_nodes.len();
307							// Insert a placeholder into output which will be replaced when this
308							// new entry is popped from the stack.
309							proof_nodes.push(Vec::new());
310							StackEntry::new(
311								child_prefix,
312								child_record.data,
313								Some(child_record.hash),
314								Some(output_index),
315							)?
316						},
317						NodeHandle::Inline(data) => {
318							if data.len() > L::Hash::LENGTH {
319								return Err(Box::new(TrieError::InvalidHash(
320									<TrieHash<L>>::default(),
321									data.to_vec(),
322								)))
323							}
324							StackEntry::new(child_prefix, data.to_vec(), None, None)?
325						},
326					};
327					stack.push(child_entry);
328				},
329				Step::FoundHashedValue(value) => {
330					assert_eq!(
331						Some(&value),
332						expected_value.as_ref(),
333						"expected_value is found using `trie_db::Lookup`; \
334						value is found by traversing the same nodes recorded during the lookup \
335						using the same logic; \
336						thus the values found must be equal"
337					);
338					assert!(
339						recorded_nodes.next().is_none(),
340						"the recorded nodes are only recorded on the lookup path to the current \
341						key; \
342						recorded nodes is the minimal sequence of trie nodes on the lookup path; \
343						the value was found by traversing recorded nodes, so there must be none \
344						remaining"
345					);
346					break
347				},
348				Step::FoundValue(value) => {
349					assert_eq!(
350						value,
351						expected_value.as_ref().map(|v| v.as_ref()),
352						"expected_value is found using `trie_db::Lookup`; \
353						value is found by traversing the same nodes recorded during the lookup \
354						using the same logic; \
355						thus the values found must be equal"
356					);
357					assert!(
358						recorded_nodes.next().is_none(),
359						"the recorded nodes are only recorded on the lookup path to the current \
360						key; \
361						recorded nodes is the minimal sequence of trie nodes on the lookup path; \
362						the value was found by traversing recorded nodes, so there must be none \
363						remaining"
364					);
365					break
366				},
367			}
368		}
369	}
370
371	unwind_stack::<L::Codec>(&mut stack, &mut proof_nodes, None)?;
372	Ok(proof_nodes)
373}
374
375enum Step<'a> {
376	Descend { child_prefix_len: usize, child: NodeHandle<'a> },
377	FoundValue(Option<&'a [u8]>),
378	FoundHashedValue(Vec<u8>),
379}
380
381fn resolve_value<C: NodeCodec>(
382	recorded_nodes: &mut dyn Iterator<Item = Record<C::HashOut>>,
383) -> TrieResult<Step<'static>, C::HashOut, C::Error> {
384	if let Some(resolve_value) = recorded_nodes.next() {
385		Ok(Step::FoundHashedValue(resolve_value.data))
386	} else {
387		Err(Box::new(TrieError::IncompleteDatabase(C::HashOut::default())))
388	}
389}
390
391/// Determine the next algorithmic step to take by matching the current key against the current top
392/// entry on the stack.
393fn match_key_to_node<'a, C: NodeCodec>(
394	node_data: &'a [u8],
395	node_plan: &NodePlan,
396	omit_value: &mut bool,
397	child_index: &mut usize,
398	children: &mut [Option<ChildReference<C::HashOut>>],
399	key: &LeftNibbleSlice,
400	prefix_len: usize,
401	recorded_nodes: &mut dyn Iterator<Item = Record<C::HashOut>>,
402) -> TrieResult<Step<'a>, C::HashOut, C::Error> {
403	Ok(match node_plan {
404		NodePlan::Empty => Step::FoundValue(None),
405		NodePlan::Leaf { partial: partial_plan, value: value_range } => {
406			let partial = partial_plan.build(node_data);
407			if key.contains(&partial, prefix_len) && key.len() == prefix_len + partial.len() {
408				match value_range {
409					ValuePlan::Inline(value_range) => {
410						*omit_value = true;
411						Step::FoundValue(Some(&node_data[value_range.clone()]))
412					},
413					ValuePlan::Node(..) => {
414						*omit_value = true;
415						resolve_value::<C>(recorded_nodes)?
416					},
417				}
418			} else {
419				Step::FoundValue(None)
420			}
421		},
422		NodePlan::Extension { partial: partial_plan, child: child_plan } => {
423			let partial = partial_plan.build(node_data);
424			if key.contains(&partial, prefix_len) {
425				assert_eq!(*child_index, 0);
426				let child_prefix_len = prefix_len + partial.len();
427				let child = child_plan.build(&node_data);
428				Step::Descend { child_prefix_len, child }
429			} else {
430				Step::FoundValue(None)
431			}
432		},
433		NodePlan::Branch { value, children: child_handles } => match_key_to_branch_node::<C>(
434			node_data,
435			value.as_ref(),
436			&child_handles,
437			omit_value,
438			child_index,
439			children,
440			key,
441			prefix_len,
442			NibbleSlice::new(&[]),
443			recorded_nodes,
444		)?,
445		NodePlan::NibbledBranch { partial: partial_plan, value, children: child_handles } =>
446			match_key_to_branch_node::<C>(
447				node_data,
448				value.as_ref(),
449				&child_handles,
450				omit_value,
451				child_index,
452				children,
453				key,
454				prefix_len,
455				partial_plan.build(node_data),
456				recorded_nodes,
457			)?,
458	})
459}
460
461fn match_key_to_branch_node<'a, 'b, C: NodeCodec>(
462	node_data: &'a [u8],
463	value_range: Option<&'b ValuePlan>,
464	child_handles: &'b [Option<NodeHandlePlan>; NIBBLE_LENGTH],
465	omit_value: &mut bool,
466	child_index: &mut usize,
467	children: &mut [Option<ChildReference<C::HashOut>>],
468	key: &'b LeftNibbleSlice<'b>,
469	prefix_len: usize,
470	partial: NibbleSlice<'b>,
471	recorded_nodes: &mut dyn Iterator<Item = Record<C::HashOut>>,
472) -> TrieResult<Step<'a>, C::HashOut, C::Error> {
473	if !key.contains(&partial, prefix_len) {
474		return Ok(Step::FoundValue(None))
475	}
476
477	if key.len() == prefix_len + partial.len() {
478		let value = match value_range {
479			Some(ValuePlan::Inline(range)) => {
480				*omit_value = true;
481				Some(&node_data[range.clone()])
482			},
483			Some(ValuePlan::Node(..)) => {
484				*omit_value = true;
485				return resolve_value::<C>(recorded_nodes)
486			},
487			None => None,
488		};
489		return Ok(Step::FoundValue(value))
490	}
491
492	let new_index = key.at(prefix_len + partial.len()).expect(
493		"key contains partial key after entry key offset; \
494			thus key len is greater than equal to entry key len plus partial key len; \
495			also they are unequal due to else condition;
496			qed",
497	) as usize;
498	assert!(*child_index <= new_index);
499	while *child_index < new_index {
500		children[*child_index] = child_handles[*child_index]
501			.as_ref()
502			.map(|child_plan| {
503				child_plan
504					.build(node_data)
505					.try_into()
506					.map_err(|hash| Box::new(TrieError::InvalidHash(C::HashOut::default(), hash)))
507			})
508			.transpose()?;
509		*child_index += 1;
510	}
511	if let Some(child_plan) = &child_handles[*child_index] {
512		Ok(Step::Descend {
513			child_prefix_len: prefix_len + partial.len() + 1,
514			child: child_plan.build(node_data),
515		})
516	} else {
517		Ok(Step::FoundValue(None))
518	}
519}
520
521/// Unwind the stack until the given key is prefixed by the entry at the top of the stack. If the
522/// key is None, unwind the stack completely. As entries are popped from the stack, they are
523/// encoded into proof nodes and added to the finalized proof.
524fn unwind_stack<C: NodeCodec>(
525	stack: &mut Vec<StackEntry<C>>,
526	proof_nodes: &mut Vec<Vec<u8>>,
527	maybe_key: Option<&LeftNibbleSlice>,
528) -> TrieResult<(), C::HashOut, C::Error> {
529	while let Some(entry) = stack.pop() {
530		match maybe_key {
531			Some(key) if key.starts_with(&entry.prefix) => {
532				// Stop if the key lies below this entry in the trie.
533				stack.push(entry);
534				break
535			},
536			_ => {
537				// Pop and finalize node from the stack.
538				let index = entry.output_index;
539				let encoded = entry.encode_node()?;
540				if let Some(parent_entry) = stack.last_mut() {
541					parent_entry.set_child(&encoded);
542				}
543				if let Some(index) = index {
544					proof_nodes[index] = encoded;
545				}
546			},
547		}
548	}
549	Ok(())
550}