1use crate::{DbState, DbStateBuilder};
22use hash_db::{Hasher as DbHasher, Prefix};
23use kvdb::{DBTransaction, KeyValueDB};
24use linked_hash_map::LinkedHashMap;
25use parking_lot::Mutex;
26use sp_core::{
27 hexdisplay::HexDisplay,
28 storage::{ChildInfo, TrackedStorageKey},
29};
30use sp_runtime::{traits::Hash, StateVersion, Storage};
31use sp_state_machine::{
32 backend::Backend as StateBackend, BackendTransaction, ChildStorageCollection, DBValue,
33 IterArgs, StorageCollection, StorageIterator, StorageKey, StorageValue,
34};
35use sp_trie::{
36 cache::{CacheSize, SharedTrieCache},
37 prefixed_key, MemoryDB, MerkleValue,
38};
39use std::{
40 cell::{Cell, RefCell},
41 collections::HashMap,
42 sync::Arc,
43};
44
45type State<H> = DbState<H>;
46
47struct StorageDb<Hasher> {
48 db: Arc<dyn KeyValueDB>,
49 _phantom: std::marker::PhantomData<Hasher>,
50}
51
52impl<Hasher: Hash> sp_state_machine::Storage<Hasher> for StorageDb<Hasher> {
53 fn get(&self, key: &Hasher::Output, prefix: Prefix) -> Result<Option<DBValue>, String> {
54 let prefixed_key = prefixed_key::<Hasher>(key, prefix);
55 self.db
56 .get(0, &prefixed_key)
57 .map_err(|e| format!("Database backend error: {:?}", e))
58 }
59}
60
61struct KeyTracker {
62 enable_tracking: bool,
63 main_keys: LinkedHashMap<Vec<u8>, TrackedStorageKey>,
67 child_keys: LinkedHashMap<Vec<u8>, LinkedHashMap<Vec<u8>, TrackedStorageKey>>,
72}
73
74pub struct BenchmarkingState<Hasher: Hash> {
76 root: Cell<Hasher::Output>,
77 genesis_root: Hasher::Output,
78 state: RefCell<Option<State<Hasher>>>,
79 db: Cell<Option<Arc<dyn KeyValueDB>>>,
80 genesis: HashMap<Vec<u8>, (Vec<u8>, i32)>,
81 record: Cell<Vec<Vec<u8>>>,
82 key_tracker: Arc<Mutex<KeyTracker>>,
83 whitelist: RefCell<Vec<TrackedStorageKey>>,
84 proof_recorder: Option<sp_trie::recorder::Recorder<Hasher>>,
85 proof_recorder_root: Cell<Hasher::Output>,
86 shared_trie_cache: SharedTrieCache<Hasher>,
87}
88
89pub struct RawIter<Hasher: Hash> {
91 inner: <DbState<Hasher> as StateBackend<Hasher>>::RawIter,
92 child_trie: Option<Vec<u8>>,
93 key_tracker: Arc<Mutex<KeyTracker>>,
94}
95
96impl<Hasher: Hash> StorageIterator<Hasher> for RawIter<Hasher> {
97 type Backend = BenchmarkingState<Hasher>;
98 type Error = String;
99
100 fn next_key(&mut self, backend: &Self::Backend) -> Option<Result<StorageKey, Self::Error>> {
101 match self.inner.next_key(backend.state.borrow().as_ref()?) {
102 Some(Ok(key)) => {
103 self.key_tracker.lock().add_read_key(self.child_trie.as_deref(), &key);
104 Some(Ok(key))
105 },
106 result => result,
107 }
108 }
109
110 fn next_pair(
111 &mut self,
112 backend: &Self::Backend,
113 ) -> Option<Result<(StorageKey, StorageValue), Self::Error>> {
114 match self.inner.next_pair(backend.state.borrow().as_ref()?) {
115 Some(Ok((key, value))) => {
116 self.key_tracker.lock().add_read_key(self.child_trie.as_deref(), &key);
117 Some(Ok((key, value)))
118 },
119 result => result,
120 }
121 }
122
123 fn was_complete(&self) -> bool {
124 self.inner.was_complete()
125 }
126}
127
128impl<Hasher: Hash> BenchmarkingState<Hasher> {
129 pub fn new(
131 genesis: Storage,
132 _cache_size_mb: Option<usize>,
133 record_proof: bool,
134 enable_tracking: bool,
135 ) -> Result<Self, String> {
136 let state_version = sp_runtime::StateVersion::default();
137 let mut root = Default::default();
138 let mut mdb = MemoryDB::<Hasher>::default();
139 sp_trie::trie_types::TrieDBMutBuilderV1::<Hasher>::new(&mut mdb, &mut root).build();
140
141 let mut state = BenchmarkingState {
142 state: RefCell::new(None),
143 db: Cell::new(None),
144 root: Cell::new(root),
145 genesis: Default::default(),
146 genesis_root: Default::default(),
147 record: Default::default(),
148 key_tracker: Arc::new(Mutex::new(KeyTracker {
149 main_keys: Default::default(),
150 child_keys: Default::default(),
151 enable_tracking,
152 })),
153 whitelist: Default::default(),
154 proof_recorder: record_proof.then(Default::default),
155 proof_recorder_root: Cell::new(root),
156 shared_trie_cache: SharedTrieCache::new(CacheSize::new(0), None),
158 };
159
160 state.add_whitelist_to_tracker();
161
162 state.reopen()?;
163 let child_delta = genesis.children_default.values().map(|child_content| {
164 (
165 &child_content.child_info,
166 child_content.data.iter().map(|(k, v)| (k.as_ref(), Some(v.as_ref()))),
167 )
168 });
169 let (root, transaction): (Hasher::Output, _) =
170 state.state.borrow().as_ref().unwrap().full_storage_root(
171 genesis.top.iter().map(|(k, v)| (k.as_ref(), Some(v.as_ref()))),
172 child_delta,
173 state_version,
174 );
175 state.genesis = transaction.clone().drain();
176 state.genesis_root = root;
177 state.commit(root, transaction, Vec::new(), Vec::new())?;
178 state.record.take();
179 Ok(state)
180 }
181
182 pub fn recorder(&self) -> Option<sp_trie::recorder::Recorder<Hasher>> {
184 self.proof_recorder.clone()
185 }
186
187 fn reopen(&self) -> Result<(), String> {
188 *self.state.borrow_mut() = None;
189 let db = match self.db.take() {
190 Some(db) => db,
191 None => Arc::new(kvdb_memorydb::create(1)),
192 };
193 self.db.set(Some(db.clone()));
194 if let Some(recorder) = &self.proof_recorder {
195 recorder.reset();
196 self.proof_recorder_root.set(self.root.get());
197 }
198 let storage_db = Arc::new(StorageDb::<Hasher> { db, _phantom: Default::default() });
199 *self.state.borrow_mut() = Some(
200 DbStateBuilder::<Hasher>::new(storage_db, self.root.get())
201 .with_optional_recorder(self.proof_recorder.clone())
202 .with_cache(self.shared_trie_cache.local_cache_trusted())
203 .build(),
204 );
205 Ok(())
206 }
207
208 fn add_whitelist_to_tracker(&self) {
209 self.key_tracker.lock().add_whitelist(&self.whitelist.borrow());
210 }
211
212 fn wipe_tracker(&self) {
213 let mut key_tracker = self.key_tracker.lock();
214 key_tracker.main_keys = LinkedHashMap::new();
215 key_tracker.child_keys = LinkedHashMap::new();
216 key_tracker.add_whitelist(&self.whitelist.borrow());
217 }
218
219 fn add_read_key(&self, childtrie: Option<&[u8]>, key: &[u8]) {
220 self.key_tracker.lock().add_read_key(childtrie, key);
221 }
222
223 fn add_write_key(&self, childtrie: Option<&[u8]>, key: &[u8]) {
224 self.key_tracker.lock().add_write_key(childtrie, key);
225 }
226
227 fn all_trackers(&self) -> Vec<TrackedStorageKey> {
228 self.key_tracker.lock().all_trackers()
229 }
230}
231
232impl KeyTracker {
233 fn add_whitelist(&mut self, whitelist: &[TrackedStorageKey]) {
234 whitelist.iter().for_each(|key| {
235 let mut whitelisted = TrackedStorageKey::new(key.key.clone());
236 whitelisted.whitelist();
237 if let Some(child_trie_key) = &key.child_trie_key {
238 self.child_keys
239 .entry(child_trie_key.clone())
240 .or_default()
241 .insert(key.key.clone(), whitelisted);
242 } else {
243 self.main_keys.insert(key.key.clone(), whitelisted);
244 }
245 });
246 }
247
248 fn add_read_key(&mut self, childtrie: Option<&[u8]>, key: &[u8]) {
250 if !self.enable_tracking {
251 return;
252 }
253
254 let child_key_tracker = &mut self.child_keys;
255 let main_key_tracker = &mut self.main_keys;
256
257 let key_tracker = if let Some(childtrie) = childtrie {
258 child_key_tracker.entry(childtrie.to_vec()).or_insert_with(LinkedHashMap::new)
259 } else {
260 main_key_tracker
261 };
262
263 let should_log = match key_tracker.get_mut(key) {
264 None => {
265 let mut has_been_read = TrackedStorageKey::new(key.to_vec());
266 has_been_read.add_read();
267 key_tracker.insert(key.to_vec(), has_been_read);
268 true
269 },
270 Some(tracker) => {
271 let should_log = !tracker.has_been_read();
272 tracker.add_read();
273 should_log
274 },
275 };
276
277 if should_log {
278 if let Some(childtrie) = childtrie {
279 log::trace!(
280 target: "benchmark",
281 "Childtrie Read: {} {}", HexDisplay::from(&childtrie), HexDisplay::from(&key)
282 );
283 } else {
284 log::trace!(target: "benchmark", "Read: {}", HexDisplay::from(&key));
285 }
286 }
287 }
288
289 fn add_write_key(&mut self, childtrie: Option<&[u8]>, key: &[u8]) {
291 if !self.enable_tracking {
292 return;
293 }
294
295 let child_key_tracker = &mut self.child_keys;
296 let main_key_tracker = &mut self.main_keys;
297
298 let key_tracker = if let Some(childtrie) = childtrie {
299 child_key_tracker.entry(childtrie.to_vec()).or_insert_with(LinkedHashMap::new)
300 } else {
301 main_key_tracker
302 };
303
304 let should_log = match key_tracker.get_mut(key) {
306 None => {
307 let mut has_been_written = TrackedStorageKey::new(key.to_vec());
308 has_been_written.add_write();
309 key_tracker.insert(key.to_vec(), has_been_written);
310 true
311 },
312 Some(tracker) => {
313 let should_log = !tracker.has_been_written();
314 tracker.add_write();
315 should_log
316 },
317 };
318
319 if should_log {
320 if let Some(childtrie) = childtrie {
321 log::trace!(
322 target: "benchmark",
323 "Childtrie Write: {} {}", HexDisplay::from(&childtrie), HexDisplay::from(&key)
324 );
325 } else {
326 log::trace!(target: "benchmark", "Write: {}", HexDisplay::from(&key));
327 }
328 }
329 }
330
331 fn all_trackers(&self) -> Vec<TrackedStorageKey> {
333 let mut all_trackers = Vec::new();
334
335 self.main_keys.iter().for_each(|(_, tracker)| {
336 all_trackers.push(tracker.clone());
337 });
338
339 self.child_keys.iter().for_each(|(_, child_tracker)| {
340 child_tracker.iter().for_each(|(_, tracker)| {
341 all_trackers.push(tracker.clone());
342 });
343 });
344
345 all_trackers
346 }
347}
348
349fn state_err() -> String {
350 "State is not open".into()
351}
352
353impl<Hasher: Hash> StateBackend<Hasher> for BenchmarkingState<Hasher> {
354 type Error = <DbState<Hasher> as StateBackend<Hasher>>::Error;
355 type TrieBackendStorage = <DbState<Hasher> as StateBackend<Hasher>>::TrieBackendStorage;
356 type RawIter = RawIter<Hasher>;
357
358 fn storage(&self, key: &[u8]) -> Result<Option<Vec<u8>>, Self::Error> {
359 self.add_read_key(None, key);
360 self.state.borrow().as_ref().ok_or_else(state_err)?.storage(key)
361 }
362
363 fn storage_hash(&self, key: &[u8]) -> Result<Option<Hasher::Output>, Self::Error> {
364 self.add_read_key(None, key);
365 self.state.borrow().as_ref().ok_or_else(state_err)?.storage_hash(key)
366 }
367
368 fn child_storage(
369 &self,
370 child_info: &ChildInfo,
371 key: &[u8],
372 ) -> Result<Option<Vec<u8>>, Self::Error> {
373 self.add_read_key(Some(child_info.storage_key()), key);
374 self.state
375 .borrow()
376 .as_ref()
377 .ok_or_else(state_err)?
378 .child_storage(child_info, key)
379 }
380
381 fn child_storage_hash(
382 &self,
383 child_info: &ChildInfo,
384 key: &[u8],
385 ) -> Result<Option<Hasher::Output>, Self::Error> {
386 self.add_read_key(Some(child_info.storage_key()), key);
387 self.state
388 .borrow()
389 .as_ref()
390 .ok_or_else(state_err)?
391 .child_storage_hash(child_info, key)
392 }
393
394 fn closest_merkle_value(
395 &self,
396 key: &[u8],
397 ) -> Result<Option<MerkleValue<Hasher::Output>>, Self::Error> {
398 self.add_read_key(None, key);
399 self.state.borrow().as_ref().ok_or_else(state_err)?.closest_merkle_value(key)
400 }
401
402 fn child_closest_merkle_value(
403 &self,
404 child_info: &ChildInfo,
405 key: &[u8],
406 ) -> Result<Option<MerkleValue<Hasher::Output>>, Self::Error> {
407 self.add_read_key(Some(child_info.storage_key()), key);
408 self.state
409 .borrow()
410 .as_ref()
411 .ok_or_else(state_err)?
412 .child_closest_merkle_value(child_info, key)
413 }
414
415 fn exists_storage(&self, key: &[u8]) -> Result<bool, Self::Error> {
416 self.add_read_key(None, key);
417 self.state.borrow().as_ref().ok_or_else(state_err)?.exists_storage(key)
418 }
419
420 fn exists_child_storage(
421 &self,
422 child_info: &ChildInfo,
423 key: &[u8],
424 ) -> Result<bool, Self::Error> {
425 self.add_read_key(Some(child_info.storage_key()), key);
426 self.state
427 .borrow()
428 .as_ref()
429 .ok_or_else(state_err)?
430 .exists_child_storage(child_info, key)
431 }
432
433 fn next_storage_key(&self, key: &[u8]) -> Result<Option<Vec<u8>>, Self::Error> {
434 self.add_read_key(None, key);
435 self.state.borrow().as_ref().ok_or_else(state_err)?.next_storage_key(key)
436 }
437
438 fn next_child_storage_key(
439 &self,
440 child_info: &ChildInfo,
441 key: &[u8],
442 ) -> Result<Option<Vec<u8>>, Self::Error> {
443 self.add_read_key(Some(child_info.storage_key()), key);
444 self.state
445 .borrow()
446 .as_ref()
447 .ok_or_else(state_err)?
448 .next_child_storage_key(child_info, key)
449 }
450
451 fn storage_root<'a>(
452 &self,
453 delta: impl Iterator<Item = (&'a [u8], Option<&'a [u8]>)>,
454 state_version: StateVersion,
455 ) -> (Hasher::Output, BackendTransaction<Hasher>) {
456 self.state
457 .borrow()
458 .as_ref()
459 .map_or(Default::default(), |s| s.storage_root(delta, state_version))
460 }
461
462 fn child_storage_root<'a>(
463 &self,
464 child_info: &ChildInfo,
465 delta: impl Iterator<Item = (&'a [u8], Option<&'a [u8]>)>,
466 state_version: StateVersion,
467 ) -> (Hasher::Output, bool, BackendTransaction<Hasher>) {
468 self.state
469 .borrow()
470 .as_ref()
471 .map_or(Default::default(), |s| s.child_storage_root(child_info, delta, state_version))
472 }
473
474 fn raw_iter(&self, args: IterArgs) -> Result<Self::RawIter, Self::Error> {
475 let child_trie =
476 args.child_info.as_ref().map(|child_info| child_info.storage_key().to_vec());
477 self.state
478 .borrow()
479 .as_ref()
480 .map(|s| s.raw_iter(args))
481 .unwrap_or(Ok(Default::default()))
482 .map(|raw_iter| RawIter {
483 inner: raw_iter,
484 key_tracker: self.key_tracker.clone(),
485 child_trie,
486 })
487 }
488
489 fn commit(
490 &self,
491 storage_root: <Hasher as DbHasher>::Out,
492 mut transaction: BackendTransaction<Hasher>,
493 main_storage_changes: StorageCollection,
494 child_storage_changes: ChildStorageCollection,
495 ) -> Result<(), Self::Error> {
496 if let Some(db) = self.db.take() {
497 let mut db_transaction = DBTransaction::new();
498 let changes = transaction.drain();
499 let mut keys = Vec::with_capacity(changes.len());
500 for (key, (val, rc)) in changes {
501 if rc > 0 {
502 db_transaction.put(0, &key, &val);
503 } else if rc < 0 {
504 db_transaction.delete(0, &key);
505 }
506 keys.push(key);
507 }
508 let mut record = self.record.take();
509 record.extend(keys);
510 self.record.set(record);
511 db.write(db_transaction)
512 .map_err(|_| String::from("Error committing transaction"))?;
513 self.root.set(storage_root);
514 self.db.set(Some(db));
515
516 main_storage_changes.iter().for_each(|(key, _)| {
518 self.add_write_key(None, key);
519 });
520 child_storage_changes.iter().for_each(|(child_storage_key, storage_changes)| {
521 storage_changes.iter().for_each(|(key, _)| {
522 self.add_write_key(Some(child_storage_key), key);
523 })
524 });
525 } else {
526 return Err("Trying to commit to a closed db".into());
527 }
528 self.reopen()
529 }
530
531 fn wipe(&self) -> Result<(), Self::Error> {
532 let record = self.record.take();
534 if let Some(db) = self.db.take() {
535 let mut db_transaction = DBTransaction::new();
536 for key in record {
537 match self.genesis.get(&key) {
538 Some((v, _)) => db_transaction.put(0, &key, v),
539 None => db_transaction.delete(0, &key),
540 }
541 }
542 db.write(db_transaction)
543 .map_err(|_| String::from("Error committing transaction"))?;
544 self.db.set(Some(db));
545 }
546
547 self.root.set(self.genesis_root);
548 self.reopen()?;
549 self.wipe_tracker();
550 Ok(())
551 }
552
553 fn read_write_count(&self) -> (u32, u32, u32, u32) {
559 let mut reads = 0;
560 let mut repeat_reads = 0;
561 let mut writes = 0;
562 let mut repeat_writes = 0;
563
564 self.all_trackers().iter().for_each(|tracker| {
565 if !tracker.whitelisted {
566 if tracker.reads > 0 {
567 reads += 1;
568 repeat_reads += tracker.reads - 1;
569 }
570
571 if tracker.writes > 0 {
572 writes += 1;
573 repeat_writes += tracker.writes - 1;
574 }
575 }
576 });
577 (reads, repeat_reads, writes, repeat_writes)
578 }
579
580 fn reset_read_write_count(&self) {
582 self.wipe_tracker()
583 }
584
585 fn get_whitelist(&self) -> Vec<TrackedStorageKey> {
586 self.whitelist.borrow().to_vec()
587 }
588
589 fn set_whitelist(&self, new: Vec<TrackedStorageKey>) {
590 *self.whitelist.borrow_mut() = new;
591 }
592
593 fn get_read_and_written_keys(&self) -> Vec<(Vec<u8>, u32, u32, bool)> {
594 let mut prefix_key_tracker = LinkedHashMap::<Vec<u8>, (u32, u32, bool)>::new();
598 self.all_trackers().iter().for_each(|tracker| {
599 if !tracker.whitelisted {
600 let prefix_length = tracker.key.len().min(32);
601 let prefix = tracker.key[0..prefix_length].to_vec();
602 let reads = tracker.reads.min(1);
605 let writes = tracker.writes.min(1);
606 if let Some(prefix_tracker) = prefix_key_tracker.get_mut(&prefix) {
607 prefix_tracker.0 += reads;
608 prefix_tracker.1 += writes;
609 } else {
610 prefix_key_tracker.insert(prefix, (reads, writes, tracker.whitelisted));
611 }
612 }
613 });
614
615 prefix_key_tracker
616 .iter()
617 .map(|(key, tracker)| -> (Vec<u8>, u32, u32, bool) {
618 (key.to_vec(), tracker.0, tracker.1, tracker.2)
619 })
620 .collect::<Vec<_>>()
621 }
622
623 fn register_overlay_stats(&self, stats: &sp_state_machine::StateMachineStats) {
624 self.state.borrow().as_ref().map(|s| s.register_overlay_stats(stats));
625 }
626
627 fn usage_info(&self) -> sp_state_machine::UsageInfo {
628 self.state
629 .borrow()
630 .as_ref()
631 .map_or(sp_state_machine::UsageInfo::empty(), |s| s.usage_info())
632 }
633
634 fn proof_size(&self) -> Option<u32> {
635 self.proof_recorder.as_ref().map(|recorder| {
636 let proof_size = recorder.estimate_encoded_size() as u32;
637
638 let proof = recorder.to_storage_proof();
639
640 let proof_recorder_root = self.proof_recorder_root.get();
641 if proof_recorder_root == Default::default() || proof_size == 1 {
642 log::debug!(target: "benchmark", "Some proof size: {}", &proof_size);
644 proof_size
645 } else {
646 if let Some(size) = proof.encoded_compact_size::<Hasher>(proof_recorder_root) {
647 size as u32
648 } else if proof_recorder_root == self.root.get() {
649 log::debug!(target: "benchmark", "No changes - no proof");
650 0
651 } else {
652 panic!(
653 "proof rec root {:?}, root {:?}, genesis {:?}, rec_len {:?}",
654 self.proof_recorder_root.get(),
655 self.root.get(),
656 self.genesis_root,
657 proof_size,
658 );
659 }
660 }
661 })
662 }
663}
664
665impl<Hasher: Hash> std::fmt::Debug for BenchmarkingState<Hasher> {
666 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
667 write!(f, "Bench DB")
668 }
669}
670
671#[cfg(test)]
672mod test {
673 use crate::bench::BenchmarkingState;
674 use sp_runtime::traits::HashingFor;
675 use sp_state_machine::backend::Backend as _;
676
677 fn hex(hex: &str) -> Vec<u8> {
678 array_bytes::hex2bytes(hex).unwrap()
679 }
680
681 #[test]
682 fn iteration_is_also_counted_in_rw_counts() {
683 let storage = sp_runtime::Storage {
684 top: vec![(
685 hex("ce6e1397e668c7fcf47744350dc59688455a2c2dbd2e2a649df4e55d93cd7158"),
686 hex("0102030405060708"),
687 )]
688 .into_iter()
689 .collect(),
690 ..sp_runtime::Storage::default()
691 };
692 let bench_state =
693 BenchmarkingState::<HashingFor<crate::tests::Block>>::new(storage, None, false, true)
694 .unwrap();
695
696 assert_eq!(bench_state.read_write_count(), (0, 0, 0, 0));
697 assert_eq!(bench_state.keys(Default::default()).unwrap().count(), 1);
698 assert_eq!(bench_state.read_write_count(), (1, 0, 0, 0));
699 }
700
701 #[test]
702 fn read_to_main_and_child_tries() {
703 let bench_state = BenchmarkingState::<HashingFor<crate::tests::Block>>::new(
704 Default::default(),
705 None,
706 false,
707 true,
708 )
709 .unwrap();
710
711 for _ in 0..2 {
712 let child1 = sp_core::storage::ChildInfo::new_default(b"child1");
713 let child2 = sp_core::storage::ChildInfo::new_default(b"child2");
714
715 bench_state.storage(b"foo").unwrap();
716 bench_state.child_storage(&child1, b"foo").unwrap();
717 bench_state.child_storage(&child2, b"foo").unwrap();
718
719 bench_state.storage(b"bar").unwrap();
720 bench_state.child_storage(&child1, b"bar").unwrap();
721 bench_state.child_storage(&child2, b"bar").unwrap();
722
723 bench_state
724 .commit(
725 Default::default(),
726 Default::default(),
727 vec![("foo".as_bytes().to_vec(), None)],
728 vec![("child1".as_bytes().to_vec(), vec![("foo".as_bytes().to_vec(), None)])],
729 )
730 .unwrap();
731
732 let rw_tracker = bench_state.read_write_count();
733 assert_eq!(rw_tracker.0, 6);
734 assert_eq!(rw_tracker.1, 0);
735 assert_eq!(rw_tracker.2, 2);
736 assert_eq!(rw_tracker.3, 0);
737 bench_state.wipe().unwrap();
738 }
739 }
740}