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::{Address, EndOfEpochTransactionKind, ObjectId, StructTag, TransactionKind};
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, 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::{GasData, Transaction, TransactionData, TransactionDataAPI, VerifiedTransaction},
61};
62use rand::rngs::OsRng;
63
64pub use self::store::{SimulatorStore, in_mem_store::InMemoryStore};
65use self::{epoch_state::EpochState, store::in_mem_store::KeyStore};
66
67/// A `Simulacrum` of IOTA.
68///
69/// This type represents a simulated instantiation of an IOTA blockchain that
70/// needs to be driven manually, that is time doesn't advance and checkpoints
71/// are not formed unless explicitly requested.
72///
73/// See [module level][mod] documentation for more details.
74///
75/// [mod]: index.html
76pub struct Simulacrum<R = OsRng, Store: SimulatorStore = InMemoryStore> {
77    // Mutable state protected by RwLock for thread-safe interior mutability
78    inner: RwLock<SimulacrumInner<R, Store>>,
79    // Immutable config - can be accessed directly
80    deny_config: TransactionDenyConfig,
81    verifier_signing_config: VerifierSigningConfig,
82}
83
84struct SimulacrumInner<R, Store: SimulatorStore> {
85    rng: R,
86    keystore: KeyStore,
87    #[expect(unused)]
88    genesis: genesis::Genesis,
89    store: Store,
90    checkpoint_builder: MockCheckpointBuilder,
91
92    // Epoch specific data
93    epoch_state: EpochState,
94    data_ingestion_path: Option<PathBuf>,
95}
96
97impl Simulacrum {
98    /// Create a new, random Simulacrum instance using an `OsRng` as the source
99    /// of randomness.
100    #[expect(clippy::new_without_default)]
101    pub fn new() -> Self {
102        Self::new_with_rng(OsRng)
103    }
104}
105
106impl<R> Simulacrum<R>
107where
108    R: rand::RngCore + rand::CryptoRng,
109{
110    /// Create a new Simulacrum instance using the provided `rng`.
111    ///
112    /// This allows you to create a fully deterministic initial chainstate when
113    /// a seeded rng is used.
114    ///
115    /// ```
116    /// use rand::{SeedableRng, rngs::StdRng};
117    /// use simulacrum::Simulacrum;
118    ///
119    /// # fn main() {
120    /// let mut rng = StdRng::seed_from_u64(1);
121    /// let simulacrum = Simulacrum::new_with_rng(rng);
122    /// # }
123    /// ```
124    pub fn new_with_rng(mut rng: R) -> Self {
125        let config = ConfigBuilder::new_with_temp_dir()
126            .rng(&mut rng)
127            .with_chain_start_timestamp_ms(1)
128            .deterministic_committee_size(NonZeroUsize::new(1).unwrap())
129            .build();
130        Self::new_with_network_config_in_mem(&config, rng)
131    }
132
133    pub fn new_with_protocol_version_and_accounts(
134        mut rng: R,
135        chain_start_timestamp_ms: u64,
136        protocol_version: ProtocolVersion,
137        account_configs: Vec<AccountConfig>,
138    ) -> Self {
139        let config = ConfigBuilder::new_with_temp_dir()
140            .rng(&mut rng)
141            .with_chain_start_timestamp_ms(chain_start_timestamp_ms)
142            .deterministic_committee_size(NonZeroUsize::new(1).unwrap())
143            .with_protocol_version(protocol_version)
144            .with_accounts(account_configs)
145            .build();
146        Self::new_with_network_config_in_mem(&config, rng)
147    }
148
149    fn new_with_network_config_in_mem(config: &NetworkConfig, rng: R) -> Self {
150        let store = InMemoryStore::new(&config.genesis);
151        Self::new_with_network_config_store(config, rng, store)
152    }
153}
154
155impl<R, S: store::SimulatorStore> Simulacrum<R, S> {
156    pub fn new_with_network_config_store(config: &NetworkConfig, rng: R, store: S) -> Self {
157        let keystore = KeyStore::from_network_config(config);
158        let checkpoint_builder = MockCheckpointBuilder::new(config.genesis.checkpoint());
159
160        let genesis = &config.genesis;
161        let epoch_state = EpochState::new(genesis.iota_system_object());
162
163        Self {
164            deny_config: TransactionDenyConfig::default(),
165            verifier_signing_config: VerifierSigningConfig::default(),
166            inner: RwLock::new(SimulacrumInner {
167                rng,
168                keystore,
169                genesis: genesis.clone(),
170                store,
171                checkpoint_builder,
172                epoch_state,
173                data_ingestion_path: None,
174            }),
175        }
176    }
177
178    /// Attempts to execute the provided Transaction.
179    ///
180    /// The provided Transaction undergoes the same types of checks that a
181    /// Validator does prior to signing and executing in the production
182    /// system. Some of these checks are as follows:
183    /// - User signature is valid
184    /// - Sender owns all OwnedObject inputs
185    /// - etc
186    ///
187    /// If the above checks are successful then the transaction is immediately
188    /// executed, enqueued to be included in the next checkpoint (the next
189    /// time `create_checkpoint` is called) and the corresponding
190    /// TransactionEffects are returned.
191    pub fn execute_transaction(
192        &self,
193        transaction: Transaction,
194    ) -> anyhow::Result<(TransactionEffects, Option<ExecutionError>)> {
195        let mut inner = self.inner.write().unwrap();
196        let transaction = transaction.try_into_verified_for_testing(&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<iota_types::transaction::SystemPackage> = vec![];
308        let kinds = vec![EndOfEpochTransactionKind::new_change_epoch_v3(
309            next_epoch,
310            next_epoch_protocol_version.as_u64(),
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_sdk_types::Address;
415    /// use iota_types::gas_coin::NANOS_PER_IOTA;
416    /// use simulacrum::Simulacrum;
417    ///
418    /// # fn main() {
419    /// let mut simulacrum = Simulacrum::new();
420    /// let address = simulacrum.with_rng(|rng| Address::generate(rng));
421    /// simulacrum.request_gas(address, NANOS_PER_IOTA).unwrap();
422    ///
423    /// // `account` now has a Coin<IOTA> object with single IOTA in it.
424    /// // ...
425    /// # }
426    /// ```
427    pub fn request_gas(&self, address: Address, amount: u64) -> Result<TransactionEffects> {
428        // For right now we'll just use the first account as the `faucet` account. We
429        // may want to explicitly cordon off the faucet account from the rest of
430        // the accounts though.
431        let (sender, key) = self.with_keystore(|keystore| -> Result<(Address, _)> {
432            let (s, k) = keystore
433                .accounts()
434                .next()
435                .ok_or_else(|| anyhow!("no accounts available in keystore"))?;
436            Ok((*s, k.copy()))
437        })?;
438
439        let object = self
440            .with_store(|store| {
441                store.owned_objects(sender).find(|object| {
442                    object.is_gas_coin()
443                        && object.get_coin_value_unchecked() > amount + NANOS_PER_IOTA
444                })
445            })
446            .ok_or_else(|| {
447                anyhow!("unable to find a coin with enough to satisfy request for {amount} Nanos")
448            })?;
449
450        let gas_data = iota_types::transaction::GasData {
451            objects: vec![object.object_ref()],
452            owner: sender,
453            price: self.reference_gas_price(),
454            budget: NANOS_PER_IOTA,
455        };
456
457        let pt = {
458            let mut builder =
459                iota_types::programmable_transaction_builder::ProgrammableTransactionBuilder::new();
460            builder.transfer_iota(address, Some(amount));
461            builder.finish()
462        };
463
464        let kind = TransactionKind::Programmable(pt);
465        let tx_data =
466            iota_types::transaction::TransactionData::new_with_gas_data(kind, sender, gas_data);
467        let tx = Transaction::from_data_and_signer(tx_data, vec![&key]);
468
469        self.execute_transaction(tx).map(|x| x.0)
470    }
471
472    pub fn set_data_ingestion_path(&self, data_ingestion_path: PathBuf) {
473        let checkpoint = {
474            let mut inner = self.inner.write().unwrap();
475            inner.data_ingestion_path = Some(data_ingestion_path);
476            let checkpoint = inner.store.get_checkpoint_by_sequence_number(0).unwrap();
477            let contents = inner
478                .store
479                .get_checkpoint_contents_by_digest(&checkpoint.content_digest);
480            (checkpoint, contents)
481        };
482        // Release lock before expensive data ingestion operation
483        if let (checkpoint, Some(contents)) = checkpoint {
484            self.process_data_ingestion(checkpoint, contents).unwrap();
485        }
486    }
487
488    /// Overrides the next checkpoint number indirectly by setting the previous
489    /// checkpoint's number to checkpoint_number - 1. This ensures the next
490    /// generated checkpoint has the exact sequence number provided. This
491    /// can be useful to generate checkpoints with specific sequence
492    /// numbers. Monotonicity of checkpoint numbers is enforced strictly.
493    pub fn override_next_checkpoint_number(&self, number: CheckpointSequenceNumber) {
494        let mut inner = self.inner.write().unwrap();
495        let committee = CommitteeWithKeys::new(&inner.keystore, inner.epoch_state.committee());
496        inner
497            .checkpoint_builder
498            .override_next_checkpoint_number(number, &committee);
499    }
500
501    /// Process data ingestion without holding the inner lock.
502    /// This version should be used when you don't already hold the lock.
503    fn process_data_ingestion(
504        &self,
505        checkpoint: VerifiedCheckpoint,
506        checkpoint_contents: CheckpointContents,
507    ) -> anyhow::Result<()> {
508        let path = self.inner.read().unwrap().data_ingestion_path.clone();
509        if let Some(data_path) = path {
510            let file_name = format!("{}.chk", checkpoint.sequence_number);
511            let checkpoint_data = self.try_get_checkpoint_data(checkpoint, checkpoint_contents)?;
512            std::fs::create_dir_all(&data_path)?;
513            let blob = Blob::encode(&checkpoint_data, BlobEncoding::Bcs)?;
514            std::fs::write(data_path.join(file_name), blob.to_bytes())?;
515        }
516        Ok(())
517    }
518}
519
520pub struct CommitteeWithKeys {
521    keystore: KeyStore,
522    committee: Committee,
523}
524
525impl CommitteeWithKeys {
526    fn new(keystore: &KeyStore, committee: &Committee) -> Self {
527        Self {
528            keystore: keystore.clone(),
529            committee: committee.clone(),
530        }
531    }
532
533    pub fn keystore(&self) -> &KeyStore {
534        &self.keystore
535    }
536}
537
538impl ValidatorKeypairProvider for CommitteeWithKeys {
539    fn get_validator_key(&self, name: &AuthorityName) -> &dyn Signer<AuthoritySignature> {
540        self.keystore.validator(name).unwrap()
541    }
542
543    fn get_committee(&self) -> &Committee {
544        &self.committee
545    }
546}
547
548impl<T, V: store::SimulatorStore> ObjectStore for Simulacrum<T, V> {
549    fn try_get_object(
550        &self,
551        object_id: &ObjectId,
552    ) -> Result<Option<Object>, iota_types::storage::error::Error> {
553        self.with_store(|store| store.try_get_object(object_id))
554    }
555
556    fn try_get_object_by_key(
557        &self,
558        object_id: &ObjectId,
559        version: VersionNumber,
560    ) -> Result<Option<Object>, iota_types::storage::error::Error> {
561        self.with_store(|store| store.try_get_object_by_key(object_id, version))
562    }
563}
564
565impl<T, V: store::SimulatorStore> ReadStore for Simulacrum<T, V> {
566    fn try_get_committee(
567        &self,
568        epoch: iota_types::committee::EpochId,
569    ) -> iota_types::storage::error::Result<Option<std::sync::Arc<Committee>>> {
570        self.with_store(|store| store.try_get_committee(epoch))
571    }
572
573    fn try_get_latest_checkpoint(&self) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
574        Ok(self.with_store(|store| store.get_highest_checkpoint().unwrap()))
575    }
576
577    fn try_get_highest_verified_checkpoint(
578        &self,
579    ) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
580        Ok(self.with_store(|store| store.get_highest_checkpoint().unwrap()))
581    }
582
583    fn try_get_highest_synced_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_lowest_available_checkpoint(
590        &self,
591    ) -> iota_types::storage::error::Result<iota_types::messages_checkpoint::CheckpointSequenceNumber>
592    {
593        // TODO wire this up to the underlying sim store, for now this will work since
594        // we never prune the sim store
595        Ok(0)
596    }
597
598    fn try_get_checkpoint_by_digest(
599        &self,
600        digest: &iota_types::messages_checkpoint::CheckpointDigest,
601    ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
602        Ok(self.with_store(|store| store.get_checkpoint_by_digest(digest)))
603    }
604
605    fn try_get_checkpoint_by_sequence_number(
606        &self,
607        sequence_number: iota_types::messages_checkpoint::CheckpointSequenceNumber,
608    ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
609        Ok(self.with_store(|store| store.get_checkpoint_by_sequence_number(sequence_number)))
610    }
611
612    fn try_get_checkpoint_contents_by_digest(
613        &self,
614        digest: &iota_types::messages_checkpoint::CheckpointContentsDigest,
615    ) -> iota_types::storage::error::Result<
616        Option<iota_types::messages_checkpoint::CheckpointContents>,
617    > {
618        Ok(self.with_store(|store| store.get_checkpoint_contents_by_digest(digest)))
619    }
620
621    fn try_get_checkpoint_contents_by_sequence_number(
622        &self,
623        sequence_number: iota_types::messages_checkpoint::CheckpointSequenceNumber,
624    ) -> iota_types::storage::error::Result<
625        Option<iota_types::messages_checkpoint::CheckpointContents>,
626    > {
627        Ok(self.with_store(|store| {
628            store
629                .get_checkpoint_by_sequence_number(sequence_number)
630                .and_then(|checkpoint| {
631                    store.get_checkpoint_contents_by_digest(&checkpoint.content_digest)
632                })
633        }))
634    }
635
636    fn try_get_transaction(
637        &self,
638        tx_digest: &iota_types::digests::TransactionDigest,
639    ) -> iota_types::storage::error::Result<Option<Arc<VerifiedTransaction>>> {
640        Ok(self.with_store(|store| store.get_transaction(tx_digest)))
641    }
642
643    fn try_get_transaction_effects(
644        &self,
645        tx_digest: &iota_types::digests::TransactionDigest,
646    ) -> iota_types::storage::error::Result<Option<TransactionEffects>> {
647        Ok(self.with_store(|store| store.get_transaction_effects(tx_digest)))
648    }
649
650    fn try_get_events(
651        &self,
652        digest: &iota_types::digests::TransactionDigest,
653    ) -> iota_types::storage::error::Result<Option<iota_types::effects::TransactionEvents>> {
654        Ok(self.with_store(|store| store.get_events(digest)))
655    }
656
657    fn try_get_full_checkpoint_contents_by_sequence_number(
658        &self,
659        sequence_number: iota_types::messages_checkpoint::CheckpointSequenceNumber,
660    ) -> iota_types::storage::error::Result<
661        Option<iota_types::messages_checkpoint::FullCheckpointContents>,
662    > {
663        self.with_store(|store| {
664            store
665                .try_get_checkpoint_by_sequence_number(sequence_number)?
666                .and_then(|chk| store.get_checkpoint_contents_by_digest(&chk.content_digest))
667                .map_or(Ok(None), |contents| {
668                    iota_types::messages_checkpoint::FullCheckpointContents::try_from_checkpoint_contents(
669                        store,
670                        contents,
671                    )
672                })
673        })
674    }
675
676    fn try_get_full_checkpoint_contents(
677        &self,
678        digest: &iota_types::messages_checkpoint::CheckpointContentsDigest,
679    ) -> iota_types::storage::error::Result<
680        Option<iota_types::messages_checkpoint::FullCheckpointContents>,
681    > {
682        self.with_store(|store| {
683            store.get_checkpoint_contents_by_digest(digest)
684            .map_or(Ok(None), |contents| {
685                iota_types::messages_checkpoint::FullCheckpointContents::try_from_checkpoint_contents(
686                    store,
687                    contents,
688                )
689            })
690        })
691    }
692}
693
694impl<T: Send + Sync, V: store::SimulatorStore + Send + Sync> GrpcStateReader for Simulacrum<T, V> {
695    fn get_lowest_available_checkpoint_objects(
696        &self,
697    ) -> iota_types::storage::error::Result<CheckpointSequenceNumber> {
698        Ok(0)
699    }
700
701    fn get_chain_identifier(
702        &self,
703    ) -> iota_types::storage::error::Result<iota_types::digests::ChainIdentifier> {
704        Ok(self
705            .with_store(|store| store.get_checkpoint_by_sequence_number(0))
706            .expect("lowest available checkpoint should exist")
707            .digest()
708            .to_owned()
709            .into())
710    }
711
712    fn get_epoch_last_checkpoint(
713        &self,
714        epoch_id: iota_types::committee::EpochId,
715    ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
716        Ok(self.with_store(|store| {
717            store
718                .get_last_checkpoint_of_epoch(epoch_id)
719                .and_then(|seq| store.get_checkpoint_by_sequence_number(seq))
720        }))
721    }
722
723    fn grpc_indexes(&self) -> Option<&dyn iota_node_storage::GrpcIndexes> {
724        Some(self)
725    }
726
727    fn get_struct_layout(
728        &self,
729        _: &StructTag,
730    ) -> iota_types::storage::error::Result<Option<move_core_types::annotated_value::MoveTypeLayout>>
731    {
732        Ok(None)
733    }
734}
735
736impl<T: Send + Sync, V: store::SimulatorStore + Send + Sync> Simulacrum<T, V> {
737    fn get_system_state_for_epoch(&self, epoch: u64) -> Option<IotaSystemState> {
738        self.with_store(|store| {
739            if let Some(historical_state) = store.get_system_state_by_epoch(epoch) {
740                return Some(historical_state.clone());
741            }
742            let current_system_state = store.get_system_state();
743            if epoch == current_system_state.epoch() {
744                return Some(current_system_state);
745            }
746            None
747        })
748    }
749}
750
751impl<T: Send + Sync, V: store::SimulatorStore + Send + Sync> GrpcIndexes for Simulacrum<T, V> {
752    fn get_epoch_info(
753        &self,
754        epoch: iota_types::committee::EpochId,
755    ) -> iota_types::storage::error::Result<Option<EpochInfo>> {
756        Ok(self.with_store(|store| {
757            let start_checkpoint_seq = if epoch != 0 {
758                store
759                    .get_last_checkpoint_of_epoch(epoch - 1)
760                    .map(|seq| Some(seq + 1))
761                    .unwrap_or(None)?
762            } else {
763                0
764            };
765
766            let start_checkpoint = store.get_checkpoint_by_sequence_number(start_checkpoint_seq)?;
767
768            let system_state = self.get_system_state_for_epoch(epoch)?;
769
770            let (end_timestamp_ms, end_checkpoint) =
771                if let Some(next_epoch_state) = self.get_system_state_for_epoch(epoch + 1) {
772                    (
773                        Some(next_epoch_state.epoch_start_timestamp_ms()),
774                        Some(
775                            store
776                                .get_last_checkpoint_of_epoch(epoch)
777                                .expect("last checkpoint of completed epoch should exist"),
778                        ),
779                    )
780                } else {
781                    (None, None)
782                };
783
784            Some(EpochInfo {
785                epoch,
786                protocol_version: system_state.protocol_version(),
787                start_timestamp_ms: start_checkpoint.data().timestamp_ms,
788                end_timestamp_ms,
789                start_checkpoint: start_checkpoint_seq,
790                end_checkpoint,
791                reference_gas_price: system_state.reference_gas_price(),
792                system_state,
793            })
794        }))
795    }
796
797    fn get_transaction_info(
798        &self,
799        digest: &TransactionDigest,
800    ) -> iota_types::storage::error::Result<Option<TransactionInfo>> {
801        Ok(self.with_store(|store| {
802            let highest_seq = store
803                .get_highest_checkpoint()
804                .map(|cp| *cp.sequence_number())?;
805
806            for seq in (0..=highest_seq).rev() {
807                if let Some(checkpoint) = store.get_checkpoint_by_sequence_number(seq) {
808                    if let Some(contents) =
809                        store.get_checkpoint_contents_by_digest(&checkpoint.content_digest)
810                    {
811                        if contents
812                            .iter()
813                            .any(|exec_digests| exec_digests.transaction == *digest)
814                        {
815                            // object_types left empty — production GrpcIndexesStore
816                            // populates this from input/output objects but that is
817                            // not needed for the simulacrum test harness.
818                            return Some(TransactionInfo {
819                                checkpoint: *checkpoint.sequence_number(),
820                                object_types: HashMap::new(),
821                            });
822                        }
823                    }
824                }
825            }
826            None
827        }))
828    }
829
830    fn account_owned_objects_info_iter(
831        &self,
832        _owner: Address,
833        _cursor: Option<&iota_types::storage::OwnedObjectCursor>,
834        _object_type: Option<StructTag>,
835    ) -> iota_types::storage::error::Result<
836        Box<dyn Iterator<Item = iota_types::storage::OwnedObjectIteratorItem> + '_>,
837    > {
838        Ok(Box::new(std::iter::empty()))
839    }
840
841    fn dynamic_field_iter(
842        &self,
843        _parent: iota_sdk_types::ObjectId,
844        _cursor: Option<iota_sdk_types::ObjectId>,
845    ) -> iota_types::storage::error::Result<
846        Box<
847            dyn Iterator<
848                    Item = Result<
849                        iota_types::storage::DynamicFieldKey,
850                        typed_store_error::TypedStoreError,
851                    >,
852                > + '_,
853        >,
854    > {
855        Ok(Box::new(std::iter::empty()))
856    }
857
858    fn get_coin_info(
859        &self,
860        _coin_type: &StructTag,
861    ) -> iota_types::storage::error::Result<Option<iota_types::storage::CoinInfo>> {
862        Ok(None)
863    }
864
865    fn package_versions_iter(
866        &self,
867        _original_package_id: iota_sdk_types::ObjectId,
868        _cursor: Option<u64>,
869    ) -> iota_types::storage::error::Result<
870        Box<dyn Iterator<Item = iota_types::storage::PackageVersionIteratorItem> + '_>,
871    > {
872        Ok(Box::new(std::iter::empty()))
873    }
874}
875
876impl Simulacrum {
877    /// Generate a random transfer transaction.
878    /// TODO: This is here today to make it easier to write tests. But we should
879    /// utilize all the existing code for generating transactions in
880    /// iota-test-transaction-builder by defining a trait
881    /// that both WalletContext and Simulacrum implement. Then we can remove
882    /// this function.
883    pub fn transfer_txn(&self, recipient: Address) -> (Transaction, u64) {
884        let (sender, key) = self.with_keystore(|keystore| {
885            let (s, k) = keystore.accounts().next().unwrap();
886            (*s, k.copy())
887        });
888
889        let (object, gas_coin_value) = self.with_store(|store| {
890            let object = store
891                .owned_objects(sender)
892                .find(|object| object.is_gas_coin())
893                .unwrap();
894            let gas_coin = GasCoin::try_from(object).unwrap();
895            (object.clone(), gas_coin.value())
896        });
897        let transfer_amount = gas_coin_value / 2;
898
899        let pt = {
900            let mut builder = ProgrammableTransactionBuilder::new();
901            builder.transfer_iota(recipient, Some(transfer_amount));
902            builder.finish()
903        };
904
905        let kind = TransactionKind::Programmable(pt);
906        let gas_data = GasData {
907            objects: vec![object.object_ref()],
908            owner: sender,
909            price: self.reference_gas_price(),
910            budget: 1_000_000_000,
911        };
912        let tx_data = TransactionData::new_with_gas_data(kind, sender, gas_data);
913        let tx = Transaction::from_data_and_signer(tx_data, vec![&key]);
914        (tx, transfer_amount)
915    }
916}
917
918#[cfg(test)]
919mod tests {
920    use std::time::Duration;
921
922    use iota_types::{
923        effects::TransactionEffectsAPI, gas_coin::GasCoin, transaction::TransactionDataAPI,
924    };
925    use rand::{SeedableRng, rngs::StdRng};
926
927    use super::*;
928
929    #[test]
930    fn deterministic_genesis() {
931        let rng = StdRng::from_seed([9; 32]);
932        let chain1 = Simulacrum::new_with_rng(rng);
933        let genesis_checkpoint_digest1 = chain1
934            .with_store(|store| *store.get_checkpoint_by_sequence_number(0).unwrap().digest());
935
936        let rng = StdRng::from_seed([9; 32]);
937        let chain2 = Simulacrum::new_with_rng(rng);
938        let genesis_checkpoint_digest2 = chain2
939            .with_store(|store| *store.get_checkpoint_by_sequence_number(0).unwrap().digest());
940
941        assert_eq!(genesis_checkpoint_digest1, genesis_checkpoint_digest2);
942
943        // Ensure the committees are different when using different seeds
944        let rng = StdRng::from_seed([0; 32]);
945        let chain3 = Simulacrum::new_with_rng(rng);
946
947        let committee1 = chain1.with_store(|store| store.get_committee_by_epoch(0).cloned());
948        let committee3 = chain3.with_store(|store| store.get_committee_by_epoch(0).cloned());
949        assert_ne!(committee1, committee3);
950    }
951
952    #[test]
953    fn simple() {
954        let steps = 10;
955        let sim = Simulacrum::new();
956
957        let start_time_ms = sim.with_store(|store| {
958            let clock = store.get_clock();
959            println!("clock: {clock:#?}");
960            clock.timestamp_ms()
961        });
962
963        for _ in 0..steps {
964            sim.advance_clock(Duration::from_millis(1));
965            sim.create_checkpoint();
966            sim.with_store(|store| {
967                let clock = store.get_clock();
968                println!("clock: {clock:#?}");
969            });
970        }
971        let end_time_ms = sim.with_store(|store| store.get_clock().timestamp_ms());
972        assert_eq!(end_time_ms - start_time_ms, steps);
973        sim.with_store(|store| {
974            dbg!(store.get_highest_checkpoint());
975        });
976    }
977
978    #[test]
979    fn simple_epoch() {
980        let steps = 10;
981        let sim = Simulacrum::new();
982
983        let start_epoch = sim.with_store(|store| store.get_highest_checkpoint().unwrap().epoch);
984        for i in 0..steps {
985            sim.advance_epoch();
986            sim.advance_clock(Duration::from_millis(1));
987            sim.create_checkpoint();
988            println!("{i}");
989        }
990        let end_epoch = sim.with_store(|store| store.get_highest_checkpoint().unwrap().epoch);
991        assert_eq!(end_epoch - start_epoch, steps);
992        sim.with_store(|store| {
993            dbg!(store.get_highest_checkpoint());
994        });
995    }
996
997    #[test]
998    fn transfer() {
999        let sim = Simulacrum::new();
1000        let recipient = Address::random();
1001        let (tx, transfer_amount) = sim.transfer_txn(recipient);
1002
1003        let gas_id = tx.data().transaction_data().gas_data().objects[0].object_id;
1004        let effects = sim.execute_transaction(tx).unwrap().0;
1005        let gas_summary = effects.gas_cost_summary();
1006        let gas_paid = gas_summary.net_gas_usage();
1007
1008        sim.with_store(|store| {
1009            assert_eq!(
1010                (transfer_amount as i64 - gas_paid) as u64,
1011                store::SimulatorStore::get_object(store, &gas_id)
1012                    .and_then(|object| GasCoin::try_from(&object).ok())
1013                    .unwrap()
1014                    .value()
1015            );
1016
1017            assert_eq!(
1018                transfer_amount,
1019                store
1020                    .owned_objects(recipient)
1021                    .next()
1022                    .and_then(|object| GasCoin::try_from(object).ok())
1023                    .unwrap()
1024                    .value()
1025            );
1026        });
1027
1028        let checkpoint = sim.create_checkpoint();
1029
1030        assert_eq!(&checkpoint.epoch_rolling_gas_cost_summary, gas_summary);
1031        assert_eq!(checkpoint.network_total_transactions, 2); // genesis + 1 txn
1032    }
1033}