Skip to main content

sp_trie/
recorder.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//! Trie recorder
19//!
20//! Provides an implementation of the [`TrieRecorder`](trie_db::TrieRecorder) trait. It can be used
21//! to record storage accesses to the state to generate a [`StorageProof`].
22
23use crate::{GenericMemoryDB, NodeCodec, StorageProof};
24use codec::{Compact, Decode, Encode};
25use hash_db::Hasher;
26use memory_db::KeyFunction;
27use parking_lot::{Mutex, MutexGuard};
28use std::{
29	collections::{HashMap, HashSet},
30	marker::PhantomData,
31	mem,
32	ops::DerefMut,
33	sync::{
34		atomic::{AtomicUsize, Ordering},
35		Arc,
36	},
37};
38use trie_db::{RecordedForKey, TrieAccess};
39
40const LOG_TARGET: &str = "trie-recorder";
41
42/// A list of ignored nodes for [`Recorder`].
43///
44/// These nodes when passed to a recorder will be ignored and not recorded by the recorder.
45#[derive(Clone, Debug)]
46pub struct IgnoredNodes<H> {
47	nodes: HashSet<H>,
48}
49
50impl<H: Encode> Encode for IgnoredNodes<H> {
51	fn encode(&self) -> Vec<u8> {
52		let mut encoded = Compact::<u32>(self.nodes.len() as _).encode();
53		self.nodes.iter().for_each(|n| n.encode_to(&mut encoded));
54		encoded
55	}
56}
57
58impl<H: Decode + std::hash::Hash + Eq> Decode for IgnoredNodes<H> {
59	fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
60		let len = Compact::<u32>::decode(input)?;
61		let data = codec::decode_vec_with_len(input, len.0 as _)?;
62		Ok(Self { nodes: HashSet::from_iter(data.into_iter()) })
63	}
64}
65
66impl<H> Default for IgnoredNodes<H> {
67	fn default() -> Self {
68		Self { nodes: HashSet::default() }
69	}
70}
71
72impl<H: Eq + std::hash::Hash + Clone> IgnoredNodes<H> {
73	/// Initialize from the given storage proof.
74	///
75	/// So, all recorded nodes of the proof will be the ignored nodes.
76	pub fn from_storage_proof<Hasher: trie_db::Hasher<Out = H>>(proof: &StorageProof) -> Self {
77		Self { nodes: proof.iter_nodes().map(|n| Hasher::hash(&n)).collect() }
78	}
79
80	/// Initialize from the given memory db.
81	///
82	/// All nodes that have a reference count > 0 will be used as ignored nodes.
83	pub fn from_memory_db<Hasher: trie_db::Hasher<Out = H>, KF: KeyFunction<Hasher>>(
84		mut memory_db: GenericMemoryDB<Hasher, KF>,
85	) -> Self {
86		Self {
87			nodes: memory_db
88				.drain()
89				.into_iter()
90				// We do not want to add removed nodes.
91				.filter(|(_, (_, counter))| *counter > 0)
92				.map(|(_, (data, _))| Hasher::hash(&data))
93				.collect(),
94		}
95	}
96
97	/// Extend `self` with the other instance of ignored nodes.
98	pub fn extend(&mut self, other: Self) {
99		self.nodes.extend(other.nodes.into_iter());
100	}
101
102	/// Returns `true` if the node is ignored.
103	pub fn is_ignored(&self, node: &H) -> bool {
104		self.nodes.contains(node)
105	}
106}
107
108/// Stores all the information per transaction.
109#[derive(Default)]
110struct Transaction<H> {
111	/// Stores transaction information about [`RecorderInner::recorded_keys`].
112	///
113	/// For each transaction we only store the `storage_root` and the old states per key. `None`
114	/// state means that the key wasn't recorded before.
115	recorded_keys: HashMap<H, HashMap<Arc<[u8]>, Option<RecordedForKey>>>,
116	/// Stores transaction information about [`RecorderInner::accessed_nodes`].
117	///
118	/// For each transaction we only store the hashes of added nodes.
119	accessed_nodes: HashSet<H>,
120}
121
122/// The internals of [`Recorder`].
123struct RecorderInner<H> {
124	/// The keys for that we have recorded the trie nodes and if we have recorded up to the value.
125	///
126	/// Mapping: `StorageRoot -> (Key -> RecordedForKey)`.
127	recorded_keys: HashMap<H, HashMap<Arc<[u8]>, RecordedForKey>>,
128
129	/// Currently active transactions.
130	transactions: Vec<Transaction<H>>,
131
132	/// The encoded nodes we accessed while recording.
133	///
134	/// Mapping: `Hash(Node) -> Node`.
135	accessed_nodes: HashMap<H, Vec<u8>>,
136
137	/// Nodes that should be ignored and not recorded.
138	ignored_nodes: IgnoredNodes<H>,
139}
140
141impl<H> Default for RecorderInner<H> {
142	fn default() -> Self {
143		Self {
144			recorded_keys: Default::default(),
145			accessed_nodes: Default::default(),
146			transactions: Vec::new(),
147			ignored_nodes: Default::default(),
148		}
149	}
150}
151
152/// The trie recorder.
153///
154/// Owns the recorded data. Is used to transform data into a storage
155/// proof and to provide transaction support. The `as_trie_recorder` method provides a
156/// [`trie_db::TrieDB`] compatible recorder that implements the actual recording logic.
157pub struct Recorder<H: Hasher> {
158	inner: Arc<Mutex<RecorderInner<H::Out>>>,
159	/// The estimated encoded size of the storage proof this recorder will produce.
160	///
161	/// We store this in an atomic to be able to fetch the value while the `inner` is may locked.
162	encoded_size_estimation: Arc<AtomicUsize>,
163}
164
165impl<H: Hasher> Default for Recorder<H> {
166	fn default() -> Self {
167		Self { inner: Default::default(), encoded_size_estimation: Arc::new(0.into()) }
168	}
169}
170
171impl<H: Hasher> Clone for Recorder<H> {
172	fn clone(&self) -> Self {
173		Self {
174			inner: self.inner.clone(),
175			encoded_size_estimation: self.encoded_size_estimation.clone(),
176		}
177	}
178}
179
180impl<H: Hasher> Recorder<H> {
181	/// Create a new instance with the given `ingored_nodes`.
182	///
183	/// These ignored nodes are not recorded when accessed.
184	pub fn with_ignored_nodes(ignored_nodes: IgnoredNodes<H::Out>) -> Self {
185		Self {
186			inner: Arc::new(Mutex::new(RecorderInner { ignored_nodes, ..Default::default() })),
187			..Default::default()
188		}
189	}
190
191	/// Returns [`RecordedForKey`] per recorded key per trie.
192	///
193	/// There are multiple tries when working with e.g. child tries.
194	pub fn recorded_keys(&self) -> HashMap<H::Out, HashMap<Arc<[u8]>, RecordedForKey>> {
195		self.inner.lock().recorded_keys.clone()
196	}
197
198	/// Returns the recorder as [`TrieRecorder`](trie_db::TrieRecorder) compatible type.
199	///
200	/// - `storage_root`: The storage root of the trie for which accesses are recorded. This is
201	///   important when recording access to different tries at once (like top and child tries).
202	///
203	/// NOTE: This locks a mutex that stays locked until the return value is dropped.
204	#[inline]
205	pub fn as_trie_recorder(&self, storage_root: H::Out) -> TrieRecorder<'_, H> {
206		TrieRecorder::<H> {
207			inner: self.inner.lock(),
208			storage_root,
209			encoded_size_estimation: self.encoded_size_estimation.clone(),
210			_phantom: PhantomData,
211		}
212	}
213
214	/// Drain the recording into a [`StorageProof`].
215	///
216	/// While a recorder can be cloned, all share the same internal state. After calling this
217	/// function, all other instances will have their internal state reset as well.
218	///
219	/// If you don't want to drain the recorded state, use [`Self::to_storage_proof`].
220	///
221	/// Returns the [`StorageProof`].
222	pub fn drain_storage_proof(self) -> StorageProof {
223		let mut recorder = mem::take(&mut *self.inner.lock());
224		StorageProof::new(recorder.accessed_nodes.drain().map(|(_, v)| v))
225	}
226
227	/// Convert the recording to a [`StorageProof`].
228	///
229	/// In contrast to [`Self::drain_storage_proof`] this doesn't consume and doesn't clear the
230	/// recordings.
231	///
232	/// Returns the [`StorageProof`].
233	pub fn to_storage_proof(&self) -> StorageProof {
234		let recorder = self.inner.lock();
235		StorageProof::new(recorder.accessed_nodes.values().cloned())
236	}
237
238	/// Returns the estimated encoded size of the proof.
239	///
240	/// The estimation is based on all the nodes that were accessed until now while
241	/// accessing the trie.
242	pub fn estimate_encoded_size(&self) -> usize {
243		self.encoded_size_estimation.load(Ordering::Relaxed)
244	}
245
246	/// Reset the state.
247	///
248	/// This discards all recorded data.
249	pub fn reset(&self) {
250		mem::take(&mut *self.inner.lock());
251		self.encoded_size_estimation.store(0, Ordering::Relaxed);
252	}
253
254	/// Start a new transaction.
255	pub fn start_transaction(&self) {
256		let mut inner = self.inner.lock();
257		inner.transactions.push(Default::default());
258	}
259
260	/// Rollback the latest transaction.
261	///
262	/// Returns an error if there wasn't any active transaction.
263	pub fn rollback_transaction(&self) -> Result<(), ()> {
264		let mut inner = self.inner.lock();
265
266		// We locked `inner` and can just update the encoded size locally and then store it back to
267		// the atomic.
268		let mut new_encoded_size_estimation = self.encoded_size_estimation.load(Ordering::Relaxed);
269		let transaction = inner.transactions.pop().ok_or(())?;
270
271		transaction.accessed_nodes.into_iter().for_each(|n| {
272			if let Some(old) = inner.accessed_nodes.remove(&n) {
273				new_encoded_size_estimation =
274					new_encoded_size_estimation.saturating_sub(old.encoded_size());
275			}
276		});
277
278		transaction.recorded_keys.into_iter().for_each(|(storage_root, keys)| {
279			keys.into_iter().for_each(|(k, old_state)| {
280				if let Some(state) = old_state {
281					inner.recorded_keys.entry(storage_root).or_default().insert(k, state);
282				} else {
283					inner.recorded_keys.entry(storage_root).or_default().remove(&k);
284				}
285			});
286		});
287
288		self.encoded_size_estimation
289			.store(new_encoded_size_estimation, Ordering::Relaxed);
290
291		Ok(())
292	}
293
294	/// Commit the latest transaction.
295	///
296	/// Returns an error if there wasn't any active transaction.
297	pub fn commit_transaction(&self) -> Result<(), ()> {
298		let mut inner = self.inner.lock();
299
300		let transaction = inner.transactions.pop().ok_or(())?;
301
302		if let Some(parent_transaction) = inner.transactions.last_mut() {
303			parent_transaction.accessed_nodes.extend(transaction.accessed_nodes);
304
305			transaction.recorded_keys.into_iter().for_each(|(storage_root, keys)| {
306				keys.into_iter().for_each(|(k, old_state)| {
307					parent_transaction
308						.recorded_keys
309						.entry(storage_root)
310						.or_default()
311						.entry(k)
312						.or_insert(old_state);
313				})
314			});
315		}
316
317		Ok(())
318	}
319}
320
321impl<H: Hasher> crate::ProofSizeProvider for Recorder<H> {
322	fn estimate_encoded_size(&self) -> usize {
323		Recorder::estimate_encoded_size(self)
324	}
325}
326
327/// The [`TrieRecorder`](trie_db::TrieRecorder) implementation.
328pub struct TrieRecorder<'a, H: Hasher> {
329	inner: MutexGuard<'a, RecorderInner<H::Out>>,
330	storage_root: H::Out,
331	encoded_size_estimation: Arc<AtomicUsize>,
332	_phantom: PhantomData<H>,
333}
334
335impl<H: Hasher> crate::TrieRecorderProvider<H> for Recorder<H> {
336	type Recorder<'a>
337		= TrieRecorder<'a, H>
338	where
339		H: 'a;
340
341	fn drain_storage_proof(self) -> Option<StorageProof> {
342		Some(Recorder::drain_storage_proof(self))
343	}
344
345	fn as_trie_recorder(&self, storage_root: H::Out) -> Self::Recorder<'_> {
346		Recorder::as_trie_recorder(&self, storage_root)
347	}
348}
349
350impl<'a, H: Hasher> TrieRecorder<'a, H> {
351	/// Update the recorded keys entry for the given `full_key`.
352	fn update_recorded_keys(&mut self, full_key: &[u8], access: RecordedForKey) {
353		let inner = self.inner.deref_mut();
354
355		let entry =
356			inner.recorded_keys.entry(self.storage_root).or_default().entry(full_key.into());
357
358		let key = entry.key().clone();
359
360		// We don't need to update the record if we only accessed the `Hash` for the given
361		// `full_key`. Only `Value` access can be an upgrade from `Hash`.
362		let entry = if matches!(access, RecordedForKey::Value) {
363			entry.and_modify(|e| {
364				if let Some(tx) = inner.transactions.last_mut() {
365					// Store the previous state only once per transaction.
366					tx.recorded_keys
367						.entry(self.storage_root)
368						.or_default()
369						.entry(key.clone())
370						.or_insert(Some(*e));
371				}
372
373				*e = access;
374			})
375		} else {
376			entry
377		};
378
379		entry.or_insert_with(|| {
380			if let Some(tx) = inner.transactions.last_mut() {
381				// The key wasn't yet recorded, so there isn't any old state.
382				tx.recorded_keys
383					.entry(self.storage_root)
384					.or_default()
385					.entry(key)
386					.or_insert(None);
387			}
388
389			access
390		});
391	}
392}
393
394impl<'a, H: Hasher> trie_db::TrieRecorder<H::Out> for TrieRecorder<'a, H> {
395	fn record(&mut self, access: TrieAccess<H::Out>) {
396		let mut encoded_size_update = 0;
397
398		match access {
399			TrieAccess::NodeOwned { hash, node_owned } => {
400				let inner = self.inner.deref_mut();
401
402				if inner.ignored_nodes.is_ignored(&hash) {
403					tracing::trace!(
404						target: LOG_TARGET,
405						?hash,
406						"Ignoring node",
407					);
408					return;
409				}
410
411				tracing::trace!(
412					target: LOG_TARGET,
413					?hash,
414					"Recording node",
415				);
416
417				inner.accessed_nodes.entry(hash).or_insert_with(|| {
418					let node = node_owned.to_encoded::<NodeCodec<H>>();
419
420					encoded_size_update += node.encoded_size();
421
422					if let Some(tx) = inner.transactions.last_mut() {
423						tx.accessed_nodes.insert(hash);
424					}
425
426					node
427				});
428			},
429			TrieAccess::EncodedNode { hash, encoded_node } => {
430				let inner = self.inner.deref_mut();
431
432				if inner.ignored_nodes.is_ignored(&hash) {
433					tracing::trace!(
434						target: LOG_TARGET,
435						?hash,
436						"Ignoring node",
437					);
438					return;
439				}
440
441				tracing::trace!(
442					target: LOG_TARGET,
443					hash = ?hash,
444					"Recording node",
445				);
446
447				inner.accessed_nodes.entry(hash).or_insert_with(|| {
448					let node = encoded_node.into_owned();
449
450					encoded_size_update += node.encoded_size();
451
452					if let Some(tx) = inner.transactions.last_mut() {
453						tx.accessed_nodes.insert(hash);
454					}
455
456					node
457				});
458			},
459			TrieAccess::Value { hash, value, full_key } => {
460				let inner = self.inner.deref_mut();
461
462				// A value is also just a node.
463				if inner.ignored_nodes.is_ignored(&hash) {
464					tracing::trace!(
465						target: LOG_TARGET,
466						?hash,
467						"Ignoring value",
468					);
469					return;
470				}
471
472				tracing::trace!(
473					target: LOG_TARGET,
474					hash = ?hash,
475					key = ?sp_core::hexdisplay::HexDisplay::from(&full_key),
476					"Recording value",
477				);
478
479				inner.accessed_nodes.entry(hash).or_insert_with(|| {
480					let value = value.into_owned();
481
482					encoded_size_update += value.encoded_size();
483
484					if let Some(tx) = inner.transactions.last_mut() {
485						tx.accessed_nodes.insert(hash);
486					}
487
488					value
489				});
490
491				self.update_recorded_keys(full_key, RecordedForKey::Value);
492			},
493			TrieAccess::Hash { full_key } => {
494				tracing::trace!(
495					target: LOG_TARGET,
496					key = ?sp_core::hexdisplay::HexDisplay::from(&full_key),
497					"Recorded hash access for key",
498				);
499
500				// We don't need to update the `encoded_size_update` as the hash was already
501				// accounted for by the recorded node that holds the hash.
502				self.update_recorded_keys(full_key, RecordedForKey::Hash);
503			},
504			TrieAccess::NonExisting { full_key } => {
505				tracing::trace!(
506					target: LOG_TARGET,
507					key = ?sp_core::hexdisplay::HexDisplay::from(&full_key),
508					"Recorded non-existing value access for key",
509				);
510
511				// Non-existing access means we recorded all trie nodes up to the value.
512				// Not the actual value, as it doesn't exist, but all trie nodes to know
513				// that the value doesn't exist in the trie.
514				self.update_recorded_keys(full_key, RecordedForKey::Value);
515			},
516			TrieAccess::InlineValue { full_key } => {
517				tracing::trace!(
518					target: LOG_TARGET,
519					key = ?sp_core::hexdisplay::HexDisplay::from(&full_key),
520					"Recorded inline value access for key",
521				);
522
523				// A value was accessed that is stored inline a node and we recorded all trie nodes
524				// to access this value.
525				self.update_recorded_keys(full_key, RecordedForKey::Value);
526			},
527		};
528
529		self.encoded_size_estimation.fetch_add(encoded_size_update, Ordering::Relaxed);
530	}
531
532	fn trie_nodes_recorded_for_key(&self, key: &[u8]) -> RecordedForKey {
533		self.inner
534			.recorded_keys
535			.get(&self.storage_root)
536			.and_then(|k| k.get(key).copied())
537			.unwrap_or(RecordedForKey::None)
538	}
539}
540
541#[cfg(test)]
542mod tests {
543	use super::*;
544	use crate::tests::create_trie;
545	use trie_db::{Trie, TrieDBBuilder, TrieRecorder};
546
547	type MemoryDB = crate::MemoryDB<sp_core::Blake2Hasher>;
548	type Layout = crate::LayoutV1<sp_core::Blake2Hasher>;
549	type Recorder = super::Recorder<sp_core::Blake2Hasher>;
550
551	const TEST_DATA: &[(&[u8], &[u8])] =
552		&[(b"key1", &[1; 64]), (b"key2", &[2; 64]), (b"key3", &[3; 64]), (b"key4", &[4; 64])];
553
554	#[test]
555	fn recorder_works() {
556		let (db, root) = create_trie::<Layout>(TEST_DATA);
557
558		let recorder = Recorder::default();
559
560		{
561			let mut trie_recorder = recorder.as_trie_recorder(root);
562			let trie = TrieDBBuilder::<Layout>::new(&db, &root)
563				.with_recorder(&mut trie_recorder)
564				.build();
565			assert_eq!(TEST_DATA[0].1.to_vec(), trie.get(TEST_DATA[0].0).unwrap().unwrap());
566		}
567
568		let storage_proof = recorder.drain_storage_proof();
569		let memory_db: MemoryDB = storage_proof.into_memory_db();
570
571		// Check that we recorded the required data
572		let trie = TrieDBBuilder::<Layout>::new(&memory_db, &root).build();
573		assert_eq!(TEST_DATA[0].1.to_vec(), trie.get(TEST_DATA[0].0).unwrap().unwrap());
574	}
575
576	#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
577	struct RecorderStats {
578		accessed_nodes: usize,
579		recorded_keys: usize,
580		estimated_size: usize,
581	}
582
583	impl RecorderStats {
584		fn extract(recorder: &Recorder) -> Self {
585			let inner = recorder.inner.lock();
586
587			let recorded_keys =
588				inner.recorded_keys.iter().flat_map(|(_, keys)| keys.keys()).count();
589
590			Self {
591				recorded_keys,
592				accessed_nodes: inner.accessed_nodes.len(),
593				estimated_size: recorder.estimate_encoded_size(),
594			}
595		}
596	}
597
598	#[test]
599	fn recorder_transactions_rollback_work() {
600		let (db, root) = create_trie::<Layout>(TEST_DATA);
601
602		let recorder = Recorder::default();
603		let mut stats = vec![RecorderStats::default()];
604
605		for i in 0..4 {
606			recorder.start_transaction();
607			{
608				let mut trie_recorder = recorder.as_trie_recorder(root);
609				let trie = TrieDBBuilder::<Layout>::new(&db, &root)
610					.with_recorder(&mut trie_recorder)
611					.build();
612
613				assert_eq!(TEST_DATA[i].1.to_vec(), trie.get(TEST_DATA[i].0).unwrap().unwrap());
614			}
615			stats.push(RecorderStats::extract(&recorder));
616		}
617
618		assert_eq!(4, recorder.inner.lock().transactions.len());
619
620		for i in 0..5 {
621			assert_eq!(stats[4 - i], RecorderStats::extract(&recorder));
622
623			let storage_proof = recorder.to_storage_proof();
624			let memory_db: MemoryDB = storage_proof.into_memory_db();
625
626			// Check that we recorded the required data
627			let trie = TrieDBBuilder::<Layout>::new(&memory_db, &root).build();
628
629			// Check that the required data is still present.
630			for a in 0..4 {
631				if a < 4 - i {
632					assert_eq!(TEST_DATA[a].1.to_vec(), trie.get(TEST_DATA[a].0).unwrap().unwrap());
633				} else {
634					// All the data that we already rolled back, should be gone!
635					assert!(trie.get(TEST_DATA[a].0).is_err());
636				}
637			}
638
639			if i < 4 {
640				recorder.rollback_transaction().unwrap();
641			}
642		}
643
644		assert_eq!(0, recorder.inner.lock().transactions.len());
645	}
646
647	#[test]
648	fn recorder_transactions_commit_work() {
649		let (db, root) = create_trie::<Layout>(TEST_DATA);
650
651		let recorder = Recorder::default();
652
653		for i in 0..4 {
654			recorder.start_transaction();
655			{
656				let mut trie_recorder = recorder.as_trie_recorder(root);
657				let trie = TrieDBBuilder::<Layout>::new(&db, &root)
658					.with_recorder(&mut trie_recorder)
659					.build();
660
661				assert_eq!(TEST_DATA[i].1.to_vec(), trie.get(TEST_DATA[i].0).unwrap().unwrap());
662			}
663		}
664
665		let stats = RecorderStats::extract(&recorder);
666		assert_eq!(4, recorder.inner.lock().transactions.len());
667
668		for _ in 0..4 {
669			recorder.commit_transaction().unwrap();
670		}
671		assert_eq!(0, recorder.inner.lock().transactions.len());
672		assert_eq!(stats, RecorderStats::extract(&recorder));
673
674		let storage_proof = recorder.to_storage_proof();
675		let memory_db: MemoryDB = storage_proof.into_memory_db();
676
677		// Check that we recorded the required data
678		let trie = TrieDBBuilder::<Layout>::new(&memory_db, &root).build();
679
680		// Check that the required data is still present.
681		for i in 0..4 {
682			assert_eq!(TEST_DATA[i].1.to_vec(), trie.get(TEST_DATA[i].0).unwrap().unwrap());
683		}
684	}
685
686	#[test]
687	fn recorder_transactions_commit_and_rollback_work() {
688		let (db, root) = create_trie::<Layout>(TEST_DATA);
689
690		let recorder = Recorder::default();
691
692		for i in 0..2 {
693			recorder.start_transaction();
694			{
695				let mut trie_recorder = recorder.as_trie_recorder(root);
696				let trie = TrieDBBuilder::<Layout>::new(&db, &root)
697					.with_recorder(&mut trie_recorder)
698					.build();
699
700				assert_eq!(TEST_DATA[i].1.to_vec(), trie.get(TEST_DATA[i].0).unwrap().unwrap());
701			}
702		}
703
704		recorder.rollback_transaction().unwrap();
705
706		for i in 2..4 {
707			recorder.start_transaction();
708			{
709				let mut trie_recorder = recorder.as_trie_recorder(root);
710				let trie = TrieDBBuilder::<Layout>::new(&db, &root)
711					.with_recorder(&mut trie_recorder)
712					.build();
713
714				assert_eq!(TEST_DATA[i].1.to_vec(), trie.get(TEST_DATA[i].0).unwrap().unwrap());
715			}
716		}
717
718		recorder.rollback_transaction().unwrap();
719
720		assert_eq!(2, recorder.inner.lock().transactions.len());
721
722		for _ in 0..2 {
723			recorder.commit_transaction().unwrap();
724		}
725
726		assert_eq!(0, recorder.inner.lock().transactions.len());
727
728		let storage_proof = recorder.to_storage_proof();
729		let memory_db: MemoryDB = storage_proof.into_memory_db();
730
731		// Check that we recorded the required data
732		let trie = TrieDBBuilder::<Layout>::new(&memory_db, &root).build();
733
734		// Check that the required data is still present.
735		for i in 0..4 {
736			if i % 2 == 0 {
737				assert_eq!(TEST_DATA[i].1.to_vec(), trie.get(TEST_DATA[i].0).unwrap().unwrap());
738			} else {
739				assert!(trie.get(TEST_DATA[i].0).is_err());
740			}
741		}
742	}
743
744	#[test]
745	fn recorder_transaction_accessed_keys_works() {
746		let key = TEST_DATA[0].0;
747		let (db, root) = create_trie::<Layout>(TEST_DATA);
748
749		let recorder = Recorder::default();
750
751		{
752			let trie_recorder = recorder.as_trie_recorder(root);
753			assert!(matches!(trie_recorder.trie_nodes_recorded_for_key(key), RecordedForKey::None));
754		}
755
756		recorder.start_transaction();
757		{
758			let mut trie_recorder = recorder.as_trie_recorder(root);
759			let trie = TrieDBBuilder::<Layout>::new(&db, &root)
760				.with_recorder(&mut trie_recorder)
761				.build();
762
763			assert_eq!(
764				sp_core::Blake2Hasher::hash(TEST_DATA[0].1),
765				trie.get_hash(TEST_DATA[0].0).unwrap().unwrap()
766			);
767			assert!(matches!(trie_recorder.trie_nodes_recorded_for_key(key), RecordedForKey::Hash));
768		}
769
770		recorder.start_transaction();
771		{
772			let mut trie_recorder = recorder.as_trie_recorder(root);
773			let trie = TrieDBBuilder::<Layout>::new(&db, &root)
774				.with_recorder(&mut trie_recorder)
775				.build();
776
777			assert_eq!(TEST_DATA[0].1.to_vec(), trie.get(TEST_DATA[0].0).unwrap().unwrap());
778			assert!(matches!(
779				trie_recorder.trie_nodes_recorded_for_key(key),
780				RecordedForKey::Value,
781			));
782		}
783
784		recorder.rollback_transaction().unwrap();
785		{
786			let trie_recorder = recorder.as_trie_recorder(root);
787			assert!(matches!(trie_recorder.trie_nodes_recorded_for_key(key), RecordedForKey::Hash));
788		}
789
790		recorder.rollback_transaction().unwrap();
791		{
792			let trie_recorder = recorder.as_trie_recorder(root);
793			assert!(matches!(trie_recorder.trie_nodes_recorded_for_key(key), RecordedForKey::None));
794		}
795
796		recorder.start_transaction();
797		{
798			let mut trie_recorder = recorder.as_trie_recorder(root);
799			let trie = TrieDBBuilder::<Layout>::new(&db, &root)
800				.with_recorder(&mut trie_recorder)
801				.build();
802
803			assert_eq!(TEST_DATA[0].1.to_vec(), trie.get(TEST_DATA[0].0).unwrap().unwrap());
804			assert!(matches!(
805				trie_recorder.trie_nodes_recorded_for_key(key),
806				RecordedForKey::Value,
807			));
808		}
809
810		recorder.start_transaction();
811		{
812			let mut trie_recorder = recorder.as_trie_recorder(root);
813			let trie = TrieDBBuilder::<Layout>::new(&db, &root)
814				.with_recorder(&mut trie_recorder)
815				.build();
816
817			assert_eq!(
818				sp_core::Blake2Hasher::hash(TEST_DATA[0].1),
819				trie.get_hash(TEST_DATA[0].0).unwrap().unwrap()
820			);
821			assert!(matches!(
822				trie_recorder.trie_nodes_recorded_for_key(key),
823				RecordedForKey::Value
824			));
825		}
826
827		recorder.rollback_transaction().unwrap();
828		{
829			let trie_recorder = recorder.as_trie_recorder(root);
830			assert!(matches!(
831				trie_recorder.trie_nodes_recorded_for_key(key),
832				RecordedForKey::Value
833			));
834		}
835
836		recorder.rollback_transaction().unwrap();
837		{
838			let trie_recorder = recorder.as_trie_recorder(root);
839			assert!(matches!(trie_recorder.trie_nodes_recorded_for_key(key), RecordedForKey::None));
840		}
841	}
842
843	#[test]
844	fn recorder_ignoring_nodes_works() {
845		let (db, root) = create_trie::<Layout>(TEST_DATA);
846
847		let recorder = Recorder::default();
848
849		{
850			let mut trie_recorder = recorder.as_trie_recorder(root);
851			let trie = TrieDBBuilder::<Layout>::new(&db, &root)
852				.with_recorder(&mut trie_recorder)
853				.build();
854
855			for (key, data) in TEST_DATA.iter().take(3) {
856				assert_eq!(data.to_vec(), trie.get(&key).unwrap().unwrap());
857			}
858		}
859
860		assert!(recorder.estimate_encoded_size() > 10);
861		let mut ignored_nodes = IgnoredNodes::from_storage_proof::<sp_core::Blake2Hasher>(
862			&recorder.drain_storage_proof(),
863		);
864
865		let recorder = Recorder::with_ignored_nodes(ignored_nodes.clone());
866
867		{
868			let mut trie_recorder = recorder.as_trie_recorder(root);
869			let trie = TrieDBBuilder::<Layout>::new(&db, &root)
870				.with_recorder(&mut trie_recorder)
871				.build();
872
873			for (key, data) in TEST_DATA {
874				assert_eq!(data.to_vec(), trie.get(&key).unwrap().unwrap());
875			}
876		}
877
878		assert!(recorder.estimate_encoded_size() > TEST_DATA[3].1.len());
879		let ignored_nodes2 = IgnoredNodes::from_storage_proof::<sp_core::Blake2Hasher>(
880			&recorder.drain_storage_proof(),
881		);
882
883		ignored_nodes.extend(ignored_nodes2);
884
885		let recorder = Recorder::with_ignored_nodes(ignored_nodes);
886
887		{
888			let mut trie_recorder = recorder.as_trie_recorder(root);
889			let trie = TrieDBBuilder::<Layout>::new(&db, &root)
890				.with_recorder(&mut trie_recorder)
891				.build();
892
893			for (key, data) in TEST_DATA {
894				assert_eq!(data.to_vec(), trie.get(&key).unwrap().unwrap());
895			}
896		}
897		assert_eq!(0, recorder.estimate_encoded_size());
898	}
899}