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