Skip to main content

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