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