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::{
35    Address, CheckpointContentsDigest, CheckpointDigest, ConsensusCommitDigest,
36    EndOfEpochTransactionKind, GasPayment, ObjectId, StructTag, SystemPackage, TransactionDigest,
37    TransactionKind,
38    checkpoint::{CheckpointContents, EndOfEpochData},
39};
40use iota_storage::blob::{Blob, BlobEncoding};
41use iota_swarm_config::{
42    genesis_config::AccountConfig, network_config::NetworkConfig,
43    network_config_builder::ConfigBuilder,
44};
45use iota_types::{
46    base_types::{AuthorityName, EpochId, VersionNumber},
47    committee::Committee,
48    crypto::AuthoritySignature,
49    effects::TransactionEffects,
50    error::ExecutionError,
51    gas_coin::{GasCoin, NANOS_PER_IOTA},
52    inner_temporary_store::InnerTemporaryStore,
53    iota_system_state::{
54        IotaSystemState, IotaSystemStateTrait, epoch_start_iota_system_state::EpochStartSystemState,
55    },
56    messages_checkpoint::{CheckpointContentsExt, CheckpointSequenceNumber, VerifiedCheckpoint},
57    mock_checkpoint_builder::{MockCheckpointBuilder, ValidatorKeypairProvider},
58    object::Object,
59    programmable_transaction_builder::ProgrammableTransactionBuilder,
60    signature::VerifyParams,
61    storage::{EpochInfoV2, ObjectStore, ReadStore, TransactionInfo},
62    transaction::{TransactionData, TransactionDataAPI, TransactionEnvelope, VerifiedTransaction},
63};
64use rand::rngs::OsRng;
65
66pub use self::store::{SimulatorStore, in_mem_store::InMemoryStore};
67use self::{epoch_state::EpochState, store::in_mem_store::KeyStore};
68
69/// A `Simulacrum` of IOTA.
70///
71/// This type represents a simulated instantiation of an IOTA blockchain that
72/// needs to be driven manually, that is time doesn't advance and checkpoints
73/// are not formed unless explicitly requested.
74///
75/// See [module level][mod] documentation for more details.
76///
77/// [mod]: index.html
78pub struct Simulacrum<R = OsRng, Store: SimulatorStore = InMemoryStore> {
79    // Mutable state protected by RwLock for thread-safe interior mutability
80    inner: RwLock<SimulacrumInner<R, Store>>,
81    // Immutable config - can be accessed directly
82    deny_config: TransactionDenyConfig,
83    verifier_signing_config: VerifierSigningConfig,
84}
85
86struct SimulacrumInner<R, Store: SimulatorStore> {
87    rng: R,
88    keystore: KeyStore,
89    #[expect(unused)]
90    genesis: genesis::Genesis,
91    store: Store,
92    checkpoint_builder: MockCheckpointBuilder,
93
94    // Epoch specific data
95    epoch_state: EpochState,
96    data_ingestion_path: Option<PathBuf>,
97}
98
99impl Simulacrum {
100    /// Create a new, random Simulacrum instance using an `OsRng` as the source
101    /// of randomness.
102    #[expect(clippy::new_without_default)]
103    pub fn new() -> Self {
104        Self::new_with_rng(OsRng)
105    }
106}
107
108impl<R> Simulacrum<R>
109where
110    R: rand::RngCore + rand::CryptoRng,
111{
112    /// Create a new Simulacrum instance using the provided `rng`.
113    ///
114    /// This allows you to create a fully deterministic initial chainstate when
115    /// a seeded rng is used.
116    ///
117    /// ```
118    /// use rand::{SeedableRng, rngs::StdRng};
119    /// use simulacrum::Simulacrum;
120    ///
121    /// # fn main() {
122    /// let mut rng = StdRng::seed_from_u64(1);
123    /// let simulacrum = Simulacrum::new_with_rng(rng);
124    /// # }
125    /// ```
126    pub fn new_with_rng(mut rng: R) -> Self {
127        let config = ConfigBuilder::new_with_temp_dir()
128            .rng(&mut rng)
129            .with_chain_start_timestamp_ms(1)
130            .deterministic_committee_size(NonZeroUsize::new(1).unwrap())
131            .build();
132        Self::new_with_network_config_in_mem(&config, rng)
133    }
134
135    pub fn new_with_protocol_version_and_accounts(
136        mut rng: R,
137        chain_start_timestamp_ms: u64,
138        protocol_version: ProtocolVersion,
139        account_configs: Vec<AccountConfig>,
140    ) -> Self {
141        let config = ConfigBuilder::new_with_temp_dir()
142            .rng(&mut rng)
143            .with_chain_start_timestamp_ms(chain_start_timestamp_ms)
144            .deterministic_committee_size(NonZeroUsize::new(1).unwrap())
145            .with_protocol_version(protocol_version)
146            .with_accounts(account_configs)
147            .build();
148        Self::new_with_network_config_in_mem(&config, rng)
149    }
150
151    fn new_with_network_config_in_mem(config: &NetworkConfig, rng: R) -> Self {
152        let store = InMemoryStore::new(&config.genesis);
153        Self::new_with_network_config_store(config, rng, store)
154    }
155}
156
157impl<R, S: store::SimulatorStore> Simulacrum<R, S> {
158    pub fn new_with_network_config_store(config: &NetworkConfig, rng: R, store: S) -> Self {
159        let keystore = KeyStore::from_network_config(config);
160        let checkpoint_builder = MockCheckpointBuilder::new(config.genesis.checkpoint());
161
162        let genesis = &config.genesis;
163        let epoch_state = EpochState::new(genesis.iota_system_object());
164
165        Self {
166            deny_config: TransactionDenyConfig::default(),
167            verifier_signing_config: VerifierSigningConfig::default(),
168            inner: RwLock::new(SimulacrumInner {
169                rng,
170                keystore,
171                genesis: genesis.clone(),
172                store,
173                checkpoint_builder,
174                epoch_state,
175                data_ingestion_path: None,
176            }),
177        }
178    }
179
180    /// Attempts to execute the provided transaction.
181    ///
182    /// The provided transaction undergoes the same types of checks that a
183    /// Validator does prior to signing and executing in the production
184    /// system. Some of these checks are as follows:
185    /// - User signature is valid
186    /// - Sender owns all OwnedObject inputs
187    /// - etc
188    ///
189    /// If the above checks are successful then the transaction is immediately
190    /// executed, enqueued to be included in the next checkpoint (the next
191    /// time `create_checkpoint` is called) and the corresponding
192    /// TransactionEffects are returned.
193    pub fn execute_transaction(
194        &self,
195        transaction: TransactionEnvelope,
196    ) -> anyhow::Result<(TransactionEffects, Option<ExecutionError>)> {
197        let mut inner = self.inner.write().unwrap();
198        let transaction = transaction.try_into_verified_for_testing(&VerifyParams::default())?;
199
200        let (inner_temporary_store, _, effects, execution_error_opt) =
201            inner.epoch_state.execute_transaction(
202                &inner.store,
203                &self.deny_config,
204                &self.verifier_signing_config,
205                &transaction,
206            )?;
207
208        let InnerTemporaryStore {
209            written, events, ..
210        } = inner_temporary_store;
211
212        inner.store.insert_executed_transaction(
213            transaction.clone(),
214            effects.clone(),
215            events,
216            written,
217        );
218
219        // Insert into checkpoint builder
220        inner
221            .checkpoint_builder
222            .push_transaction(transaction, effects.clone());
223        Ok((effects, execution_error_opt.err()))
224    }
225
226    /// Simulate a transaction without committing changes.
227    /// This is useful for testing transaction behavior without modifying state.
228    pub fn simulate_transaction(
229        &self,
230        transaction: TransactionData,
231        checks: iota_types::transaction_executor::VmChecks,
232    ) -> iota_types::error::IotaResult<iota_types::transaction_executor::SimulateTransactionResult>
233    {
234        let inner = self.inner.read().unwrap();
235        inner.epoch_state.simulate_transaction(
236            &inner.store,
237            &self.deny_config,
238            &self.verifier_signing_config,
239            transaction,
240            checks,
241        )
242    }
243
244    /// Creates the next Checkpoint using the Transactions enqueued since the
245    /// last checkpoint was created.
246    pub fn create_checkpoint(&self) -> VerifiedCheckpoint {
247        let (checkpoint, contents) = {
248            let mut inner = self.inner.write().unwrap();
249            let committee = CommitteeWithKeys::new(&inner.keystore, inner.epoch_state.committee());
250            let timestamp_ms = inner.store.get_clock().timestamp_ms();
251            let (checkpoint, contents, _) =
252                inner.checkpoint_builder.build(&committee, timestamp_ms);
253            inner.store.insert_checkpoint(checkpoint.clone());
254            inner.store.insert_checkpoint_contents(contents.clone());
255            (checkpoint, contents)
256        };
257        // Release lock before expensive data ingestion operation
258        self.process_data_ingestion(checkpoint.clone(), contents)
259            .unwrap();
260        checkpoint
261    }
262
263    /// Advances the clock by `duration`.
264    ///
265    /// This creates and executes a ConsensusCommitPrologue transaction which
266    /// advances the chain Clock by the provided duration.
267    pub fn advance_clock(&self, duration: std::time::Duration) -> TransactionEffects {
268        let mut inner = self.inner.write().unwrap();
269        let epoch = inner.epoch_state.epoch();
270        let round = inner.epoch_state.next_consensus_round();
271        let timestamp_ms = inner.store.get_clock().timestamp_ms() + duration.as_millis() as u64;
272        drop(inner);
273
274        let consensus_commit_prologue_transaction =
275            VerifiedTransaction::new_consensus_commit_prologue_v1(
276                epoch,
277                round,
278                timestamp_ms,
279                ConsensusCommitDigest::default(),
280                Vec::new(),
281            );
282
283        self.execute_transaction(consensus_commit_prologue_transaction.into())
284            .expect("advancing the clock cannot fail")
285            .0
286    }
287
288    /// Advances the epoch.
289    ///
290    /// This creates and executes an EndOfEpoch transaction which advances the
291    /// chain into the next epoch. Since it is required to be the final
292    /// transaction in an epoch, the final checkpoint in the epoch is also
293    /// created.
294    ///
295    /// NOTE: This function does not currently support updating the protocol
296    /// version or the system packages
297    pub fn advance_epoch(&self) {
298        let inner = self.inner.read().unwrap();
299        let current_epoch = inner.epoch_state.epoch();
300        let next_epoch = current_epoch + 1;
301        let next_epoch_protocol_version = inner.epoch_state.protocol_version();
302        let gas_cost_summary = inner
303            .checkpoint_builder
304            .epoch_rolling_gas_cost_summary()
305            .clone();
306        let epoch_start_timestamp_ms = inner.store.get_clock().timestamp_ms();
307        drop(inner);
308
309        let next_epoch_system_package_bytes: Vec<SystemPackage> = vec![];
310        let kinds = vec![EndOfEpochTransactionKind::new_change_epoch_v3(
311            next_epoch,
312            next_epoch_protocol_version.as_u64(),
313            gas_cost_summary.storage_cost,
314            gas_cost_summary.computation_cost,
315            gas_cost_summary.computation_cost_burned,
316            gas_cost_summary.storage_rebate,
317            gas_cost_summary.non_refundable_storage_fee,
318            epoch_start_timestamp_ms,
319            next_epoch_system_package_bytes,
320            vec![],
321        )];
322
323        let tx = VerifiedTransaction::new_end_of_epoch_transaction(kinds);
324        self.execute_transaction(tx.into())
325            .expect("advancing the epoch cannot fail");
326
327        let (checkpoint, contents, new_epoch_state) = {
328            let mut inner = self.inner.write().unwrap();
329            let new_epoch_state = EpochState::new(inner.store.get_system_state());
330            let end_of_epoch_data = EndOfEpochData {
331                next_epoch_committee: new_epoch_state.committee().committee_members(),
332                next_epoch_protocol_version: next_epoch_protocol_version.as_u64(),
333                epoch_commitments: vec![],
334                // Do not simulate supply changes for now.
335                epoch_supply_change: 0,
336            };
337            let (checkpoint, contents, _) = {
338                let committee =
339                    CommitteeWithKeys::new(&inner.keystore, inner.epoch_state.committee());
340                let timestamp_ms = inner.store.get_clock().timestamp_ms();
341                inner.checkpoint_builder.build_end_of_epoch(
342                    &committee,
343                    timestamp_ms,
344                    next_epoch,
345                    end_of_epoch_data,
346                )
347            };
348
349            inner.store.insert_checkpoint(checkpoint.clone());
350            inner.store.insert_checkpoint_contents(contents.clone());
351            inner
352                .store
353                .update_last_checkpoint_of_epoch(current_epoch, checkpoint.sequence_number());
354            (checkpoint, contents, new_epoch_state)
355        };
356
357        // Process data ingestion without holding the lock
358        self.process_data_ingestion(checkpoint, contents).unwrap();
359
360        // Finally, update the epoch state
361        let mut inner = self.inner.write().unwrap();
362        inner.epoch_state = new_epoch_state;
363    }
364
365    /// Execute a function with read access to the store.
366    ///
367    /// This provides thread-safe access to the underlying store by locking it
368    /// for the duration of the closure execution.
369    pub fn with_store<F, T>(&self, f: F) -> T
370    where
371        F: FnOnce(&S) -> T,
372    {
373        let inner = self.inner.read().unwrap();
374        f(&inner.store)
375    }
376
377    /// Execute a function with read access to the keystore.
378    ///
379    /// This provides thread-safe access to the keystore by locking it
380    /// for the duration of the closure execution.
381    pub fn with_keystore<F, T>(&self, f: F) -> T
382    where
383        F: FnOnce(&KeyStore) -> T,
384    {
385        let inner = self.inner.read().unwrap();
386        f(&inner.keystore)
387    }
388
389    pub fn epoch_start_state(&self) -> EpochStartSystemState {
390        let inner = self.inner.read().unwrap();
391        inner.epoch_state.epoch_start_state()
392    }
393
394    /// Execute a function with mutable access to the internally held RNG.
395    ///
396    /// Provides mutable access to the RNG used to create this Simulacrum for
397    /// use as a source of randomness. Using a seeded RNG to build a
398    /// Simulacrum and then utilizing the stored RNG as a source of
399    /// randomness can lead to a fully deterministic chain evolution.
400    pub fn with_rng<F, T>(&self, f: F) -> T
401    where
402        F: FnOnce(&mut R) -> T,
403    {
404        let mut inner = self.inner.write().unwrap();
405        f(&mut inner.rng)
406    }
407
408    /// Return the reference gas price for the current epoch
409    pub fn reference_gas_price(&self) -> u64 {
410        self.inner.read().unwrap().epoch_state.reference_gas_price()
411    }
412
413    /// Request that `amount` Nanos be sent to `address` from a faucet account.
414    ///
415    /// ```
416    /// use iota_sdk_types::Address;
417    /// use iota_types::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| Address::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: Address, 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<(Address, _)> {
434            let (s, k) = keystore
435                .accounts()
436                .next()
437                .ok_or_else(|| anyhow!("no accounts available in keystore"))?;
438            Ok((*s, k.clone()))
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 = GasPayment {
453            objects: vec![object.object_ref()],
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 = TransactionKind::Programmable(pt);
467        let tx_data =
468            iota_types::transaction::TransactionData::new_with_gas_data(kind, sender, gas_data);
469        let tx = TransactionEnvelope::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.contents_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_latest_epoch_id(&self) -> iota_types::storage::error::Result<EpochId> {
580        Ok(self.inner.read().unwrap().epoch_state.epoch())
581    }
582
583    fn try_get_highest_verified_checkpoint(
584        &self,
585    ) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
586        Ok(self.with_store(|store| store.get_highest_checkpoint().unwrap()))
587    }
588
589    fn try_get_highest_synced_checkpoint(
590        &self,
591    ) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
592        Ok(self.with_store(|store| store.get_highest_checkpoint().unwrap()))
593    }
594
595    fn try_get_lowest_available_checkpoint(
596        &self,
597    ) -> iota_types::storage::error::Result<iota_types::messages_checkpoint::CheckpointSequenceNumber>
598    {
599        // TODO wire this up to the underlying sim store, for now this will work since
600        // we never prune the sim store
601        Ok(0)
602    }
603
604    fn try_get_checkpoint_by_digest(
605        &self,
606        digest: &CheckpointDigest,
607    ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
608        Ok(self.with_store(|store| store.get_checkpoint_by_digest(digest)))
609    }
610
611    fn try_get_checkpoint_by_sequence_number(
612        &self,
613        sequence_number: iota_types::messages_checkpoint::CheckpointSequenceNumber,
614    ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
615        Ok(self.with_store(|store| store.get_checkpoint_by_sequence_number(sequence_number)))
616    }
617
618    fn try_get_checkpoint_contents_by_digest(
619        &self,
620        digest: &CheckpointContentsDigest,
621    ) -> iota_types::storage::error::Result<Option<CheckpointContents>> {
622        Ok(self.with_store(|store| store.get_checkpoint_contents_by_digest(digest)))
623    }
624
625    fn try_get_checkpoint_contents_by_sequence_number(
626        &self,
627        sequence_number: iota_types::messages_checkpoint::CheckpointSequenceNumber,
628    ) -> iota_types::storage::error::Result<Option<CheckpointContents>> {
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.contents_digest)
634                })
635        }))
636    }
637
638    fn try_get_transaction(
639        &self,
640        tx_digest: &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: &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: &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.contents_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: &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 get_epoch_info(
726        &self,
727        epoch: iota_types::committee::EpochId,
728    ) -> iota_types::storage::error::Result<Option<EpochInfoV2>> {
729        Ok(self.with_store(|store| {
730            let start_checkpoint_seq = if epoch != 0 {
731                store
732                    .get_last_checkpoint_of_epoch(epoch - 1)
733                    .map(|seq| Some(seq + 1))
734                    .unwrap_or(None)?
735            } else {
736                0
737            };
738
739            let start_checkpoint = store.get_checkpoint_by_sequence_number(start_checkpoint_seq)?;
740
741            let system_state = self.get_system_state_for_epoch(epoch)?;
742
743            Some(EpochInfoV2 {
744                epoch,
745                start_checkpoint: start_checkpoint_seq,
746                start_timestamp_ms: start_checkpoint.data().timestamp_ms,
747                system_state,
748                // Simulacrum doesn't build the close-of-epoch proof, so the
749                // derived `end_*` fields report `None`.
750                epoch_close_proof: None,
751            })
752        }))
753    }
754
755    fn grpc_indexes(&self) -> Option<&dyn iota_node_storage::GrpcIndexes> {
756        Some(self)
757    }
758
759    fn get_struct_layout(
760        &self,
761        _: &StructTag,
762    ) -> iota_types::storage::error::Result<Option<move_core_types::annotated_value::MoveTypeLayout>>
763    {
764        Ok(None)
765    }
766}
767
768impl<T: Send + Sync, V: store::SimulatorStore + Send + Sync> Simulacrum<T, V> {
769    fn get_system_state_for_epoch(&self, epoch: u64) -> Option<IotaSystemState> {
770        self.with_store(|store| {
771            if let Some(historical_state) = store.get_system_state_by_epoch(epoch) {
772                return Some(historical_state.clone());
773            }
774            let current_system_state = store.get_system_state();
775            if epoch == current_system_state.epoch() {
776                return Some(current_system_state);
777            }
778            None
779        })
780    }
781}
782
783impl<T: Send + Sync, V: store::SimulatorStore + Send + Sync> GrpcIndexes for Simulacrum<T, V> {
784    fn get_transaction_info(
785        &self,
786        digest: &TransactionDigest,
787    ) -> iota_types::storage::error::Result<Option<TransactionInfo>> {
788        Ok(self.with_store(|store| {
789            let highest_seq = store
790                .get_highest_checkpoint()
791                .map(|cp| cp.sequence_number())?;
792
793            for seq in (0..=highest_seq).rev() {
794                if let Some(checkpoint) = store.get_checkpoint_by_sequence_number(seq) {
795                    if let Some(contents) =
796                        store.get_checkpoint_contents_by_digest(&checkpoint.contents_digest)
797                    {
798                        if contents
799                            .iter()
800                            .any(|exec_digests| exec_digests.transaction == *digest)
801                        {
802                            // object_types left empty — production GrpcIndexesStore
803                            // populates this from input/output objects but that is
804                            // not needed for the simulacrum test harness.
805                            return Some(TransactionInfo {
806                                checkpoint: checkpoint.sequence_number(),
807                                object_types: HashMap::new(),
808                            });
809                        }
810                    }
811                }
812            }
813            None
814        }))
815    }
816
817    fn account_owned_objects_info_iter(
818        &self,
819        _owner: Address,
820        _cursor: Option<&iota_types::storage::OwnedObjectCursor>,
821        _object_type: Option<StructTag>,
822    ) -> iota_types::storage::error::Result<
823        Box<dyn Iterator<Item = iota_types::storage::OwnedObjectIteratorItem> + '_>,
824    > {
825        Ok(Box::new(std::iter::empty()))
826    }
827
828    fn dynamic_field_iter(
829        &self,
830        _parent: iota_sdk_types::ObjectId,
831        _cursor: Option<iota_sdk_types::ObjectId>,
832    ) -> iota_types::storage::error::Result<
833        Box<
834            dyn Iterator<
835                    Item = Result<
836                        iota_types::storage::DynamicFieldKey,
837                        typed_store_error::TypedStoreError,
838                    >,
839                > + '_,
840        >,
841    > {
842        Ok(Box::new(std::iter::empty()))
843    }
844
845    fn get_coin_info(
846        &self,
847        _coin_type: &StructTag,
848    ) -> iota_types::storage::error::Result<Option<iota_types::storage::CoinInfo>> {
849        Ok(None)
850    }
851
852    fn package_versions_iter(
853        &self,
854        _original_package_id: iota_sdk_types::ObjectId,
855        _cursor: Option<u64>,
856    ) -> iota_types::storage::error::Result<
857        Box<dyn Iterator<Item = iota_types::storage::PackageVersionIteratorItem> + '_>,
858    > {
859        Ok(Box::new(std::iter::empty()))
860    }
861}
862
863impl Simulacrum {
864    /// Generate a random transfer transaction.
865    /// TODO: This is here today to make it easier to write tests. But we should
866    /// utilize all the existing code for generating transactions in
867    /// iota-test-transaction-builder by defining a trait
868    /// that both WalletContext and Simulacrum implement. Then we can remove
869    /// this function.
870    pub fn transfer_txn(&self, recipient: Address) -> (TransactionEnvelope, u64) {
871        let (sender, key) = self.with_keystore(|keystore| {
872            let (s, k) = keystore.accounts().next().unwrap();
873            (*s, k.clone())
874        });
875
876        let (object, gas_coin_value) = self.with_store(|store| {
877            let object = store
878                .owned_objects(sender)
879                .find(|object| object.is_gas_coin())
880                .unwrap();
881            let gas_coin = GasCoin::try_from(object).unwrap();
882            (object.clone(), gas_coin.value())
883        });
884        let transfer_amount = gas_coin_value / 2;
885
886        let pt = {
887            let mut builder = ProgrammableTransactionBuilder::new();
888            builder.transfer_iota(recipient, Some(transfer_amount));
889            builder.finish()
890        };
891
892        let kind = TransactionKind::Programmable(pt);
893        let gas_data = GasPayment {
894            objects: vec![object.object_ref()],
895            owner: sender,
896            price: self.reference_gas_price(),
897            budget: 1_000_000_000,
898        };
899        let tx_data = TransactionData::new_with_gas_data(kind, sender, gas_data);
900        let tx = TransactionEnvelope::from_data_and_signer(tx_data, vec![&key]);
901        (tx, transfer_amount)
902    }
903}
904
905#[cfg(test)]
906mod tests {
907    use std::time::Duration;
908
909    use iota_types::{
910        effects::TransactionEffectsAPI, gas_coin::GasCoin, transaction::TransactionDataAPI,
911    };
912    use rand::{SeedableRng, rngs::StdRng};
913
914    use super::*;
915
916    #[test]
917    fn deterministic_genesis() {
918        let rng = StdRng::from_seed([9; 32]);
919        let chain1 = Simulacrum::new_with_rng(rng);
920        let genesis_checkpoint_digest1 = chain1
921            .with_store(|store| *store.get_checkpoint_by_sequence_number(0).unwrap().digest());
922
923        let rng = StdRng::from_seed([9; 32]);
924        let chain2 = Simulacrum::new_with_rng(rng);
925        let genesis_checkpoint_digest2 = chain2
926            .with_store(|store| *store.get_checkpoint_by_sequence_number(0).unwrap().digest());
927
928        assert_eq!(genesis_checkpoint_digest1, genesis_checkpoint_digest2);
929
930        // Ensure the committees are different when using different seeds
931        let rng = StdRng::from_seed([0; 32]);
932        let chain3 = Simulacrum::new_with_rng(rng);
933
934        let committee1 = chain1.with_store(|store| store.get_committee_by_epoch(0).cloned());
935        let committee3 = chain3.with_store(|store| store.get_committee_by_epoch(0).cloned());
936        assert_ne!(committee1, committee3);
937    }
938
939    #[test]
940    fn simple() {
941        let steps = 10;
942        let sim = Simulacrum::new();
943
944        let start_time_ms = sim.with_store(|store| {
945            let clock = store.get_clock();
946            println!("clock: {clock:#?}");
947            clock.timestamp_ms()
948        });
949
950        for _ in 0..steps {
951            sim.advance_clock(Duration::from_millis(1));
952            sim.create_checkpoint();
953            sim.with_store(|store| {
954                let clock = store.get_clock();
955                println!("clock: {clock:#?}");
956            });
957        }
958        let end_time_ms = sim.with_store(|store| store.get_clock().timestamp_ms());
959        assert_eq!(end_time_ms - start_time_ms, steps);
960        sim.with_store(|store| {
961            dbg!(store.get_highest_checkpoint());
962        });
963    }
964
965    #[test]
966    fn simple_epoch() {
967        let steps = 10;
968        let sim = Simulacrum::new();
969
970        let start_epoch = sim.with_store(|store| store.get_highest_checkpoint().unwrap().epoch);
971        for i in 0..steps {
972            sim.advance_epoch();
973            sim.advance_clock(Duration::from_millis(1));
974            sim.create_checkpoint();
975            println!("{i}");
976        }
977        let end_epoch = sim.with_store(|store| store.get_highest_checkpoint().unwrap().epoch);
978        assert_eq!(end_epoch - start_epoch, steps);
979        sim.with_store(|store| {
980            dbg!(store.get_highest_checkpoint());
981        });
982    }
983
984    #[test]
985    fn transfer() {
986        let sim = Simulacrum::new();
987        let recipient = Address::random();
988        let (tx, transfer_amount) = sim.transfer_txn(recipient);
989
990        let gas_id = tx.data().transaction().gas_data().objects[0].object_id;
991        let effects = sim.execute_transaction(tx).unwrap().0;
992        let gas_summary = effects.gas_cost_summary();
993        let gas_paid = gas_summary.net_gas_usage();
994
995        sim.with_store(|store| {
996            assert_eq!(
997                (transfer_amount as i64 - gas_paid) as u64,
998                store::SimulatorStore::get_object(store, &gas_id)
999                    .and_then(|object| GasCoin::try_from(&object).ok())
1000                    .unwrap()
1001                    .value()
1002            );
1003
1004            assert_eq!(
1005                transfer_amount,
1006                store
1007                    .owned_objects(recipient)
1008                    .next()
1009                    .and_then(|object| GasCoin::try_from(object).ok())
1010                    .unwrap()
1011                    .value()
1012            );
1013        });
1014
1015        let checkpoint = sim.create_checkpoint();
1016
1017        assert_eq!(&checkpoint.epoch_rolling_gas_cost_summary, gas_summary);
1018        assert_eq!(checkpoint.network_total_transactions, 2); // genesis + 1 txn
1019    }
1020}