iota_transactional_test_runner/
simulator_persisted_store.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use 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    // Checkpoint data
58    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    // Transaction data
64    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    // Committee data
70    epoch_to_committee: DBMap<(), Vec<Committee>>,
71
72    // Object data
73    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        // Ensure the committees are different when using different seeds
820        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}