simulacrum/
lib.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5//! A `Simulacrum` of IOTA.
6//!
7//! The word simulacrum is latin for "likeness, semblance", it is also a spell
8//! in D&D which creates a copy of a creature which then follows the player's
9//! commands and wishes. As such this crate provides the [`Simulacrum`] type
10//! which is a implementation or instantiation of a iota blockchain, one which
11//! doesn't do anything unless acted upon.
12//!
13//! [`Simulacrum`]: crate::Simulacrum
14
15mod epoch_state;
16pub mod state_reader;
17pub mod store;
18pub mod transaction_executor;
19
20use std::{
21    num::NonZeroUsize,
22    path::PathBuf,
23    sync::{Arc, RwLock},
24};
25
26use anyhow::{Result, anyhow};
27use fastcrypto::traits::Signer;
28use iota_config::{
29    genesis, transaction_deny_config::TransactionDenyConfig,
30    verifier_signing_config::VerifierSigningConfig,
31};
32use iota_protocol_config::ProtocolVersion;
33use iota_storage::blob::{Blob, BlobEncoding};
34use iota_swarm_config::{
35    genesis_config::AccountConfig, network_config::NetworkConfig,
36    network_config_builder::ConfigBuilder,
37};
38use iota_types::{
39    base_types::{AuthorityName, IotaAddress, ObjectID, VersionNumber},
40    committee::Committee,
41    crypto::{AuthoritySignature, KeypairTraits},
42    digests::ConsensusCommitDigest,
43    effects::TransactionEffects,
44    error::ExecutionError,
45    gas_coin::{GasCoin, NANOS_PER_IOTA},
46    inner_temporary_store::InnerTemporaryStore,
47    iota_system_state::epoch_start_iota_system_state::EpochStartSystemState,
48    messages_checkpoint::{
49        CheckpointContents, CheckpointSequenceNumber, EndOfEpochData, VerifiedCheckpoint,
50    },
51    mock_checkpoint_builder::{MockCheckpointBuilder, ValidatorKeypairProvider},
52    object::Object,
53    programmable_transaction_builder::ProgrammableTransactionBuilder,
54    signature::VerifyParams,
55    storage::{ObjectStore, ReadStore, RestStateReader},
56    transaction::{
57        EndOfEpochTransactionKind, GasData, Transaction, TransactionData, TransactionKind,
58        VerifiedTransaction,
59    },
60};
61use rand::rngs::OsRng;
62
63pub use self::store::{SimulatorStore, in_mem_store::InMemoryStore};
64use self::{epoch_state::EpochState, store::in_mem_store::KeyStore};
65
66/// A `Simulacrum` of IOTA.
67///
68/// This type represents a simulated instantiation of an IOTA blockchain that
69/// needs to be driven manually, that is time doesn't advance and checkpoints
70/// are not formed unless explicitly requested.
71///
72/// See [module level][mod] documentation for more details.
73///
74/// [mod]: index.html
75pub struct Simulacrum<R = OsRng, Store: SimulatorStore = InMemoryStore> {
76    // Mutable state protected by RwLock for thread-safe interior mutability
77    inner: RwLock<SimulacrumInner<R, Store>>,
78    // Immutable config - can be accessed directly
79    deny_config: TransactionDenyConfig,
80    verifier_signing_config: VerifierSigningConfig,
81}
82
83struct SimulacrumInner<R, Store: SimulatorStore> {
84    rng: R,
85    keystore: KeyStore,
86    #[expect(unused)]
87    genesis: genesis::Genesis,
88    store: Store,
89    checkpoint_builder: MockCheckpointBuilder,
90
91    // Epoch specific data
92    epoch_state: EpochState,
93    data_ingestion_path: Option<PathBuf>,
94}
95
96impl Simulacrum {
97    /// Create a new, random Simulacrum instance using an `OsRng` as the source
98    /// of randomness.
99    #[expect(clippy::new_without_default)]
100    pub fn new() -> Self {
101        Self::new_with_rng(OsRng)
102    }
103}
104
105impl<R> Simulacrum<R>
106where
107    R: rand::RngCore + rand::CryptoRng,
108{
109    /// Create a new Simulacrum instance using the provided `rng`.
110    ///
111    /// This allows you to create a fully deterministic initial chainstate when
112    /// a seeded rng is used.
113    ///
114    /// ```
115    /// use rand::{SeedableRng, rngs::StdRng};
116    /// use simulacrum::Simulacrum;
117    ///
118    /// # fn main() {
119    /// let mut rng = StdRng::seed_from_u64(1);
120    /// let simulacrum = Simulacrum::new_with_rng(rng);
121    /// # }
122    /// ```
123    pub fn new_with_rng(mut rng: R) -> Self {
124        let config = ConfigBuilder::new_with_temp_dir()
125            .rng(&mut rng)
126            .with_chain_start_timestamp_ms(1)
127            .deterministic_committee_size(NonZeroUsize::new(1).unwrap())
128            .build();
129        Self::new_with_network_config_in_mem(&config, rng)
130    }
131
132    pub fn new_with_protocol_version_and_accounts(
133        mut rng: R,
134        chain_start_timestamp_ms: u64,
135        protocol_version: ProtocolVersion,
136        account_configs: Vec<AccountConfig>,
137    ) -> Self {
138        let config = ConfigBuilder::new_with_temp_dir()
139            .rng(&mut rng)
140            .with_chain_start_timestamp_ms(chain_start_timestamp_ms)
141            .deterministic_committee_size(NonZeroUsize::new(1).unwrap())
142            .with_protocol_version(protocol_version)
143            .with_accounts(account_configs)
144            .build();
145        Self::new_with_network_config_in_mem(&config, rng)
146    }
147
148    fn new_with_network_config_in_mem(config: &NetworkConfig, rng: R) -> Self {
149        let store = InMemoryStore::new(&config.genesis);
150        Self::new_with_network_config_store(config, rng, store)
151    }
152}
153
154impl<R, S: store::SimulatorStore> Simulacrum<R, S> {
155    pub fn new_with_network_config_store(config: &NetworkConfig, rng: R, store: S) -> Self {
156        let keystore = KeyStore::from_network_config(config);
157        let checkpoint_builder = MockCheckpointBuilder::new(config.genesis.checkpoint());
158
159        let genesis = &config.genesis;
160        let epoch_state = EpochState::new(genesis.iota_system_object());
161
162        Self {
163            deny_config: TransactionDenyConfig::default(),
164            verifier_signing_config: VerifierSigningConfig::default(),
165            inner: RwLock::new(SimulacrumInner {
166                rng,
167                keystore,
168                genesis: genesis.clone(),
169                store,
170                checkpoint_builder,
171                epoch_state,
172                data_ingestion_path: None,
173            }),
174        }
175    }
176
177    /// Attempts to execute the provided Transaction.
178    ///
179    /// The provided Transaction undergoes the same types of checks that a
180    /// Validator does prior to signing and executing in the production
181    /// system. Some of these checks are as follows:
182    /// - User signature is valid
183    /// - Sender owns all OwnedObject inputs
184    /// - etc
185    ///
186    /// If the above checks are successful then the transaction is immediately
187    /// executed, enqueued to be included in the next checkpoint (the next
188    /// time `create_checkpoint` is called) and the corresponding
189    /// TransactionEffects are returned.
190    pub fn execute_transaction(
191        &self,
192        transaction: Transaction,
193    ) -> anyhow::Result<(TransactionEffects, Option<ExecutionError>)> {
194        let mut inner = self.inner.write().unwrap();
195        let transaction = transaction
196            .try_into_verified_for_testing(inner.epoch_state.epoch(), &VerifyParams::default())?;
197
198        let (inner_temporary_store, _, effects, execution_error_opt) =
199            inner.epoch_state.execute_transaction(
200                &inner.store,
201                &self.deny_config,
202                &self.verifier_signing_config,
203                &transaction,
204            )?;
205
206        let InnerTemporaryStore {
207            written, events, ..
208        } = inner_temporary_store;
209
210        inner.store.insert_executed_transaction(
211            transaction.clone(),
212            effects.clone(),
213            events,
214            written,
215        );
216
217        // Insert into checkpoint builder
218        inner
219            .checkpoint_builder
220            .push_transaction(transaction, effects.clone());
221        Ok((effects, execution_error_opt.err()))
222    }
223
224    /// Simulate a transaction without committing changes.
225    /// This is useful for testing transaction behavior without modifying state.
226    pub fn simulate_transaction(
227        &self,
228        transaction: TransactionData,
229        checks: iota_types::transaction_executor::VmChecks,
230    ) -> iota_types::error::IotaResult<iota_types::transaction_executor::SimulateTransactionResult>
231    {
232        let inner = self.inner.read().unwrap();
233        inner.epoch_state.simulate_transaction(
234            &inner.store,
235            &self.deny_config,
236            &self.verifier_signing_config,
237            transaction,
238            checks,
239        )
240    }
241
242    /// Creates the next Checkpoint using the Transactions enqueued since the
243    /// last checkpoint was created.
244    pub fn create_checkpoint(&self) -> VerifiedCheckpoint {
245        let (checkpoint, contents) = {
246            let mut inner = self.inner.write().unwrap();
247            let committee = CommitteeWithKeys::new(&inner.keystore, inner.epoch_state.committee());
248            let timestamp_ms = inner.store.get_clock().timestamp_ms();
249            let (checkpoint, contents, _) =
250                inner.checkpoint_builder.build(&committee, timestamp_ms);
251            inner.store.insert_checkpoint(checkpoint.clone());
252            inner.store.insert_checkpoint_contents(contents.clone());
253            (checkpoint, contents)
254        };
255        // Release lock before expensive data ingestion operation
256        self.process_data_ingestion(checkpoint.clone(), contents)
257            .unwrap();
258        checkpoint
259    }
260
261    /// Advances the clock by `duration`.
262    ///
263    /// This creates and executes a ConsensusCommitPrologue transaction which
264    /// advances the chain Clock by the provided duration.
265    pub fn advance_clock(&self, duration: std::time::Duration) -> TransactionEffects {
266        let mut inner = self.inner.write().unwrap();
267        let epoch = inner.epoch_state.epoch();
268        let round = inner.epoch_state.next_consensus_round();
269        let timestamp_ms = inner.store.get_clock().timestamp_ms() + duration.as_millis() as u64;
270        drop(inner);
271
272        let consensus_commit_prologue_transaction =
273            VerifiedTransaction::new_consensus_commit_prologue_v1(
274                epoch,
275                round,
276                timestamp_ms,
277                ConsensusCommitDigest::default(),
278                Vec::new(),
279            );
280
281        self.execute_transaction(consensus_commit_prologue_transaction.into())
282            .expect("advancing the clock cannot fail")
283            .0
284    }
285
286    /// Advances the epoch.
287    ///
288    /// This creates and executes an EndOfEpoch transaction which advances the
289    /// chain into the next epoch. Since it is required to be the final
290    /// transaction in an epoch, the final checkpoint in the epoch is also
291    /// created.
292    ///
293    /// NOTE: This function does not currently support updating the protocol
294    /// version or the system packages
295    pub fn advance_epoch(&self) {
296        let inner = self.inner.read().unwrap();
297        let current_epoch = inner.epoch_state.epoch();
298        let next_epoch = current_epoch + 1;
299        let next_epoch_protocol_version = inner.epoch_state.protocol_version();
300        let gas_cost_summary = inner
301            .checkpoint_builder
302            .epoch_rolling_gas_cost_summary()
303            .clone();
304        let epoch_start_timestamp_ms = inner.store.get_clock().timestamp_ms();
305        drop(inner);
306
307        let next_epoch_system_package_bytes = vec![];
308        let kinds = vec![EndOfEpochTransactionKind::new_change_epoch_v3(
309            next_epoch,
310            next_epoch_protocol_version,
311            gas_cost_summary.storage_cost,
312            gas_cost_summary.computation_cost,
313            gas_cost_summary.computation_cost_burned,
314            gas_cost_summary.storage_rebate,
315            gas_cost_summary.non_refundable_storage_fee,
316            epoch_start_timestamp_ms,
317            next_epoch_system_package_bytes,
318            vec![],
319        )];
320
321        let tx = VerifiedTransaction::new_end_of_epoch_transaction(kinds);
322        self.execute_transaction(tx.into())
323            .expect("advancing the epoch cannot fail");
324
325        let (checkpoint, contents, new_epoch_state) = {
326            let mut inner = self.inner.write().unwrap();
327            let new_epoch_state = EpochState::new(inner.store.get_system_state());
328            let end_of_epoch_data = EndOfEpochData {
329                next_epoch_committee: new_epoch_state.committee().voting_rights.clone(),
330                next_epoch_protocol_version,
331                epoch_commitments: vec![],
332                // Do not simulate supply changes for now.
333                epoch_supply_change: 0,
334            };
335            let (checkpoint, contents, _) = {
336                let committee =
337                    CommitteeWithKeys::new(&inner.keystore, inner.epoch_state.committee());
338                let timestamp_ms = inner.store.get_clock().timestamp_ms();
339                inner.checkpoint_builder.build_end_of_epoch(
340                    &committee,
341                    timestamp_ms,
342                    next_epoch,
343                    end_of_epoch_data,
344                )
345            };
346
347            inner.store.insert_checkpoint(checkpoint.clone());
348            inner.store.insert_checkpoint_contents(contents.clone());
349            inner
350                .store
351                .update_last_checkpoint_of_epoch(current_epoch, *checkpoint.sequence_number());
352            (checkpoint, contents, new_epoch_state)
353        };
354
355        // Process data ingestion without holding the lock
356        self.process_data_ingestion(checkpoint, contents).unwrap();
357
358        // Finally, update the epoch state
359        let mut inner = self.inner.write().unwrap();
360        inner.epoch_state = new_epoch_state;
361    }
362
363    /// Execute a function with read access to the store.
364    ///
365    /// This provides thread-safe access to the underlying store by locking it
366    /// for the duration of the closure execution.
367    pub fn with_store<F, T>(&self, f: F) -> T
368    where
369        F: FnOnce(&S) -> T,
370    {
371        let inner = self.inner.read().unwrap();
372        f(&inner.store)
373    }
374
375    /// Execute a function with read access to the keystore.
376    ///
377    /// This provides thread-safe access to the keystore by locking it
378    /// for the duration of the closure execution.
379    pub fn with_keystore<F, T>(&self, f: F) -> T
380    where
381        F: FnOnce(&KeyStore) -> T,
382    {
383        let inner = self.inner.read().unwrap();
384        f(&inner.keystore)
385    }
386
387    pub fn epoch_start_state(&self) -> EpochStartSystemState {
388        let inner = self.inner.read().unwrap();
389        inner.epoch_state.epoch_start_state()
390    }
391
392    /// Execute a function with mutable access to the internally held RNG.
393    ///
394    /// Provides mutable access to the RNG used to create this Simulacrum for
395    /// use as a source of randomness. Using a seeded RNG to build a
396    /// Simulacrum and then utilizing the stored RNG as a source of
397    /// randomness can lead to a fully deterministic chain evolution.
398    pub fn with_rng<F, T>(&self, f: F) -> T
399    where
400        F: FnOnce(&mut R) -> T,
401    {
402        let mut inner = self.inner.write().unwrap();
403        f(&mut inner.rng)
404    }
405
406    /// Return the reference gas price for the current epoch
407    pub fn reference_gas_price(&self) -> u64 {
408        self.inner.read().unwrap().epoch_state.reference_gas_price()
409    }
410
411    /// Request that `amount` Nanos be sent to `address` from a faucet account.
412    ///
413    /// ```
414    /// use iota_types::{base_types::IotaAddress, gas_coin::NANOS_PER_IOTA};
415    /// use simulacrum::Simulacrum;
416    ///
417    /// # fn main() {
418    /// let mut simulacrum = Simulacrum::new();
419    /// let address = simulacrum.with_rng(|rng| IotaAddress::generate(rng));
420    /// simulacrum.request_gas(address, NANOS_PER_IOTA).unwrap();
421    ///
422    /// // `account` now has a Coin<IOTA> object with single IOTA in it.
423    /// // ...
424    /// # }
425    /// ```
426    pub fn request_gas(&self, address: IotaAddress, amount: u64) -> Result<TransactionEffects> {
427        // For right now we'll just use the first account as the `faucet` account. We
428        // may want to explicitly cordon off the faucet account from the rest of
429        // the accounts though.
430        let (sender, key) = self.with_keystore(|keystore| -> Result<(IotaAddress, _)> {
431            let (s, k) = keystore
432                .accounts()
433                .next()
434                .ok_or_else(|| anyhow!("no accounts available in keystore"))?;
435            Ok((*s, k.copy()))
436        })?;
437
438        let object = self
439            .with_store(|store| {
440                store.owned_objects(sender).find(|object| {
441                    object.is_gas_coin() && object.get_coin_value_unsafe() > amount + NANOS_PER_IOTA
442                })
443            })
444            .ok_or_else(|| {
445                anyhow!("unable to find a coin with enough to satisfy request for {amount} Nanos")
446            })?;
447
448        let gas_data = iota_types::transaction::GasData {
449            payment: vec![object.compute_object_reference()],
450            owner: sender,
451            price: self.reference_gas_price(),
452            budget: NANOS_PER_IOTA,
453        };
454
455        let pt = {
456            let mut builder =
457                iota_types::programmable_transaction_builder::ProgrammableTransactionBuilder::new();
458            builder.transfer_iota(address, Some(amount));
459            builder.finish()
460        };
461
462        let kind = iota_types::transaction::TransactionKind::ProgrammableTransaction(pt);
463        let tx_data =
464            iota_types::transaction::TransactionData::new_with_gas_data(kind, sender, gas_data);
465        let tx = Transaction::from_data_and_signer(tx_data, vec![&key]);
466
467        self.execute_transaction(tx).map(|x| x.0)
468    }
469
470    pub fn set_data_ingestion_path(&self, data_ingestion_path: PathBuf) {
471        let checkpoint = {
472            let mut inner = self.inner.write().unwrap();
473            inner.data_ingestion_path = Some(data_ingestion_path);
474            let checkpoint = inner.store.get_checkpoint_by_sequence_number(0).unwrap();
475            let contents = inner
476                .store
477                .get_checkpoint_contents_by_digest(&checkpoint.content_digest);
478            (checkpoint, contents)
479        };
480        // Release lock before expensive data ingestion operation
481        if let (checkpoint, Some(contents)) = checkpoint {
482            self.process_data_ingestion(checkpoint, contents).unwrap();
483        }
484    }
485
486    /// Overrides the next checkpoint number indirectly by setting the previous
487    /// checkpoint's number to checkpoint_number - 1. This ensures the next
488    /// generated checkpoint has the exact sequence number provided. This
489    /// can be useful to generate checkpoints with specific sequence
490    /// numbers. Monotonicity of checkpoint numbers is enforced strictly.
491    pub fn override_next_checkpoint_number(&self, number: CheckpointSequenceNumber) {
492        let mut inner = self.inner.write().unwrap();
493        let committee = CommitteeWithKeys::new(&inner.keystore, inner.epoch_state.committee());
494        inner
495            .checkpoint_builder
496            .override_next_checkpoint_number(number, &committee);
497    }
498
499    /// Process data ingestion without holding the inner lock.
500    /// This version should be used when you don't already hold the lock.
501    fn process_data_ingestion(
502        &self,
503        checkpoint: VerifiedCheckpoint,
504        checkpoint_contents: CheckpointContents,
505    ) -> anyhow::Result<()> {
506        let path = self.inner.read().unwrap().data_ingestion_path.clone();
507        if let Some(data_path) = path {
508            let file_name = format!("{}.chk", checkpoint.sequence_number);
509            let checkpoint_data = self.try_get_checkpoint_data(checkpoint, checkpoint_contents)?;
510            std::fs::create_dir_all(&data_path)?;
511            let blob = Blob::encode(&checkpoint_data, BlobEncoding::Bcs)?;
512            std::fs::write(data_path.join(file_name), blob.to_bytes())?;
513        }
514        Ok(())
515    }
516}
517
518pub struct CommitteeWithKeys {
519    keystore: KeyStore,
520    committee: Committee,
521}
522
523impl CommitteeWithKeys {
524    fn new(keystore: &KeyStore, committee: &Committee) -> Self {
525        Self {
526            keystore: keystore.clone(),
527            committee: committee.clone(),
528        }
529    }
530
531    pub fn keystore(&self) -> &KeyStore {
532        &self.keystore
533    }
534}
535
536impl ValidatorKeypairProvider for CommitteeWithKeys {
537    fn get_validator_key(&self, name: &AuthorityName) -> &dyn Signer<AuthoritySignature> {
538        self.keystore.validator(name).unwrap()
539    }
540
541    fn get_committee(&self) -> &Committee {
542        &self.committee
543    }
544}
545
546impl<T, V: store::SimulatorStore> ObjectStore for Simulacrum<T, V> {
547    fn try_get_object(
548        &self,
549        object_id: &ObjectID,
550    ) -> Result<Option<Object>, iota_types::storage::error::Error> {
551        self.with_store(|store| store.try_get_object(object_id))
552    }
553
554    fn try_get_object_by_key(
555        &self,
556        object_id: &ObjectID,
557        version: VersionNumber,
558    ) -> Result<Option<Object>, iota_types::storage::error::Error> {
559        self.with_store(|store| store.try_get_object_by_key(object_id, version))
560    }
561}
562
563impl<T, V: store::SimulatorStore> ReadStore for Simulacrum<T, V> {
564    fn try_get_committee(
565        &self,
566        _epoch: iota_types::committee::EpochId,
567    ) -> iota_types::storage::error::Result<Option<std::sync::Arc<Committee>>> {
568        todo!()
569    }
570
571    fn try_get_latest_checkpoint(&self) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
572        Ok(self.with_store(|store| store.get_highest_checkpoint().unwrap()))
573    }
574
575    fn try_get_highest_verified_checkpoint(
576        &self,
577    ) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
578        Ok(self.with_store(|store| store.get_highest_checkpoint().unwrap()))
579    }
580
581    fn try_get_highest_synced_checkpoint(
582        &self,
583    ) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
584        Ok(self.with_store(|store| store.get_highest_checkpoint().unwrap()))
585    }
586
587    fn try_get_lowest_available_checkpoint(
588        &self,
589    ) -> iota_types::storage::error::Result<iota_types::messages_checkpoint::CheckpointSequenceNumber>
590    {
591        // TODO wire this up to the underlying sim store, for now this will work since
592        // we never prune the sim store
593        Ok(0)
594    }
595
596    fn try_get_checkpoint_by_digest(
597        &self,
598        digest: &iota_types::messages_checkpoint::CheckpointDigest,
599    ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
600        Ok(self.with_store(|store| store.get_checkpoint_by_digest(digest)))
601    }
602
603    fn try_get_checkpoint_by_sequence_number(
604        &self,
605        sequence_number: iota_types::messages_checkpoint::CheckpointSequenceNumber,
606    ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
607        Ok(self.with_store(|store| store.get_checkpoint_by_sequence_number(sequence_number)))
608    }
609
610    fn try_get_checkpoint_contents_by_digest(
611        &self,
612        digest: &iota_types::messages_checkpoint::CheckpointContentsDigest,
613    ) -> iota_types::storage::error::Result<
614        Option<iota_types::messages_checkpoint::CheckpointContents>,
615    > {
616        Ok(self.with_store(|store| store.get_checkpoint_contents_by_digest(digest)))
617    }
618
619    fn try_get_checkpoint_contents_by_sequence_number(
620        &self,
621        sequence_number: iota_types::messages_checkpoint::CheckpointSequenceNumber,
622    ) -> iota_types::storage::error::Result<
623        Option<iota_types::messages_checkpoint::CheckpointContents>,
624    > {
625        Ok(self.with_store(|store| {
626            store
627                .get_checkpoint_by_sequence_number(sequence_number)
628                .and_then(|checkpoint| {
629                    store.get_checkpoint_contents_by_digest(&checkpoint.content_digest)
630                })
631        }))
632    }
633
634    fn try_get_transaction(
635        &self,
636        tx_digest: &iota_types::digests::TransactionDigest,
637    ) -> iota_types::storage::error::Result<Option<Arc<VerifiedTransaction>>> {
638        Ok(self.with_store(|store| store.get_transaction(tx_digest)))
639    }
640
641    fn try_get_transaction_effects(
642        &self,
643        tx_digest: &iota_types::digests::TransactionDigest,
644    ) -> iota_types::storage::error::Result<Option<TransactionEffects>> {
645        Ok(self.with_store(|store| store.get_transaction_effects(tx_digest)))
646    }
647
648    fn try_get_events(
649        &self,
650        event_digest: &iota_types::digests::TransactionEventsDigest,
651    ) -> iota_types::storage::error::Result<Option<iota_types::effects::TransactionEvents>> {
652        Ok(self.with_store(|store| store.get_events(event_digest)))
653    }
654
655    fn try_get_full_checkpoint_contents_by_sequence_number(
656        &self,
657        sequence_number: iota_types::messages_checkpoint::CheckpointSequenceNumber,
658    ) -> iota_types::storage::error::Result<
659        Option<iota_types::messages_checkpoint::FullCheckpointContents>,
660    > {
661        self.with_store(|store| {
662            store
663                .try_get_checkpoint_by_sequence_number(sequence_number)?
664                .and_then(|chk| store.get_checkpoint_contents_by_digest(&chk.content_digest))
665                .map_or(Ok(None), |contents| {
666                    iota_types::messages_checkpoint::FullCheckpointContents::try_from_checkpoint_contents(
667                        store,
668                        contents.clone(),
669                    )
670                })
671        })
672    }
673
674    fn try_get_full_checkpoint_contents(
675        &self,
676        digest: &iota_types::messages_checkpoint::CheckpointContentsDigest,
677    ) -> iota_types::storage::error::Result<
678        Option<iota_types::messages_checkpoint::FullCheckpointContents>,
679    > {
680        self.with_store(|store| {
681            store.get_checkpoint_contents_by_digest(digest)
682            .map_or(Ok(None), |contents| {
683                iota_types::messages_checkpoint::FullCheckpointContents::try_from_checkpoint_contents(
684                    store,
685                    contents.clone(),
686                )
687            })
688        })
689    }
690}
691
692impl<T: Send + Sync, V: store::SimulatorStore + Send + Sync> RestStateReader for Simulacrum<T, V> {
693    fn get_lowest_available_checkpoint_objects(
694        &self,
695    ) -> iota_types::storage::error::Result<CheckpointSequenceNumber> {
696        Ok(0)
697    }
698
699    fn get_chain_identifier(
700        &self,
701    ) -> iota_types::storage::error::Result<iota_types::digests::ChainIdentifier> {
702        Ok(self
703            .with_store(|store| store.get_checkpoint_by_sequence_number(0))
704            .expect("lowest available checkpoint should exist")
705            .digest()
706            .to_owned()
707            .into())
708    }
709
710    fn get_epoch_last_checkpoint(
711        &self,
712        epoch_id: iota_types::committee::EpochId,
713    ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
714        Ok(self.with_store(|store| {
715            store
716                .get_last_checkpoint_of_epoch(epoch_id)
717                .and_then(|seq| store.get_checkpoint_by_sequence_number(seq))
718        }))
719    }
720
721    fn indexes(&self) -> Option<&dyn iota_types::storage::RestIndexes> {
722        None
723    }
724
725    fn get_struct_layout(
726        &self,
727        _: &move_core_types::language_storage::StructTag,
728    ) -> iota_types::storage::error::Result<Option<move_core_types::annotated_value::MoveTypeLayout>>
729    {
730        Ok(None)
731    }
732}
733
734impl Simulacrum {
735    /// Generate a random transfer transaction.
736    /// TODO: This is here today to make it easier to write tests. But we should
737    /// utilize all the existing code for generating transactions in
738    /// iota-test-transaction-builder by defining a trait
739    /// that both WalletContext and Simulacrum implement. Then we can remove
740    /// this function.
741    pub fn transfer_txn(&self, recipient: IotaAddress) -> (Transaction, u64) {
742        let (sender, key) = self.with_keystore(|keystore| {
743            let (s, k) = keystore.accounts().next().unwrap();
744            (*s, k.copy())
745        });
746
747        let (object, gas_coin_value) = self.with_store(|store| {
748            let object = store
749                .owned_objects(sender)
750                .find(|object| object.is_gas_coin())
751                .unwrap();
752            let gas_coin = GasCoin::try_from(object).unwrap();
753            (object.clone(), gas_coin.value())
754        });
755        let transfer_amount = gas_coin_value / 2;
756
757        let pt = {
758            let mut builder = ProgrammableTransactionBuilder::new();
759            builder.transfer_iota(recipient, Some(transfer_amount));
760            builder.finish()
761        };
762
763        let kind = TransactionKind::ProgrammableTransaction(pt);
764        let gas_data = GasData {
765            payment: vec![object.compute_object_reference()],
766            owner: sender,
767            price: self.reference_gas_price(),
768            budget: 1_000_000_000,
769        };
770        let tx_data = TransactionData::new_with_gas_data(kind, sender, gas_data);
771        let tx = Transaction::from_data_and_signer(tx_data, vec![&key]);
772        (tx, transfer_amount)
773    }
774}
775
776#[cfg(test)]
777mod tests {
778    use std::time::Duration;
779
780    use iota_types::{
781        base_types::IotaAddress, effects::TransactionEffectsAPI, gas_coin::GasCoin,
782        transaction::TransactionDataAPI,
783    };
784    use rand::{SeedableRng, rngs::StdRng};
785
786    use super::*;
787
788    #[test]
789    fn deterministic_genesis() {
790        let rng = StdRng::from_seed([9; 32]);
791        let chain1 = Simulacrum::new_with_rng(rng);
792        let genesis_checkpoint_digest1 = chain1
793            .with_store(|store| *store.get_checkpoint_by_sequence_number(0).unwrap().digest());
794
795        let rng = StdRng::from_seed([9; 32]);
796        let chain2 = Simulacrum::new_with_rng(rng);
797        let genesis_checkpoint_digest2 = chain2
798            .with_store(|store| *store.get_checkpoint_by_sequence_number(0).unwrap().digest());
799
800        assert_eq!(genesis_checkpoint_digest1, genesis_checkpoint_digest2);
801
802        // Ensure the committees are different when using different seeds
803        let rng = StdRng::from_seed([0; 32]);
804        let chain3 = Simulacrum::new_with_rng(rng);
805
806        let committee1 = chain1.with_store(|store| store.get_committee_by_epoch(0).cloned());
807        let committee3 = chain3.with_store(|store| store.get_committee_by_epoch(0).cloned());
808        assert_ne!(committee1, committee3);
809    }
810
811    #[test]
812    fn simple() {
813        let steps = 10;
814        let sim = Simulacrum::new();
815
816        let start_time_ms = sim.with_store(|store| {
817            let clock = store.get_clock();
818            println!("clock: {clock:#?}");
819            clock.timestamp_ms()
820        });
821
822        for _ in 0..steps {
823            sim.advance_clock(Duration::from_millis(1));
824            sim.create_checkpoint();
825            sim.with_store(|store| {
826                let clock = store.get_clock();
827                println!("clock: {clock:#?}");
828            });
829        }
830        let end_time_ms = sim.with_store(|store| store.get_clock().timestamp_ms());
831        assert_eq!(end_time_ms - start_time_ms, steps);
832        sim.with_store(|store| {
833            dbg!(store.get_highest_checkpoint());
834        });
835    }
836
837    #[test]
838    fn simple_epoch() {
839        let steps = 10;
840        let sim = Simulacrum::new();
841
842        let start_epoch = sim.with_store(|store| store.get_highest_checkpoint().unwrap().epoch);
843        for i in 0..steps {
844            sim.advance_epoch();
845            sim.advance_clock(Duration::from_millis(1));
846            sim.create_checkpoint();
847            println!("{i}");
848        }
849        let end_epoch = sim.with_store(|store| store.get_highest_checkpoint().unwrap().epoch);
850        assert_eq!(end_epoch - start_epoch, steps);
851        sim.with_store(|store| {
852            dbg!(store.get_highest_checkpoint());
853        });
854    }
855
856    #[test]
857    fn transfer() {
858        let sim = Simulacrum::new();
859        let recipient = IotaAddress::random_for_testing_only();
860        let (tx, transfer_amount) = sim.transfer_txn(recipient);
861
862        let gas_id = tx.data().transaction_data().gas_data().payment[0].0;
863        let effects = sim.execute_transaction(tx).unwrap().0;
864        let gas_summary = effects.gas_cost_summary();
865        let gas_paid = gas_summary.net_gas_usage();
866
867        sim.with_store(|store| {
868            assert_eq!(
869                (transfer_amount as i64 - gas_paid) as u64,
870                store::SimulatorStore::get_object(store, &gas_id)
871                    .and_then(|object| GasCoin::try_from(&object).ok())
872                    .unwrap()
873                    .value()
874            );
875
876            assert_eq!(
877                transfer_amount,
878                store
879                    .owned_objects(recipient)
880                    .next()
881                    .and_then(|object| GasCoin::try_from(object).ok())
882                    .unwrap()
883                    .value()
884            );
885        });
886
887        let checkpoint = sim.create_checkpoint();
888
889        assert_eq!(&checkpoint.epoch_rolling_gas_cost_summary, gas_summary);
890        assert_eq!(checkpoint.network_total_transactions, 2); // genesis + 1 txn
891    }
892}