1mod 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
69pub struct Simulacrum<R = OsRng, Store: SimulatorStore = InMemoryStore> {
79 inner: RwLock<SimulacrumInner<R, Store>>,
81 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_state: EpochState,
96 data_ingestion_path: Option<PathBuf>,
97}
98
99impl Simulacrum {
100 #[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 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 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 inner
222 .checkpoint_builder
223 .push_transaction(transaction, effects.clone());
224 Ok((effects, execution_error_opt.err()))
225 }
226
227 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 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 self.process_data_ingestion(checkpoint.clone(), contents)
260 .unwrap();
261 checkpoint
262 }
263
264 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 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 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 self.process_data_ingestion(checkpoint, contents).unwrap();
360
361 let mut inner = self.inner.write().unwrap();
363 inner.epoch_state = new_epoch_state;
364 }
365
366 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 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 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 pub fn reference_gas_price(&self) -> u64 {
411 self.inner.read().unwrap().epoch_state.reference_gas_price()
412 }
413
414 pub fn request_gas(&self, address: IotaAddress, amount: u64) -> Result<TransactionEffects> {
430 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 if let (checkpoint, Some(contents)) = checkpoint {
485 self.process_data_ingestion(checkpoint, contents).unwrap();
486 }
487 }
488
489 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 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 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 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 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 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); }
1035}