simulacrum/
lib.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! A `Simulacrum` of Sui.
5//!
6//! The word simulacrum is latin for "likeness, semblance", it is also a spell in D&D which creates
7//! a copy of a creature which then follows the player's commands and wishes. As such this crate
8//! provides the [`Simulacrum`] type which is a implementation or instantiation of a sui
9//! blockchain, one which doesn't do anything unless acted upon.
10//!
11//! [`Simulacrum`]: crate::Simulacrum
12
13use std::num::NonZeroUsize;
14use std::path::PathBuf;
15use std::sync::Arc;
16
17use anyhow::{Context, Result, anyhow, ensure};
18use fastcrypto::traits::Signer;
19use rand::rngs::OsRng;
20use sui_config::verifier_signing_config::VerifierSigningConfig;
21use sui_config::{genesis, transaction_deny_config::TransactionDenyConfig};
22use sui_framework_snapshot::load_bytecode_snapshot;
23use sui_protocol_config::ProtocolVersion;
24use sui_storage::blob::{Blob, BlobEncoding};
25use sui_swarm_config::genesis_config::AccountConfig;
26use sui_swarm_config::network_config::NetworkConfig;
27use sui_swarm_config::network_config_builder::ConfigBuilder;
28use sui_types::base_types::{AuthorityName, ObjectID, ObjectRef, SequenceNumber, VersionNumber};
29use sui_types::crypto::{AccountKeyPair, AuthoritySignature, get_account_key_pair};
30use sui_types::digests::{ChainIdentifier, ConsensusCommitDigest};
31use sui_types::effects::TransactionEffectsAPI;
32use sui_types::messages_consensus::ConsensusDeterminedVersionAssignments;
33use sui_types::object::{Object, Owner};
34use sui_types::storage::ObjectKey;
35use sui_types::storage::{ObjectStore, ReadStore, RpcStateReader};
36use sui_types::sui_system_state::epoch_start_sui_system_state::EpochStartSystemState;
37use sui_types::transaction::EndOfEpochTransactionKind;
38use sui_types::{
39    base_types::{EpochId, SuiAddress},
40    committee::Committee,
41    effects::TransactionEffects,
42    error::ExecutionError,
43    gas_coin::MIST_PER_SUI,
44    inner_temporary_store::InnerTemporaryStore,
45    messages_checkpoint::{EndOfEpochData, VerifiedCheckpoint},
46    signature::VerifyParams,
47    transaction::{Transaction, VerifiedTransaction},
48};
49
50use self::epoch_state::EpochState;
51pub use self::store::SimulatorStore;
52pub use self::store::in_mem_store::InMemoryStore;
53use self::store::in_mem_store::KeyStore;
54use sui_core::mock_checkpoint_builder::{MockCheckpointBuilder, ValidatorKeypairProvider};
55use sui_types::messages_checkpoint::{CheckpointContents, CheckpointSequenceNumber};
56use sui_types::{
57    gas_coin::GasCoin,
58    programmable_transaction_builder::ProgrammableTransactionBuilder,
59    transaction::{GasData, TransactionData, TransactionKind},
60};
61
62/// Configuration for advancing epochs in the Simulacrum.
63///
64/// Controls which special end-of-epoch transactions are created during epoch transitions.
65#[derive(Debug, Clone, Default)]
66pub struct AdvanceEpochConfig {
67    /// Controls whether a `RandomStateCreate` end-of-epoch transaction is included
68    /// (to initialise on-chain randomness for the first time).
69    pub create_random_state: bool,
70    /// Controls whether to create authenticator state.
71    pub create_authenticator_state: bool,
72    /// Controls whether to expire authenticator state.
73    pub create_authenticator_state_expire: bool,
74    /// Controls whether to create deny list state.
75    pub create_deny_list_state: bool,
76    /// Controls whether to create bridge state.
77    pub create_bridge_state: bool,
78    /// Controls whether to create bridge committee.
79    pub create_bridge_committee: bool,
80    /// When specified, loads system packages from a framework snapshot for the given
81    /// protocol version and includes them in the epoch change transaction.
82    /// This provides test stability as snapshot packages don't change when the framework is updated.
83    /// If None, no system packages are included in the epoch change transaction.
84    pub system_packages_snapshot: Option<u64>,
85}
86
87mod epoch_state;
88pub mod store;
89
90/// A `Simulacrum` of Sui.
91///
92/// This type represents a simulated instantiation of a Sui blockchain that needs to be driven
93/// manually, that is time doesn't advance and checkpoints are not formed unless explicitly
94/// requested.
95///
96/// See [module level][mod] documentation for more details.
97///
98/// [mod]: index.html
99pub struct Simulacrum<R = OsRng, Store: SimulatorStore = InMemoryStore> {
100    rng: R,
101    keystore: KeyStore,
102    #[allow(unused)]
103    genesis: genesis::Genesis,
104    store: Store,
105    checkpoint_builder: MockCheckpointBuilder,
106
107    // Epoch specific data
108    epoch_state: EpochState,
109
110    // Other
111    deny_config: TransactionDenyConfig,
112    data_ingestion_path: Option<PathBuf>,
113    verifier_signing_config: VerifierSigningConfig,
114}
115
116impl Simulacrum {
117    /// Create a new, random Simulacrum instance using an `OsRng` as the source of randomness.
118    #[allow(clippy::new_without_default)]
119    pub fn new() -> Self {
120        Self::new_with_rng(OsRng)
121    }
122}
123
124impl<R> Simulacrum<R>
125where
126    R: rand::RngCore + rand::CryptoRng,
127{
128    /// Create a new Simulacrum instance using the provided `rng`.
129    ///
130    /// This allows you to create a fully deterministic initial chainstate when a seeded rng is
131    /// used.
132    ///
133    /// ```
134    /// use simulacrum::Simulacrum;
135    /// use rand::{SeedableRng, rngs::StdRng};
136    ///
137    /// # fn main() {
138    /// let mut rng = StdRng::seed_from_u64(1);
139    /// let simulacrum = Simulacrum::new_with_rng(rng);
140    /// # }
141    /// ```
142    pub fn new_with_rng(mut rng: R) -> Self {
143        let config = ConfigBuilder::new_with_temp_dir()
144            .rng(&mut rng)
145            .with_chain_start_timestamp_ms(1)
146            .deterministic_committee_size(NonZeroUsize::new(1).unwrap())
147            .build();
148        Self::new_with_network_config_in_mem(&config, rng)
149    }
150
151    /// Create a new Simulacrum instance with a specific protocol version.
152    pub fn new_with_protocol_version(mut rng: R, protocol_version: ProtocolVersion) -> Self {
153        let config = ConfigBuilder::new_with_temp_dir()
154            .rng(&mut rng)
155            .with_chain_start_timestamp_ms(1)
156            .deterministic_committee_size(NonZeroUsize::new(1).unwrap())
157            .with_protocol_version(protocol_version)
158            .build();
159        Self::new_with_network_config_in_mem(&config, rng)
160    }
161
162    pub fn new_with_protocol_version_and_accounts(
163        mut rng: R,
164        chain_start_timestamp_ms: u64,
165        protocol_version: ProtocolVersion,
166        account_configs: Vec<AccountConfig>,
167    ) -> Self {
168        let config = ConfigBuilder::new_with_temp_dir()
169            .rng(&mut rng)
170            .with_chain_start_timestamp_ms(chain_start_timestamp_ms)
171            .deterministic_committee_size(NonZeroUsize::new(1).unwrap())
172            .with_protocol_version(protocol_version)
173            .with_accounts(account_configs)
174            .build();
175        Self::new_with_network_config_in_mem(&config, rng)
176    }
177
178    fn new_with_network_config_in_mem(config: &NetworkConfig, rng: R) -> Self {
179        let store = InMemoryStore::new(&config.genesis);
180        Self::new_with_network_config_store(config, rng, store)
181    }
182}
183
184impl<R, S: store::SimulatorStore> Simulacrum<R, S> {
185    pub fn new_with_network_config_store(config: &NetworkConfig, rng: R, store: S) -> Self {
186        let keystore = KeyStore::from_network_config(config);
187        let checkpoint_builder = MockCheckpointBuilder::new(config.genesis.checkpoint());
188
189        let genesis = &config.genesis;
190        let epoch_state = EpochState::new(genesis.sui_system_object());
191
192        Self {
193            rng,
194            keystore,
195            genesis: genesis.clone(),
196            store,
197            checkpoint_builder,
198            epoch_state,
199            deny_config: TransactionDenyConfig::default(),
200            verifier_signing_config: VerifierSigningConfig::default(),
201            data_ingestion_path: None,
202        }
203    }
204
205    /// Attempts to execute the provided Transaction.
206    ///
207    /// The provided Transaction undergoes the same types of checks that a Validator does prior to
208    /// signing and executing in the production system. Some of these checks are as follows:
209    /// - User signature is valid
210    /// - Sender owns all OwnedObject inputs
211    /// - etc
212    ///
213    /// If the above checks are successful then the transaction is immediately executed, enqueued
214    /// to be included in the next checkpoint (the next time `create_checkpoint` is called) and the
215    /// corresponding TransactionEffects are returned.
216    pub fn execute_transaction(
217        &mut self,
218        transaction: Transaction,
219    ) -> anyhow::Result<(TransactionEffects, Option<ExecutionError>)> {
220        let transaction = transaction
221            .try_into_verified_for_testing(self.epoch_state.epoch(), &VerifyParams::default())?;
222        self.execute_transaction_impl(transaction)
223    }
224
225    fn execute_transaction_impl(
226        &mut self,
227        transaction: VerifiedTransaction,
228    ) -> anyhow::Result<(TransactionEffects, Option<ExecutionError>)> {
229        let (inner_temporary_store, _, effects, execution_error_opt) =
230            self.epoch_state.execute_transaction(
231                &self.store,
232                &self.deny_config,
233                &self.verifier_signing_config,
234                &transaction,
235            )?;
236
237        let InnerTemporaryStore {
238            written, events, ..
239        } = inner_temporary_store;
240
241        self.store.insert_executed_transaction(
242            transaction.clone(),
243            effects.clone(),
244            events,
245            written,
246        );
247
248        // Insert into checkpoint builder
249        self.checkpoint_builder
250            .push_transaction(transaction, effects.clone());
251        Ok((effects, execution_error_opt.err()))
252    }
253
254    fn execute_system_transaction(
255        &mut self,
256        transaction: Transaction,
257    ) -> anyhow::Result<(TransactionEffects, Option<ExecutionError>)> {
258        let transaction = VerifiedTransaction::new_unchecked(transaction);
259        self.execute_transaction_impl(transaction)
260    }
261
262    /// Creates the next Checkpoint using the Transactions enqueued since the last checkpoint was
263    /// created.
264    pub fn create_checkpoint(&mut self) -> VerifiedCheckpoint {
265        if self.epoch_state.protocol_config().enable_accumulators() {
266            let (settlement_txns, checkpoint_height) = self
267                .checkpoint_builder
268                .get_settlement_txns(self.epoch_state.protocol_config());
269
270            // Execute settlement transactions and collect their effects
271            let mut settlement_effects = Vec::with_capacity(settlement_txns.len());
272            for txn in settlement_txns {
273                let (effects, _) = self
274                    .execute_system_transaction(txn)
275                    .expect("settlement txn cannot fail");
276                effects.status().unwrap();
277                settlement_effects.push(effects);
278            }
279
280            // Build and execute the barrier transaction using settlement effects
281            let barrier_tx = self
282                .checkpoint_builder
283                .get_barrier_tx(checkpoint_height, &settlement_effects);
284            self.execute_system_transaction(barrier_tx)
285                .expect("barrier txn cannot fail")
286                .0
287                .status()
288                .unwrap();
289        }
290
291        let committee = CommitteeWithKeys::new(&self.keystore, self.epoch_state.committee());
292        let (checkpoint, contents, _) = self
293            .checkpoint_builder
294            .build(&committee, self.store.get_clock().timestamp_ms());
295        self.store.insert_checkpoint(checkpoint.clone());
296        self.store.insert_checkpoint_contents(contents.clone());
297        self.process_data_ingestion(checkpoint.clone(), contents)
298            .unwrap();
299        checkpoint
300    }
301
302    /// Advances the clock by `duration`.
303    ///
304    /// This creates and executes a ConsensusCommitPrologue transaction which advances the chain
305    /// Clock by the provided duration.
306    pub fn advance_clock(&mut self, duration: std::time::Duration) -> TransactionEffects {
307        let epoch = self.epoch_state.epoch();
308        let round = self.epoch_state.next_consensus_round();
309        let timestamp_ms = self.store.get_clock().timestamp_ms() + duration.as_millis() as u64;
310
311        let consensus_commit_prologue_transaction =
312            VerifiedTransaction::new_consensus_commit_prologue_v3(
313                epoch,
314                round,
315                timestamp_ms,
316                ConsensusCommitDigest::default(),
317                ConsensusDeterminedVersionAssignments::empty_for_testing(),
318            );
319
320        self.execute_transaction(consensus_commit_prologue_transaction.into())
321            .expect("advancing the clock cannot fail")
322            .0
323    }
324
325    /// Advances the epoch.
326    ///
327    /// This creates and executes an EndOfEpoch transaction which advances the chain into the next
328    /// epoch. Since it is required to be the final transaction in an epoch, the final checkpoint in
329    /// the epoch is also created.
330    ///
331    /// The `config` parameter controls which special end-of-epoch transactions are created
332    /// as part of this epoch change.
333    ///
334    /// NOTE: This function does not currently support updating the protocol version
335    pub fn advance_epoch(&mut self, config: AdvanceEpochConfig) {
336        let next_epoch = self.epoch_state.epoch() + 1;
337        let next_epoch_protocol_version = self.epoch_state.protocol_version();
338        let gas_cost_summary = self.checkpoint_builder.epoch_rolling_gas_cost_summary();
339        let epoch_start_timestamp_ms = self.store.get_clock().timestamp_ms();
340
341        let next_epoch_system_package_bytes = if let Some(snapshot_version) =
342            config.system_packages_snapshot
343        {
344            let packages = match load_bytecode_snapshot(snapshot_version) {
345                Ok(snapshot_packages) => snapshot_packages,
346                Err(e) => {
347                    panic!("Failed to load bytecode snapshot for version {snapshot_version}: {e}");
348                }
349            };
350
351            packages
352                .into_iter()
353                .map(|pkg| (SequenceNumber::from(1u64), pkg.bytes, pkg.dependencies))
354                .collect()
355        } else {
356            vec![]
357        };
358
359        let mut kinds = vec![];
360
361        if config.create_random_state {
362            kinds.push(EndOfEpochTransactionKind::new_randomness_state_create());
363        }
364
365        if config.create_authenticator_state {
366            kinds.push(EndOfEpochTransactionKind::new_authenticator_state_create());
367        }
368
369        if config.create_authenticator_state_expire {
370            let current_epoch = self.epoch_state.epoch();
371            kinds.push(EndOfEpochTransactionKind::new_authenticator_state_expire(
372                current_epoch,
373                SequenceNumber::from(1),
374            ));
375        }
376
377        if config.create_deny_list_state {
378            kinds.push(EndOfEpochTransactionKind::new_deny_list_state_create());
379        }
380
381        if config.create_bridge_state {
382            // Use a default test chain identifier for bridge state creation
383            let chain_id = ChainIdentifier::default();
384            kinds.push(EndOfEpochTransactionKind::new_bridge_create(chain_id));
385        }
386
387        if config.create_bridge_committee {
388            // Use a default sequence number for bridge committee initialization
389            let bridge_version = SequenceNumber::from(1);
390            kinds.push(EndOfEpochTransactionKind::init_bridge_committee(
391                bridge_version,
392            ));
393        }
394
395        kinds.push(EndOfEpochTransactionKind::new_change_epoch(
396            next_epoch,
397            next_epoch_protocol_version,
398            gas_cost_summary.storage_cost,
399            gas_cost_summary.computation_cost,
400            gas_cost_summary.storage_rebate,
401            gas_cost_summary.non_refundable_storage_fee,
402            epoch_start_timestamp_ms,
403            next_epoch_system_package_bytes,
404        ));
405
406        let tx = VerifiedTransaction::new_end_of_epoch_transaction(kinds);
407        self.execute_transaction(tx.into())
408            .expect("advancing the epoch cannot fail");
409
410        let new_epoch_state = EpochState::new_with_protocol_config(
411            self.store.get_system_state(),
412            self.epoch_state.protocol_config().clone(),
413        );
414        let end_of_epoch_data = EndOfEpochData {
415            next_epoch_committee: new_epoch_state.committee().voting_rights.clone(),
416            next_epoch_protocol_version,
417            epoch_commitments: vec![],
418        };
419        let committee = CommitteeWithKeys::new(&self.keystore, self.epoch_state.committee());
420        let (checkpoint, contents, _) = self.checkpoint_builder.build_end_of_epoch(
421            &committee,
422            self.store.get_clock().timestamp_ms(),
423            next_epoch,
424            end_of_epoch_data,
425        );
426
427        self.store.insert_checkpoint(checkpoint.clone());
428        self.store.insert_checkpoint_contents(contents.clone());
429        self.process_data_ingestion(checkpoint, contents).unwrap();
430        self.epoch_state = new_epoch_state;
431    }
432
433    pub fn store(&self) -> &dyn SimulatorStore {
434        &self.store
435    }
436
437    pub fn keystore(&self) -> &KeyStore {
438        &self.keystore
439    }
440
441    pub fn epoch_start_state(&self) -> &EpochStartSystemState {
442        self.epoch_state.epoch_start_state()
443    }
444
445    /// Return a handle to the internally held RNG.
446    ///
447    /// Returns a handle to the RNG used to create this Simulacrum for use as a source of
448    /// randomness. Using a seeded RNG to build a Simulacrum and then utilizing the stored RNG as a
449    /// source of randomness can lead to a fully deterministic chain evolution.
450    pub fn rng(&mut self) -> &mut R {
451        &mut self.rng
452    }
453
454    /// Return the reference gas price for the current epoch
455    pub fn reference_gas_price(&self) -> u64 {
456        self.epoch_state.reference_gas_price()
457    }
458
459    /// Create a new account and credit it with `amount` gas units from a faucet account. Returns
460    /// the account, its keypair, and a reference to the gas object it was funded with.
461    ///
462    /// ```
463    /// use simulacrum::Simulacrum;
464    /// use sui_types::base_types::SuiAddress;
465    /// use sui_types::gas_coin::MIST_PER_SUI;
466    ///
467    /// # fn main() {
468    /// let mut simulacrum = Simulacrum::new();
469    /// let (account, kp, gas) = simulacrum.funded_account(MIST_PER_SUI).unwrap();
470    ///
471    /// // `account` is a fresh SuiAddress that owns a Coin<SUI> object with single SUI in it,
472    /// // referred to by `gas`.
473    /// // ...
474    /// # }
475    /// ```
476    pub fn funded_account(
477        &mut self,
478        amount: u64,
479    ) -> Result<(SuiAddress, AccountKeyPair, ObjectRef)> {
480        let (address, key) = get_account_key_pair();
481        let fx = self.request_gas(address, amount)?;
482        ensure!(fx.status().is_ok(), "Failed to request gas for account");
483
484        let gas = fx
485            .created()
486            .into_iter()
487            .find_map(|(oref, owner)| {
488                matches!(owner, Owner::AddressOwner(owner) if owner == address).then_some(oref)
489            })
490            .context("Could not find created object")?;
491
492        Ok((address, key, gas))
493    }
494
495    /// Request that `amount` Mist be sent to `address` from a faucet account.
496    ///
497    /// ```
498    /// use simulacrum::Simulacrum;
499    /// use sui_types::base_types::SuiAddress;
500    /// use sui_types::gas_coin::MIST_PER_SUI;
501    ///
502    /// # fn main() {
503    /// let mut simulacrum = Simulacrum::new();
504    /// let address = SuiAddress::generate(simulacrum.rng());
505    /// simulacrum.request_gas(address, MIST_PER_SUI).unwrap();
506    ///
507    /// // `account` now has a Coin<SUI> object with single SUI in it.
508    /// // ...
509    /// # }
510    /// ```
511    pub fn request_gas(&mut self, address: SuiAddress, amount: u64) -> Result<TransactionEffects> {
512        // For right now we'll just use the first account as the `faucet` account. We may want to
513        // explicitly cordon off the faucet account from the rest of the accounts though.
514        let (sender, key) = self.keystore().accounts().next().unwrap();
515        let object = self
516            .store()
517            .owned_objects(*sender)
518            .find(|object| {
519                object.is_gas_coin() && object.get_coin_value_unsafe() > amount + MIST_PER_SUI
520            })
521            .ok_or_else(|| {
522                anyhow!("unable to find a coin with enough to satisfy request for {amount} Mist")
523            })?;
524
525        let gas_data = sui_types::transaction::GasData {
526            payment: vec![object.compute_object_reference()],
527            owner: *sender,
528            price: self.reference_gas_price(),
529            budget: MIST_PER_SUI,
530        };
531
532        let pt = {
533            let mut builder =
534                sui_types::programmable_transaction_builder::ProgrammableTransactionBuilder::new();
535            builder.transfer_sui(address, Some(amount));
536            builder.finish()
537        };
538
539        let kind = sui_types::transaction::TransactionKind::ProgrammableTransaction(pt);
540        let tx_data =
541            sui_types::transaction::TransactionData::new_with_gas_data(kind, *sender, gas_data);
542        let tx = Transaction::from_data_and_signer(tx_data, vec![key]);
543
544        self.execute_transaction(tx).map(|x| x.0)
545    }
546
547    pub fn set_data_ingestion_path(&mut self, data_ingestion_path: PathBuf) {
548        self.data_ingestion_path = Some(data_ingestion_path);
549        let checkpoint = self.store.get_checkpoint_by_sequence_number(0).unwrap();
550        let contents = self
551            .store
552            .get_checkpoint_contents(&checkpoint.content_digest);
553        self.process_data_ingestion(checkpoint, contents.unwrap())
554            .unwrap();
555    }
556
557    pub fn override_next_checkpoint_number(&mut self, number: CheckpointSequenceNumber) {
558        let committee = CommitteeWithKeys::new(&self.keystore, self.epoch_state.committee());
559        self.checkpoint_builder
560            .override_next_checkpoint_number(number, &committee);
561    }
562
563    fn process_data_ingestion(
564        &self,
565        checkpoint: VerifiedCheckpoint,
566        checkpoint_contents: CheckpointContents,
567    ) -> anyhow::Result<()> {
568        if let Some(path) = &self.data_ingestion_path {
569            let file_name = format!("{}.chk", checkpoint.sequence_number);
570            let checkpoint_data: sui_types::full_checkpoint_content::CheckpointData = self
571                .get_checkpoint_data(checkpoint, checkpoint_contents)?
572                .into();
573            std::fs::create_dir_all(path)?;
574            let blob = Blob::encode(&checkpoint_data, BlobEncoding::Bcs)?;
575            std::fs::write(path.join(file_name), blob.to_bytes())?;
576        }
577        Ok(())
578    }
579}
580
581pub struct CommitteeWithKeys<'a> {
582    keystore: &'a KeyStore,
583    committee: &'a Committee,
584}
585
586impl<'a> CommitteeWithKeys<'a> {
587    fn new(keystore: &'a KeyStore, committee: &'a Committee) -> Self {
588        Self {
589            keystore,
590            committee,
591        }
592    }
593
594    pub fn keystore(&self) -> &KeyStore {
595        self.keystore
596    }
597}
598
599impl ValidatorKeypairProvider for CommitteeWithKeys<'_> {
600    fn get_validator_key(&self, name: &AuthorityName) -> &dyn Signer<AuthoritySignature> {
601        self.keystore.validator(name).unwrap()
602    }
603
604    fn get_committee(&self) -> &Committee {
605        self.committee
606    }
607}
608
609impl<T, V: store::SimulatorStore> ObjectStore for Simulacrum<T, V> {
610    fn get_object(&self, object_id: &ObjectID) -> Option<Object> {
611        store::SimulatorStore::get_object(&self.store, object_id)
612    }
613
614    fn get_object_by_key(&self, object_id: &ObjectID, version: VersionNumber) -> Option<Object> {
615        self.store.get_object_by_key(object_id, version)
616    }
617}
618
619impl<T, V: store::SimulatorStore> ReadStore for Simulacrum<T, V> {
620    fn get_committee(
621        &self,
622        _epoch: sui_types::committee::EpochId,
623    ) -> Option<std::sync::Arc<Committee>> {
624        todo!()
625    }
626
627    fn get_latest_checkpoint(&self) -> sui_types::storage::error::Result<VerifiedCheckpoint> {
628        Ok(self.store().get_highest_checkpint().unwrap())
629    }
630
631    fn get_latest_epoch_id(&self) -> sui_types::storage::error::Result<EpochId> {
632        Ok(self.epoch_state.epoch())
633    }
634
635    fn get_highest_verified_checkpoint(
636        &self,
637    ) -> sui_types::storage::error::Result<VerifiedCheckpoint> {
638        todo!()
639    }
640
641    fn get_highest_synced_checkpoint(
642        &self,
643    ) -> sui_types::storage::error::Result<VerifiedCheckpoint> {
644        todo!()
645    }
646
647    fn get_lowest_available_checkpoint(
648        &self,
649    ) -> sui_types::storage::error::Result<sui_types::messages_checkpoint::CheckpointSequenceNumber>
650    {
651        // TODO wire this up to the underlying sim store, for now this will work since we never
652        // prune the sim store
653        Ok(0)
654    }
655
656    fn get_checkpoint_by_digest(
657        &self,
658        digest: &sui_types::messages_checkpoint::CheckpointDigest,
659    ) -> Option<VerifiedCheckpoint> {
660        self.store().get_checkpoint_by_digest(digest)
661    }
662
663    fn get_checkpoint_by_sequence_number(
664        &self,
665        sequence_number: sui_types::messages_checkpoint::CheckpointSequenceNumber,
666    ) -> Option<VerifiedCheckpoint> {
667        self.store()
668            .get_checkpoint_by_sequence_number(sequence_number)
669    }
670
671    fn get_checkpoint_contents_by_digest(
672        &self,
673        digest: &sui_types::messages_checkpoint::CheckpointContentsDigest,
674    ) -> Option<sui_types::messages_checkpoint::CheckpointContents> {
675        self.store().get_checkpoint_contents(digest)
676    }
677
678    fn get_checkpoint_contents_by_sequence_number(
679        &self,
680        _sequence_number: sui_types::messages_checkpoint::CheckpointSequenceNumber,
681    ) -> Option<sui_types::messages_checkpoint::CheckpointContents> {
682        todo!()
683    }
684
685    fn get_transaction(
686        &self,
687        tx_digest: &sui_types::digests::TransactionDigest,
688    ) -> Option<Arc<VerifiedTransaction>> {
689        self.store().get_transaction(tx_digest).map(Arc::new)
690    }
691
692    fn get_transaction_effects(
693        &self,
694        tx_digest: &sui_types::digests::TransactionDigest,
695    ) -> Option<TransactionEffects> {
696        self.store().get_transaction_effects(tx_digest)
697    }
698
699    fn get_events(
700        &self,
701        event_digest: &sui_types::digests::TransactionDigest,
702    ) -> Option<sui_types::effects::TransactionEvents> {
703        self.store().get_transaction_events(event_digest)
704    }
705
706    fn get_full_checkpoint_contents(
707        &self,
708        _sequence_number: Option<sui_types::messages_checkpoint::CheckpointSequenceNumber>,
709        _digest: &sui_types::messages_checkpoint::CheckpointContentsDigest,
710    ) -> Option<sui_types::messages_checkpoint::VersionedFullCheckpointContents> {
711        todo!()
712    }
713
714    fn get_unchanged_loaded_runtime_objects(
715        &self,
716        _digest: &sui_types::digests::TransactionDigest,
717    ) -> Option<Vec<ObjectKey>> {
718        None
719    }
720
721    fn get_transaction_checkpoint(
722        &self,
723        _digest: &sui_types::digests::TransactionDigest,
724    ) -> Option<CheckpointSequenceNumber> {
725        None
726    }
727}
728
729impl<T: Send + Sync, V: store::SimulatorStore + Send + Sync> RpcStateReader for Simulacrum<T, V> {
730    fn get_lowest_available_checkpoint_objects(
731        &self,
732    ) -> sui_types::storage::error::Result<CheckpointSequenceNumber> {
733        Ok(0)
734    }
735
736    fn get_chain_identifier(
737        &self,
738    ) -> sui_types::storage::error::Result<sui_types::digests::ChainIdentifier> {
739        Ok(self
740            .store()
741            .get_checkpoint_by_sequence_number(0)
742            .unwrap()
743            .digest()
744            .to_owned()
745            .into())
746    }
747
748    fn indexes(&self) -> Option<&dyn sui_types::storage::RpcIndexes> {
749        None
750    }
751
752    fn get_struct_layout(
753        &self,
754        _: &move_core_types::language_storage::StructTag,
755    ) -> sui_types::storage::error::Result<Option<move_core_types::annotated_value::MoveTypeLayout>>
756    {
757        Ok(None)
758    }
759}
760
761impl Simulacrum {
762    /// Generate a random transfer transaction.
763    /// TODO: This is here today to make it easier to write tests. But we should utilize all the
764    /// existing code for generating transactions in sui-test-transaction-builder by defining a trait
765    /// that both WalletContext and Simulacrum implement. Then we can remove this function.
766    pub fn transfer_txn(&mut self, recipient: SuiAddress) -> (Transaction, u64) {
767        let (sender, key) = self.keystore().accounts().next().unwrap();
768        let sender = *sender;
769
770        let object = self
771            .store()
772            .owned_objects(sender)
773            .find(|object| object.is_gas_coin())
774            .unwrap();
775        let gas_coin = GasCoin::try_from(&object).unwrap();
776        let transfer_amount = gas_coin.value() / 2;
777
778        let pt = {
779            let mut builder = ProgrammableTransactionBuilder::new();
780            builder.transfer_sui(recipient, Some(transfer_amount));
781            builder.finish()
782        };
783
784        let kind = TransactionKind::ProgrammableTransaction(pt);
785        let gas_data = GasData {
786            payment: vec![object.compute_object_reference()],
787            owner: sender,
788            price: self.reference_gas_price(),
789            budget: 1_000_000_000,
790        };
791        let tx_data = TransactionData::new_with_gas_data(kind, sender, gas_data);
792        let tx = Transaction::from_data_and_signer(tx_data, vec![key]);
793        (tx, transfer_amount)
794    }
795}
796
797#[cfg(test)]
798mod tests {
799    use std::time::Duration;
800
801    use rand::{SeedableRng, rngs::StdRng};
802    use sui_types::{
803        base_types::SuiAddress, effects::TransactionEffectsAPI, gas_coin::GasCoin,
804        transaction::TransactionDataAPI,
805    };
806
807    use super::*;
808
809    #[test]
810    fn deterministic_genesis() {
811        let rng = StdRng::from_seed([9; 32]);
812        let chain1 = Simulacrum::new_with_rng(rng);
813        let genesis_checkpoint_digest1 = *chain1
814            .store()
815            .get_checkpoint_by_sequence_number(0)
816            .unwrap()
817            .digest();
818
819        let rng = StdRng::from_seed([9; 32]);
820        let chain2 = Simulacrum::new_with_rng(rng);
821        let genesis_checkpoint_digest2 = *chain2
822            .store()
823            .get_checkpoint_by_sequence_number(0)
824            .unwrap()
825            .digest();
826
827        assert_eq!(genesis_checkpoint_digest1, genesis_checkpoint_digest2);
828
829        // Ensure the committees are different when using different seeds
830        let rng = StdRng::from_seed([0; 32]);
831        let chain3 = Simulacrum::new_with_rng(rng);
832
833        assert_ne!(
834            chain1.store().get_committee_by_epoch(0),
835            chain3.store().get_committee_by_epoch(0),
836        );
837    }
838
839    #[test]
840    fn simple() {
841        let steps = 10;
842        let mut chain = Simulacrum::new();
843
844        let clock = chain.store().get_clock();
845        let start_time_ms = clock.timestamp_ms();
846        println!("clock: {:#?}", clock);
847        for _ in 0..steps {
848            chain.advance_clock(Duration::from_millis(1));
849            chain.create_checkpoint();
850            let clock = chain.store().get_clock();
851            println!("clock: {:#?}", clock);
852        }
853        let end_time_ms = chain.store().get_clock().timestamp_ms();
854        assert_eq!(end_time_ms - start_time_ms, steps);
855        dbg!(chain.store().get_highest_checkpint());
856    }
857
858    #[test]
859    fn simple_epoch() {
860        let steps = 10;
861        let mut chain = Simulacrum::new();
862
863        let start_epoch = chain.store.get_highest_checkpint().unwrap().epoch;
864        for i in 0..steps {
865            chain.advance_epoch(AdvanceEpochConfig::default());
866            chain.advance_clock(Duration::from_millis(1));
867            chain.create_checkpoint();
868            println!("{i}");
869        }
870        let end_epoch = chain.store.get_highest_checkpint().unwrap().epoch;
871        assert_eq!(end_epoch - start_epoch, steps);
872        dbg!(chain.store().get_highest_checkpint());
873    }
874
875    #[test]
876    fn transfer() {
877        let mut sim = Simulacrum::new();
878        let recipient = SuiAddress::random_for_testing_only();
879        let (tx, transfer_amount) = sim.transfer_txn(recipient);
880
881        let gas_id = tx.data().transaction_data().gas_data().payment[0].0;
882        let effects = sim.execute_transaction(tx).unwrap().0;
883        let gas_summary = effects.gas_cost_summary();
884        let gas_paid = gas_summary.net_gas_usage();
885
886        assert_eq!(
887            (transfer_amount as i64 - gas_paid) as u64,
888            store::SimulatorStore::get_object(sim.store(), &gas_id)
889                .and_then(|object| GasCoin::try_from(&object).ok())
890                .unwrap()
891                .value()
892        );
893
894        assert_eq!(
895            transfer_amount,
896            sim.store()
897                .owned_objects(recipient)
898                .next()
899                .and_then(|object| GasCoin::try_from(&object).ok())
900                .unwrap()
901                .value()
902        );
903
904        let checkpoint = sim.create_checkpoint();
905
906        assert_eq!(&checkpoint.epoch_rolling_gas_cost_summary, gas_summary);
907        if sim.epoch_state.protocol_config().enable_accumulators() {
908            assert_eq!(checkpoint.network_total_transactions, 3); // genesis + 1 user txn + 1 settlement txn
909        } else {
910            assert_eq!(checkpoint.network_total_transactions, 2); // genesis + 1 user txn
911        };
912    }
913}