1use std::{collections::BTreeMap, num::NonZeroUsize, path::PathBuf, sync::Arc, time::Duration};
6
7use iota_config::genesis;
8use iota_protocol_config::ProtocolVersion;
9use iota_swarm_config::{genesis_config::AccountConfig, network_config_builder::ConfigBuilder};
10use iota_types::{
11 base_types::{IotaAddress, ObjectID, SequenceNumber, VersionNumber},
12 committee::{Committee, EpochId},
13 crypto::AccountKeyPair,
14 digests::{ObjectDigest, TransactionDigest, TransactionEventsDigest},
15 effects::{TransactionEffects, TransactionEffectsAPI, TransactionEvents},
16 error::{IotaError, UserInputError},
17 messages_checkpoint::{
18 CheckpointContents, CheckpointContentsDigest, CheckpointDigest, CheckpointSequenceNumber,
19 VerifiedCheckpoint,
20 },
21 object::{Object, Owner},
22 storage::{
23 BackingPackageStore, ChildObjectResolver, ObjectStore, PackageObject, ReadStore,
24 RestStateReader, load_package_object_from_object_store,
25 },
26 transaction::VerifiedTransaction,
27};
28use move_binary_format::CompiledModule;
29use move_bytecode_utils::module_cache::GetModule;
30use move_core_types::{
31 language_storage::{ModuleId, StructTag},
32 resolver::ModuleResolver,
33};
34use simulacrum::Simulacrum;
35use tempfile::tempdir;
36use typed_store::{
37 DBMapUtils, Map,
38 metrics::SamplingInterval,
39 rocks::{DBMap, MetricConf},
40 traits::{TableSummary, TypedStoreDebug},
41};
42
43use super::SimulatorStore;
44
45pub struct PersistedStore {
46 pub path: PathBuf,
47 pub read_write: PersistedStoreInner,
48}
49
50pub struct PersistedStoreInnerReadOnlyWrapper {
51 pub path: PathBuf,
52 pub inner: PersistedStoreInnerReadOnly,
53}
54
55#[derive(Debug, DBMapUtils)]
56pub struct PersistedStoreInner {
57 checkpoints:
59 DBMap<CheckpointSequenceNumber, iota_types::messages_checkpoint::TrustedCheckpoint>,
60 checkpoint_digest_to_sequence_number: DBMap<CheckpointDigest, CheckpointSequenceNumber>,
61 checkpoint_contents: DBMap<CheckpointContentsDigest, CheckpointContents>,
62
63 transactions: DBMap<TransactionDigest, iota_types::transaction::TrustedTransaction>,
65 effects: DBMap<TransactionDigest, TransactionEffects>,
66 events: DBMap<TransactionEventsDigest, TransactionEvents>,
67 events_tx_digest_index: DBMap<TransactionDigest, TransactionEventsDigest>,
68
69 epoch_to_committee: DBMap<(), Vec<Committee>>,
71
72 live_objects: DBMap<ObjectID, SequenceNumber>,
74 objects: DBMap<ObjectID, BTreeMap<SequenceNumber, Object>>,
75}
76
77impl PersistedStore {
78 pub fn new(genesis: &genesis::Genesis, path: PathBuf) -> Self {
79 let samp: SamplingInterval = SamplingInterval::new(Duration::from_secs(60), 0);
80 let read_write = PersistedStoreInner::open_tables_read_write(
81 path.clone(),
82 MetricConf::new("persisted").with_sampling(samp.clone()),
83 None,
84 None,
85 );
86
87 let mut res = Self { path, read_write };
88 res.init_with_genesis(genesis);
89
90 res
91 }
92
93 pub fn read_replica(&self) -> PersistedStoreInnerReadOnlyWrapper {
94 let samp: SamplingInterval = SamplingInterval::new(Duration::from_secs(60), 0);
95 PersistedStoreInnerReadOnlyWrapper {
96 path: self.path.clone(),
97 inner: PersistedStoreInner::get_read_only_handle(
98 self.path.clone(),
99 None,
100 None,
101 MetricConf::new("persisted_readonly").with_sampling(samp),
102 ),
103 }
104 }
105
106 pub fn new_sim_replica_with_protocol_version_and_accounts<R>(
107 mut rng: R,
108 chain_start_timestamp_ms: u64,
109 protocol_version: ProtocolVersion,
110 account_configs: Vec<AccountConfig>,
111 validator_keys: Option<Vec<AccountKeyPair>>,
112 reference_gas_price: Option<u64>,
113 path: Option<PathBuf>,
114 ) -> (Simulacrum<R, Self>, PersistedStoreInnerReadOnlyWrapper)
115 where
116 R: rand::RngCore + rand::CryptoRng,
117 {
118 let path: PathBuf = path.unwrap_or(tempdir().unwrap().into_path());
119
120 let mut builder = ConfigBuilder::new_with_temp_dir()
121 .rng(&mut rng)
122 .with_chain_start_timestamp_ms(chain_start_timestamp_ms)
123 .deterministic_committee_size(NonZeroUsize::new(1).unwrap())
124 .with_protocol_version(protocol_version)
125 .with_accounts(account_configs);
126
127 if let Some(validator_keys) = validator_keys {
128 builder = builder.deterministic_committee_validators(validator_keys)
129 };
130 if let Some(reference_gas_price) = reference_gas_price {
131 builder = builder.with_reference_gas_price(reference_gas_price)
132 };
133
134 let config = builder.build();
135
136 let genesis = &config.genesis;
137
138 let store = PersistedStore::new(genesis, path);
139 let read_only_wrapper = store.read_replica();
140 (
141 Simulacrum::new_with_network_config_store(&config, rng, store),
142 read_only_wrapper,
143 )
144 }
145
146 pub fn new_sim_with_protocol_version_and_accounts<R>(
147 rng: R,
148 chain_start_timestamp_ms: u64,
149 protocol_version: ProtocolVersion,
150 account_configs: Vec<AccountConfig>,
151 path: Option<PathBuf>,
152 ) -> Simulacrum<R, Self>
153 where
154 R: rand::RngCore + rand::CryptoRng,
155 {
156 Self::new_sim_replica_with_protocol_version_and_accounts(
157 rng,
158 chain_start_timestamp_ms,
159 protocol_version,
160 account_configs,
161 None,
162 None,
163 path,
164 )
165 .0
166 }
167}
168
169impl SimulatorStore for PersistedStore {
170 fn get_checkpoint_by_sequence_number(
171 &self,
172 sequence_number: CheckpointSequenceNumber,
173 ) -> Option<VerifiedCheckpoint> {
174 self.read_write
175 .checkpoints
176 .get(&sequence_number)
177 .expect("Fatal: DB read failed")
178 .map(|checkpoint| checkpoint.into())
179 }
180
181 fn get_checkpoint_by_digest(&self, digest: &CheckpointDigest) -> Option<VerifiedCheckpoint> {
182 self.read_write
183 .checkpoint_digest_to_sequence_number
184 .get(digest)
185 .expect("Fatal: DB read failed")
186 .and_then(|sequence_number| self.get_checkpoint_by_sequence_number(sequence_number))
187 }
188
189 fn get_highest_checkpoint(&self) -> Option<VerifiedCheckpoint> {
190 self.read_write
191 .checkpoints
192 .unbounded_iter()
193 .skip_to_last()
194 .next()
195 .map(|(_, checkpoint)| checkpoint.into())
196 }
197
198 fn get_checkpoint_contents(
199 &self,
200 digest: &CheckpointContentsDigest,
201 ) -> Option<CheckpointContents> {
202 self.read_write
203 .checkpoint_contents
204 .get(digest)
205 .expect("Fatal: DB read failed")
206 }
207
208 fn get_committee_by_epoch(&self, epoch: EpochId) -> Option<Committee> {
209 self.read_write
210 .epoch_to_committee
211 .get(&())
212 .expect("Fatal: DB read failed")
213 .and_then(|committees| committees.get(epoch as usize).cloned())
214 }
215
216 fn get_transaction(&self, digest: &TransactionDigest) -> Option<VerifiedTransaction> {
217 self.read_write
218 .transactions
219 .get(digest)
220 .expect("Fatal: DB read failed")
221 .map(|transaction| transaction.into())
222 }
223
224 fn get_transaction_effects(&self, digest: &TransactionDigest) -> Option<TransactionEffects> {
225 self.read_write
226 .effects
227 .get(digest)
228 .expect("Fatal: DB read failed")
229 }
230
231 fn get_transaction_events(
232 &self,
233 digest: &TransactionEventsDigest,
234 ) -> Option<TransactionEvents> {
235 self.read_write
236 .events
237 .get(digest)
238 .expect("Fatal: DB read failed")
239 }
240
241 fn get_transaction_events_by_tx_digest(
242 &self,
243 tx_digest: &TransactionDigest,
244 ) -> Option<TransactionEvents> {
245 self.read_write
246 .events_tx_digest_index
247 .get(tx_digest)
248 .expect("Fatal: DB read failed")
249 .and_then(|x| {
250 self.read_write
251 .events
252 .get(&x)
253 .expect("Fatal: DB read failed")
254 })
255 }
256
257 fn get_object(&self, id: &ObjectID) -> Option<Object> {
258 let version = self
259 .read_write
260 .live_objects
261 .get(id)
262 .expect("Fatal: DB read failed")?;
263 self.get_object_at_version(id, version)
264 }
265
266 fn get_object_at_version(&self, id: &ObjectID, version: SequenceNumber) -> Option<Object> {
267 self.read_write
268 .objects
269 .get(id)
270 .expect("Fatal: DB read failed")
271 .and_then(|versions| versions.get(&version).cloned())
272 }
273
274 fn get_system_state(&self) -> iota_types::iota_system_state::IotaSystemState {
275 iota_types::iota_system_state::get_iota_system_state(self).expect("system state must exist")
276 }
277
278 fn get_clock(&self) -> iota_types::clock::Clock {
279 SimulatorStore::get_object(self, &iota_types::IOTA_CLOCK_OBJECT_ID)
280 .expect("clock should exist")
281 .to_rust()
282 .expect("clock object should deserialize")
283 }
284
285 fn owned_objects(&self, owner: IotaAddress) -> Box<dyn Iterator<Item = Object> + '_> {
286 Box::new(self.read_write.live_objects
287 .unbounded_iter()
288 .flat_map(|(id, version)| self.get_object_at_version(&id, version))
289 .filter(
290 move |object| matches!(object.owner, Owner::AddressOwner(addr) if addr == owner),
291 ))
292 }
293
294 fn insert_checkpoint(&mut self, checkpoint: VerifiedCheckpoint) {
295 self.read_write
296 .checkpoint_digest_to_sequence_number
297 .insert(checkpoint.digest(), checkpoint.sequence_number())
298 .expect("Fatal: DB write failed");
299 self.read_write
300 .checkpoints
301 .insert(checkpoint.sequence_number(), checkpoint.serializable_ref())
302 .expect("Fatal: DB write failed");
303 }
304
305 fn insert_checkpoint_contents(&mut self, contents: CheckpointContents) {
306 self.read_write
307 .checkpoint_contents
308 .insert(contents.digest(), &contents)
309 .expect("Fatal: DB write failed");
310 }
311
312 fn insert_committee(&mut self, committee: Committee) {
313 let epoch = committee.epoch as usize;
314
315 let mut committees = self
316 .read_write
317 .epoch_to_committee
318 .get(&())
319 .expect("Fatal: DB read failed")
320 .unwrap_or_default();
321
322 if committees.get(epoch).is_some() {
323 return;
324 }
325
326 if committees.len() == epoch {
327 committees.push(committee);
328 } else {
329 panic!("committee was inserted into EpochCommitteeMap out of order");
330 }
331 self.read_write
332 .epoch_to_committee
333 .insert(&(), &committees)
334 .expect("Fatal: DB write failed");
335 }
336
337 fn insert_executed_transaction(
338 &mut self,
339 transaction: VerifiedTransaction,
340 effects: TransactionEffects,
341 events: TransactionEvents,
342 written_objects: BTreeMap<ObjectID, Object>,
343 ) {
344 let deleted_objects = effects.deleted();
345 let tx_digest = *effects.transaction_digest();
346 self.insert_transaction(transaction);
347 self.insert_transaction_effects(effects);
348 self.insert_events(&tx_digest, events);
349 self.update_objects(written_objects, deleted_objects);
350 }
351
352 fn insert_transaction(&mut self, transaction: VerifiedTransaction) {
353 self.read_write
354 .transactions
355 .insert(transaction.digest(), transaction.serializable_ref())
356 .expect("Fatal: DB write failed");
357 }
358
359 fn insert_transaction_effects(&mut self, effects: TransactionEffects) {
360 self.read_write
361 .effects
362 .insert(effects.transaction_digest(), &effects)
363 .expect("Fatal: DB write failed");
364 }
365
366 fn insert_events(&mut self, tx_digest: &TransactionDigest, events: TransactionEvents) {
367 self.read_write
368 .events_tx_digest_index
369 .insert(tx_digest, &events.digest())
370 .expect("Fatal: DB write failed");
371 self.read_write
372 .events
373 .insert(&events.digest(), &events)
374 .expect("Fatal: DB write failed");
375 }
376
377 fn update_objects(
378 &mut self,
379 written_objects: BTreeMap<ObjectID, Object>,
380 deleted_objects: Vec<(ObjectID, SequenceNumber, ObjectDigest)>,
381 ) {
382 for (object_id, _, _) in deleted_objects {
383 self.read_write
384 .live_objects
385 .remove(&object_id)
386 .expect("Fatal: DB write failed");
387 }
388
389 for (object_id, object) in written_objects {
390 let version = object.version();
391 self.read_write
392 .live_objects
393 .insert(&object_id, &version)
394 .expect("Fatal: DB write failed");
395 let mut q = self
396 .read_write
397 .objects
398 .get(&object_id)
399 .expect("Fatal: DB read failed")
400 .unwrap_or_default();
401 q.insert(version, object);
402 self.read_write
403 .objects
404 .insert(&object_id, &q)
405 .expect("Fatal: DB write failed");
406 }
407 }
408
409 fn backing_store(&self) -> &dyn iota_types::storage::BackingStore {
410 self
411 }
412}
413
414impl BackingPackageStore for PersistedStore {
415 fn get_package_object(
416 &self,
417 package_id: &ObjectID,
418 ) -> iota_types::error::IotaResult<Option<PackageObject>> {
419 load_package_object_from_object_store(self, package_id)
420 }
421}
422
423impl ChildObjectResolver for PersistedStore {
424 fn read_child_object(
425 &self,
426 parent: &ObjectID,
427 child: &ObjectID,
428 child_version_upper_bound: SequenceNumber,
429 ) -> iota_types::error::IotaResult<Option<Object>> {
430 let child_object = match SimulatorStore::get_object(self, child) {
431 None => return Ok(None),
432 Some(obj) => obj,
433 };
434
435 let parent = *parent;
436 if child_object.owner != Owner::ObjectOwner(parent.into()) {
437 return Err(IotaError::InvalidChildObjectAccess {
438 object: *child,
439 given_parent: parent,
440 actual_owner: child_object.owner,
441 });
442 }
443
444 if child_object.version() > child_version_upper_bound {
445 return Err(IotaError::UnsupportedFeature {
446 error: "TODO InMemoryStorage::read_child_object does not yet support bounded reads"
447 .to_owned(),
448 });
449 }
450
451 Ok(Some(child_object))
452 }
453
454 fn get_object_received_at_version(
455 &self,
456 owner: &ObjectID,
457 receiving_object_id: &ObjectID,
458 receive_object_at_version: SequenceNumber,
459 _epoch_id: EpochId,
460 ) -> iota_types::error::IotaResult<Option<Object>> {
461 let recv_object = match SimulatorStore::get_object(self, receiving_object_id) {
462 None => return Ok(None),
463 Some(obj) => obj,
464 };
465 if recv_object.owner != Owner::AddressOwner((*owner).into()) {
466 return Ok(None);
467 }
468
469 if recv_object.version() != receive_object_at_version {
470 return Ok(None);
471 }
472 Ok(Some(recv_object))
473 }
474}
475
476impl GetModule for PersistedStore {
477 type Error = IotaError;
478 type Item = CompiledModule;
479
480 fn get_module_by_id(&self, id: &ModuleId) -> Result<Option<Self::Item>, Self::Error> {
481 Ok(self
482 .get_module(id)?
483 .map(|bytes| CompiledModule::deserialize_with_defaults(&bytes).unwrap()))
484 }
485}
486
487impl ModuleResolver for PersistedStore {
488 type Error = IotaError;
489
490 fn get_module(&self, module_id: &ModuleId) -> Result<Option<Vec<u8>>, Self::Error> {
491 Ok(self
492 .get_package_object(&ObjectID::from(*module_id.address()))?
493 .and_then(|package| {
494 package
495 .move_package()
496 .serialized_module_map()
497 .get(module_id.name().as_str())
498 .cloned()
499 }))
500 }
501}
502
503impl ObjectStore for PersistedStore {
504 fn get_object(
505 &self,
506 object_id: &ObjectID,
507 ) -> Result<Option<Object>, iota_types::storage::error::Error> {
508 Ok(SimulatorStore::get_object(self, object_id))
509 }
510
511 fn get_object_by_key(
512 &self,
513 object_id: &ObjectID,
514 version: iota_types::base_types::VersionNumber,
515 ) -> Result<Option<Object>, iota_types::storage::error::Error> {
516 Ok(self.get_object_at_version(object_id, version))
517 }
518}
519
520impl ObjectStore for PersistedStoreInnerReadOnlyWrapper {
521 fn get_object(
522 &self,
523 object_id: &ObjectID,
524 ) -> iota_types::storage::error::Result<Option<Object>> {
525 self.sync();
526
527 self.inner
528 .live_objects
529 .get(object_id)
530 .expect("Fatal: DB read failed")
531 .map(|version| self.get_object_by_key(object_id, version))
532 .transpose()
533 .map(|f| f.flatten())
534 }
535
536 fn get_object_by_key(
537 &self,
538 object_id: &ObjectID,
539 version: VersionNumber,
540 ) -> iota_types::storage::error::Result<Option<Object>> {
541 self.sync();
542
543 Ok(self
544 .inner
545 .objects
546 .get(object_id)
547 .expect("Fatal: DB read failed")
548 .and_then(|x| x.get(&version).cloned()))
549 }
550}
551
552impl ReadStore for PersistedStoreInnerReadOnlyWrapper {
553 fn get_committee(
554 &self,
555 _epoch: EpochId,
556 ) -> iota_types::storage::error::Result<Option<std::sync::Arc<Committee>>> {
557 todo!()
558 }
559
560 fn get_latest_checkpoint(&self) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
561 self.sync();
562 self.inner
563 .checkpoints
564 .unbounded_iter()
565 .skip_to_last()
566 .next()
567 .map(|(_, checkpoint)| checkpoint.into())
568 .ok_or(IotaError::UserInput {
569 error: UserInputError::LatestCheckpointSequenceNumberNotFound,
570 })
571 .map_err(iota_types::storage::error::Error::custom)
572 }
573
574 fn get_highest_verified_checkpoint(
575 &self,
576 ) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
577 todo!()
578 }
579
580 fn get_highest_synced_checkpoint(
581 &self,
582 ) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
583 todo!()
584 }
585
586 fn get_lowest_available_checkpoint(
587 &self,
588 ) -> iota_types::storage::error::Result<CheckpointSequenceNumber> {
589 Ok(0)
590 }
591
592 fn get_checkpoint_by_digest(
593 &self,
594 _digest: &CheckpointDigest,
595 ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
596 todo!()
597 }
598
599 fn get_checkpoint_by_sequence_number(
600 &self,
601 sequence_number: CheckpointSequenceNumber,
602 ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
603 self.sync();
604 Ok(self
605 .inner
606 .checkpoints
607 .get(&sequence_number)
608 .expect("Fatal: DB read failed")
609 .map(|checkpoint| checkpoint.into()))
610 }
611
612 fn get_checkpoint_contents_by_digest(
613 &self,
614 digest: &CheckpointContentsDigest,
615 ) -> iota_types::storage::error::Result<Option<CheckpointContents>> {
616 self.sync();
617
618 Ok(self
619 .inner
620 .checkpoint_contents
621 .get(digest)
622 .expect("Fatal: DB read failed"))
623 }
624
625 fn get_checkpoint_contents_by_sequence_number(
626 &self,
627 _sequence_number: CheckpointSequenceNumber,
628 ) -> iota_types::storage::error::Result<Option<CheckpointContents>> {
629 todo!()
630 }
631
632 fn get_transaction(
633 &self,
634 tx_digest: &TransactionDigest,
635 ) -> iota_types::storage::error::Result<Option<Arc<VerifiedTransaction>>> {
636 self.sync();
637
638 Ok(self
639 .inner
640 .transactions
641 .get(tx_digest)
642 .expect("Fatal: DB read failed")
643 .map(|transaction| Arc::new(transaction.into())))
644 }
645
646 fn get_transaction_effects(
647 &self,
648 tx_digest: &TransactionDigest,
649 ) -> iota_types::storage::error::Result<Option<TransactionEffects>> {
650 self.sync();
651
652 Ok(self
653 .inner
654 .effects
655 .get(tx_digest)
656 .expect("Fatal: DB read failed"))
657 }
658
659 fn get_events(
660 &self,
661 event_digest: &TransactionEventsDigest,
662 ) -> iota_types::storage::error::Result<Option<TransactionEvents>> {
663 self.sync();
664
665 Ok(self
666 .inner
667 .events
668 .get(event_digest)
669 .expect("Fatal: DB read failed"))
670 }
671
672 fn get_full_checkpoint_contents_by_sequence_number(
673 &self,
674 _sequence_number: CheckpointSequenceNumber,
675 ) -> iota_types::storage::error::Result<
676 Option<iota_types::messages_checkpoint::FullCheckpointContents>,
677 > {
678 todo!()
679 }
680
681 fn get_full_checkpoint_contents(
682 &self,
683 _digest: &CheckpointContentsDigest,
684 ) -> iota_types::storage::error::Result<
685 Option<iota_types::messages_checkpoint::FullCheckpointContents>,
686 > {
687 todo!()
688 }
689}
690
691impl RestStateReader for PersistedStoreInnerReadOnlyWrapper {
692 fn get_transaction_checkpoint(
693 &self,
694 _digest: &TransactionDigest,
695 ) -> iota_types::storage::error::Result<Option<CheckpointSequenceNumber>> {
696 todo!()
697 }
698
699 fn get_lowest_available_checkpoint_objects(
700 &self,
701 ) -> iota_types::storage::error::Result<CheckpointSequenceNumber> {
702 Ok(0)
703 }
704
705 fn get_chain_identifier(
706 &self,
707 ) -> iota_types::storage::error::Result<iota_types::digests::ChainIdentifier> {
708 Ok((*self
709 .get_checkpoint_by_sequence_number(0)
710 .unwrap()
711 .unwrap()
712 .digest())
713 .into())
714 }
715
716 fn account_owned_objects_info_iter(
717 &self,
718 _owner: IotaAddress,
719 _cursor: Option<ObjectID>,
720 ) -> iota_types::storage::error::Result<
721 Box<dyn Iterator<Item = iota_types::storage::AccountOwnedObjectInfo> + '_>,
722 > {
723 todo!()
724 }
725
726 fn dynamic_field_iter(
727 &self,
728 _parent: ObjectID,
729 _cursor: Option<ObjectID>,
730 ) -> iota_types::storage::error::Result<
731 Box<
732 dyn Iterator<
733 Item = (
734 iota_types::storage::DynamicFieldKey,
735 iota_types::storage::DynamicFieldIndexInfo,
736 ),
737 > + '_,
738 >,
739 > {
740 todo!()
741 }
742
743 fn get_coin_info(
744 &self,
745 _coin_type: &StructTag,
746 ) -> iota_types::storage::error::Result<Option<iota_types::storage::CoinInfo>> {
747 todo!()
748 }
749
750 fn get_epoch_last_checkpoint(
751 &self,
752 _epoch_id: EpochId,
753 ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
754 todo!()
755 }
756}
757
758impl PersistedStoreInnerReadOnlyWrapper {
759 pub fn sync(&self) {
760 self.inner
761 .try_catch_up_with_primary_all()
762 .expect("Fatal: DB sync failed");
763 }
764}
765
766impl Clone for PersistedStoreInnerReadOnlyWrapper {
767 fn clone(&self) -> Self {
768 let samp: SamplingInterval = SamplingInterval::new(Duration::from_secs(60), 0);
769 PersistedStoreInnerReadOnlyWrapper {
770 path: self.path.clone(),
771 inner: PersistedStoreInner::get_read_only_handle(
772 self.path.clone(),
773 None,
774 None,
775 MetricConf::new("persisted_readonly").with_sampling(samp),
776 ),
777 }
778 }
779}
780
781#[cfg(test)]
782mod tests {
783 use rand::{SeedableRng, rngs::StdRng};
784
785 use super::*;
786
787 #[tokio::test]
788 async fn deterministic_genesis() {
789 let rng = StdRng::from_seed([9; 32]);
790 let chain1 = PersistedStore::new_sim_with_protocol_version_and_accounts(
791 rng,
792 0,
793 ProtocolVersion::MAX,
794 vec![],
795 None,
796 );
797 let genesis_checkpoint_digest1 = *chain1
798 .store()
799 .get_checkpoint_by_sequence_number(0)
800 .unwrap()
801 .digest();
802
803 let rng = StdRng::from_seed([9; 32]);
804 let chain2 = PersistedStore::new_sim_with_protocol_version_and_accounts(
805 rng,
806 0,
807 ProtocolVersion::MAX,
808 vec![],
809 None,
810 );
811 let genesis_checkpoint_digest2 = *chain2
812 .store()
813 .get_checkpoint_by_sequence_number(0)
814 .unwrap()
815 .digest();
816
817 assert_eq!(genesis_checkpoint_digest1, genesis_checkpoint_digest2);
818
819 let rng = StdRng::from_seed([0; 32]);
821 let chain3 = PersistedStore::new_sim_with_protocol_version_and_accounts(
822 rng,
823 0,
824 ProtocolVersion::MAX,
825 vec![],
826 None,
827 );
828
829 assert_ne!(
830 chain1.store().get_committee_by_epoch(0),
831 chain3.store().get_committee_by_epoch(0),
832 );
833 }
834}