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