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_sdk_types::StructTag;
35use iota_storage::blob::{Blob, BlobEncoding};
36use iota_swarm_config::{
37 genesis_config::AccountConfig, network_config::NetworkConfig,
38 network_config_builder::ConfigBuilder,
39};
40use iota_types::{
41 base_types::{AuthorityName, IotaAddress, ObjectID, VersionNumber},
42 committee::Committee,
43 crypto::{AuthoritySignature, KeypairTraits},
44 digests::{ConsensusCommitDigest, TransactionDigest},
45 effects::TransactionEffects,
46 error::ExecutionError,
47 gas_coin::{GasCoin, NANOS_PER_IOTA},
48 inner_temporary_store::InnerTemporaryStore,
49 iota_system_state::{
50 IotaSystemState, IotaSystemStateTrait, epoch_start_iota_system_state::EpochStartSystemState,
51 },
52 messages_checkpoint::{
53 CheckpointContents, CheckpointSequenceNumber, EndOfEpochData, VerifiedCheckpoint,
54 },
55 mock_checkpoint_builder::{MockCheckpointBuilder, ValidatorKeypairProvider},
56 object::Object,
57 programmable_transaction_builder::ProgrammableTransactionBuilder,
58 signature::VerifyParams,
59 storage::{EpochInfo, ObjectStore, ReadStore, TransactionInfo},
60 transaction::{
61 EndOfEpochTransactionKind, GasData, Transaction, TransactionData, TransactionDataAPI,
62 TransactionKind, VerifiedTransaction,
63 },
64};
65use rand::rngs::OsRng;
66
67pub use self::store::{SimulatorStore, in_mem_store::InMemoryStore};
68use self::{epoch_state::EpochState, store::in_mem_store::KeyStore};
69
70pub struct Simulacrum<R = OsRng, Store: SimulatorStore = InMemoryStore> {
80 inner: RwLock<SimulacrumInner<R, Store>>,
82 deny_config: TransactionDenyConfig,
84 verifier_signing_config: VerifierSigningConfig,
85}
86
87struct SimulacrumInner<R, Store: SimulatorStore> {
88 rng: R,
89 keystore: KeyStore,
90 #[expect(unused)]
91 genesis: genesis::Genesis,
92 store: Store,
93 checkpoint_builder: MockCheckpointBuilder,
94
95 epoch_state: EpochState,
97 data_ingestion_path: Option<PathBuf>,
98}
99
100impl Simulacrum {
101 #[expect(clippy::new_without_default)]
104 pub fn new() -> Self {
105 Self::new_with_rng(OsRng)
106 }
107}
108
109impl<R> Simulacrum<R>
110where
111 R: rand::RngCore + rand::CryptoRng,
112{
113 pub fn new_with_rng(mut rng: R) -> Self {
128 let config = ConfigBuilder::new_with_temp_dir()
129 .rng(&mut rng)
130 .with_chain_start_timestamp_ms(1)
131 .deterministic_committee_size(NonZeroUsize::new(1).unwrap())
132 .build();
133 Self::new_with_network_config_in_mem(&config, rng)
134 }
135
136 pub fn new_with_protocol_version_and_accounts(
137 mut rng: R,
138 chain_start_timestamp_ms: u64,
139 protocol_version: ProtocolVersion,
140 account_configs: Vec<AccountConfig>,
141 ) -> Self {
142 let config = ConfigBuilder::new_with_temp_dir()
143 .rng(&mut rng)
144 .with_chain_start_timestamp_ms(chain_start_timestamp_ms)
145 .deterministic_committee_size(NonZeroUsize::new(1).unwrap())
146 .with_protocol_version(protocol_version)
147 .with_accounts(account_configs)
148 .build();
149 Self::new_with_network_config_in_mem(&config, rng)
150 }
151
152 fn new_with_network_config_in_mem(config: &NetworkConfig, rng: R) -> Self {
153 let store = InMemoryStore::new(&config.genesis);
154 Self::new_with_network_config_store(config, rng, store)
155 }
156}
157
158impl<R, S: store::SimulatorStore> Simulacrum<R, S> {
159 pub fn new_with_network_config_store(config: &NetworkConfig, rng: R, store: S) -> Self {
160 let keystore = KeyStore::from_network_config(config);
161 let checkpoint_builder = MockCheckpointBuilder::new(config.genesis.checkpoint());
162
163 let genesis = &config.genesis;
164 let epoch_state = EpochState::new(genesis.iota_system_object());
165
166 Self {
167 deny_config: TransactionDenyConfig::default(),
168 verifier_signing_config: VerifierSigningConfig::default(),
169 inner: RwLock::new(SimulacrumInner {
170 rng,
171 keystore,
172 genesis: genesis.clone(),
173 store,
174 checkpoint_builder,
175 epoch_state,
176 data_ingestion_path: None,
177 }),
178 }
179 }
180
181 pub fn execute_transaction(
195 &self,
196 transaction: Transaction,
197 ) -> anyhow::Result<(TransactionEffects, Option<ExecutionError>)> {
198 let mut inner = self.inner.write().unwrap();
199 let transaction = transaction.try_into_verified_for_testing(&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<iota_types::transaction::SystemPackage> = vec![];
311 let kinds = vec![EndOfEpochTransactionKind::new_change_epoch_v3(
312 next_epoch,
313 next_epoch_protocol_version.as_u64(),
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()
445 && object.get_coin_value_unchecked() > amount + NANOS_PER_IOTA
446 })
447 })
448 .ok_or_else(|| {
449 anyhow!("unable to find a coin with enough to satisfy request for {amount} Nanos")
450 })?;
451
452 let gas_data = iota_types::transaction::GasData {
453 objects: vec![object.compute_object_reference()],
454 owner: sender,
455 price: self.reference_gas_price(),
456 budget: NANOS_PER_IOTA,
457 };
458
459 let pt = {
460 let mut builder =
461 iota_types::programmable_transaction_builder::ProgrammableTransactionBuilder::new();
462 builder.transfer_iota(address, Some(amount));
463 builder.finish()
464 };
465
466 let kind = iota_types::transaction::TransactionKind::Programmable(pt);
467 let tx_data =
468 iota_types::transaction::TransactionData::new_with_gas_data(kind, sender, gas_data);
469 let tx = Transaction::from_data_and_signer(tx_data, vec![&key]);
470
471 self.execute_transaction(tx).map(|x| x.0)
472 }
473
474 pub fn set_data_ingestion_path(&self, data_ingestion_path: PathBuf) {
475 let checkpoint = {
476 let mut inner = self.inner.write().unwrap();
477 inner.data_ingestion_path = Some(data_ingestion_path);
478 let checkpoint = inner.store.get_checkpoint_by_sequence_number(0).unwrap();
479 let contents = inner
480 .store
481 .get_checkpoint_contents_by_digest(&checkpoint.content_digest);
482 (checkpoint, contents)
483 };
484 if let (checkpoint, Some(contents)) = checkpoint {
486 self.process_data_ingestion(checkpoint, contents).unwrap();
487 }
488 }
489
490 pub fn override_next_checkpoint_number(&self, number: CheckpointSequenceNumber) {
496 let mut inner = self.inner.write().unwrap();
497 let committee = CommitteeWithKeys::new(&inner.keystore, inner.epoch_state.committee());
498 inner
499 .checkpoint_builder
500 .override_next_checkpoint_number(number, &committee);
501 }
502
503 fn process_data_ingestion(
506 &self,
507 checkpoint: VerifiedCheckpoint,
508 checkpoint_contents: CheckpointContents,
509 ) -> anyhow::Result<()> {
510 let path = self.inner.read().unwrap().data_ingestion_path.clone();
511 if let Some(data_path) = path {
512 let file_name = format!("{}.chk", checkpoint.sequence_number);
513 let checkpoint_data = self.try_get_checkpoint_data(checkpoint, checkpoint_contents)?;
514 std::fs::create_dir_all(&data_path)?;
515 let blob = Blob::encode(&checkpoint_data, BlobEncoding::Bcs)?;
516 std::fs::write(data_path.join(file_name), blob.to_bytes())?;
517 }
518 Ok(())
519 }
520}
521
522pub struct CommitteeWithKeys {
523 keystore: KeyStore,
524 committee: Committee,
525}
526
527impl CommitteeWithKeys {
528 fn new(keystore: &KeyStore, committee: &Committee) -> Self {
529 Self {
530 keystore: keystore.clone(),
531 committee: committee.clone(),
532 }
533 }
534
535 pub fn keystore(&self) -> &KeyStore {
536 &self.keystore
537 }
538}
539
540impl ValidatorKeypairProvider for CommitteeWithKeys {
541 fn get_validator_key(&self, name: &AuthorityName) -> &dyn Signer<AuthoritySignature> {
542 self.keystore.validator(name).unwrap()
543 }
544
545 fn get_committee(&self) -> &Committee {
546 &self.committee
547 }
548}
549
550impl<T, V: store::SimulatorStore> ObjectStore for Simulacrum<T, V> {
551 fn try_get_object(
552 &self,
553 object_id: &ObjectID,
554 ) -> Result<Option<Object>, iota_types::storage::error::Error> {
555 self.with_store(|store| store.try_get_object(object_id))
556 }
557
558 fn try_get_object_by_key(
559 &self,
560 object_id: &ObjectID,
561 version: VersionNumber,
562 ) -> Result<Option<Object>, iota_types::storage::error::Error> {
563 self.with_store(|store| store.try_get_object_by_key(object_id, version))
564 }
565}
566
567impl<T, V: store::SimulatorStore> ReadStore for Simulacrum<T, V> {
568 fn try_get_committee(
569 &self,
570 epoch: iota_types::committee::EpochId,
571 ) -> iota_types::storage::error::Result<Option<std::sync::Arc<Committee>>> {
572 self.with_store(|store| store.try_get_committee(epoch))
573 }
574
575 fn try_get_latest_checkpoint(&self) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
576 Ok(self.with_store(|store| store.get_highest_checkpoint().unwrap()))
577 }
578
579 fn try_get_highest_verified_checkpoint(
580 &self,
581 ) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
582 Ok(self.with_store(|store| store.get_highest_checkpoint().unwrap()))
583 }
584
585 fn try_get_highest_synced_checkpoint(
586 &self,
587 ) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
588 Ok(self.with_store(|store| store.get_highest_checkpoint().unwrap()))
589 }
590
591 fn try_get_lowest_available_checkpoint(
592 &self,
593 ) -> iota_types::storage::error::Result<iota_types::messages_checkpoint::CheckpointSequenceNumber>
594 {
595 Ok(0)
598 }
599
600 fn try_get_checkpoint_by_digest(
601 &self,
602 digest: &iota_types::messages_checkpoint::CheckpointDigest,
603 ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
604 Ok(self.with_store(|store| store.get_checkpoint_by_digest(digest)))
605 }
606
607 fn try_get_checkpoint_by_sequence_number(
608 &self,
609 sequence_number: iota_types::messages_checkpoint::CheckpointSequenceNumber,
610 ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
611 Ok(self.with_store(|store| store.get_checkpoint_by_sequence_number(sequence_number)))
612 }
613
614 fn try_get_checkpoint_contents_by_digest(
615 &self,
616 digest: &iota_types::messages_checkpoint::CheckpointContentsDigest,
617 ) -> iota_types::storage::error::Result<
618 Option<iota_types::messages_checkpoint::CheckpointContents>,
619 > {
620 Ok(self.with_store(|store| store.get_checkpoint_contents_by_digest(digest)))
621 }
622
623 fn try_get_checkpoint_contents_by_sequence_number(
624 &self,
625 sequence_number: iota_types::messages_checkpoint::CheckpointSequenceNumber,
626 ) -> iota_types::storage::error::Result<
627 Option<iota_types::messages_checkpoint::CheckpointContents>,
628 > {
629 Ok(self.with_store(|store| {
630 store
631 .get_checkpoint_by_sequence_number(sequence_number)
632 .and_then(|checkpoint| {
633 store.get_checkpoint_contents_by_digest(&checkpoint.content_digest)
634 })
635 }))
636 }
637
638 fn try_get_transaction(
639 &self,
640 tx_digest: &iota_types::digests::TransactionDigest,
641 ) -> iota_types::storage::error::Result<Option<Arc<VerifiedTransaction>>> {
642 Ok(self.with_store(|store| store.get_transaction(tx_digest)))
643 }
644
645 fn try_get_transaction_effects(
646 &self,
647 tx_digest: &iota_types::digests::TransactionDigest,
648 ) -> iota_types::storage::error::Result<Option<TransactionEffects>> {
649 Ok(self.with_store(|store| store.get_transaction_effects(tx_digest)))
650 }
651
652 fn try_get_events(
653 &self,
654 digest: &iota_types::digests::TransactionDigest,
655 ) -> iota_types::storage::error::Result<Option<iota_types::effects::TransactionEvents>> {
656 Ok(self.with_store(|store| store.get_events(digest)))
657 }
658
659 fn try_get_full_checkpoint_contents_by_sequence_number(
660 &self,
661 sequence_number: iota_types::messages_checkpoint::CheckpointSequenceNumber,
662 ) -> iota_types::storage::error::Result<
663 Option<iota_types::messages_checkpoint::FullCheckpointContents>,
664 > {
665 self.with_store(|store| {
666 store
667 .try_get_checkpoint_by_sequence_number(sequence_number)?
668 .and_then(|chk| store.get_checkpoint_contents_by_digest(&chk.content_digest))
669 .map_or(Ok(None), |contents| {
670 iota_types::messages_checkpoint::FullCheckpointContents::try_from_checkpoint_contents(
671 store,
672 contents,
673 )
674 })
675 })
676 }
677
678 fn try_get_full_checkpoint_contents(
679 &self,
680 digest: &iota_types::messages_checkpoint::CheckpointContentsDigest,
681 ) -> iota_types::storage::error::Result<
682 Option<iota_types::messages_checkpoint::FullCheckpointContents>,
683 > {
684 self.with_store(|store| {
685 store.get_checkpoint_contents_by_digest(digest)
686 .map_or(Ok(None), |contents| {
687 iota_types::messages_checkpoint::FullCheckpointContents::try_from_checkpoint_contents(
688 store,
689 contents,
690 )
691 })
692 })
693 }
694}
695
696impl<T: Send + Sync, V: store::SimulatorStore + Send + Sync> GrpcStateReader for Simulacrum<T, V> {
697 fn get_lowest_available_checkpoint_objects(
698 &self,
699 ) -> iota_types::storage::error::Result<CheckpointSequenceNumber> {
700 Ok(0)
701 }
702
703 fn get_chain_identifier(
704 &self,
705 ) -> iota_types::storage::error::Result<iota_types::digests::ChainIdentifier> {
706 Ok(self
707 .with_store(|store| store.get_checkpoint_by_sequence_number(0))
708 .expect("lowest available checkpoint should exist")
709 .digest()
710 .to_owned()
711 .into())
712 }
713
714 fn get_epoch_last_checkpoint(
715 &self,
716 epoch_id: iota_types::committee::EpochId,
717 ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
718 Ok(self.with_store(|store| {
719 store
720 .get_last_checkpoint_of_epoch(epoch_id)
721 .and_then(|seq| store.get_checkpoint_by_sequence_number(seq))
722 }))
723 }
724
725 fn grpc_indexes(&self) -> Option<&dyn iota_node_storage::GrpcIndexes> {
726 Some(self)
727 }
728
729 fn get_struct_layout(
730 &self,
731 _: &StructTag,
732 ) -> iota_types::storage::error::Result<Option<move_core_types::annotated_value::MoveTypeLayout>>
733 {
734 Ok(None)
735 }
736}
737
738impl<T: Send + Sync, V: store::SimulatorStore + Send + Sync> Simulacrum<T, V> {
739 fn get_system_state_for_epoch(&self, epoch: u64) -> Option<IotaSystemState> {
740 self.with_store(|store| {
741 if let Some(historical_state) = store.get_system_state_by_epoch(epoch) {
742 return Some(historical_state.clone());
743 }
744 let current_system_state = store.get_system_state();
745 if epoch == current_system_state.epoch() {
746 return Some(current_system_state);
747 }
748 None
749 })
750 }
751}
752
753impl<T: Send + Sync, V: store::SimulatorStore + Send + Sync> GrpcIndexes for Simulacrum<T, V> {
754 fn get_epoch_info(
755 &self,
756 epoch: iota_types::committee::EpochId,
757 ) -> iota_types::storage::error::Result<Option<EpochInfo>> {
758 Ok(self.with_store(|store| {
759 let start_checkpoint_seq = if epoch != 0 {
760 store
761 .get_last_checkpoint_of_epoch(epoch - 1)
762 .map(|seq| Some(seq + 1))
763 .unwrap_or(None)?
764 } else {
765 0
766 };
767
768 let start_checkpoint = store.get_checkpoint_by_sequence_number(start_checkpoint_seq)?;
769
770 let system_state = self.get_system_state_for_epoch(epoch)?;
771
772 let (end_timestamp_ms, end_checkpoint) =
773 if let Some(next_epoch_state) = self.get_system_state_for_epoch(epoch + 1) {
774 (
775 Some(next_epoch_state.epoch_start_timestamp_ms()),
776 Some(
777 store
778 .get_last_checkpoint_of_epoch(epoch)
779 .expect("last checkpoint of completed epoch should exist"),
780 ),
781 )
782 } else {
783 (None, None)
784 };
785
786 Some(EpochInfo {
787 epoch,
788 protocol_version: system_state.protocol_version(),
789 start_timestamp_ms: start_checkpoint.data().timestamp_ms,
790 end_timestamp_ms,
791 start_checkpoint: start_checkpoint_seq,
792 end_checkpoint,
793 reference_gas_price: system_state.reference_gas_price(),
794 system_state,
795 })
796 }))
797 }
798
799 fn get_transaction_info(
800 &self,
801 digest: &TransactionDigest,
802 ) -> iota_types::storage::error::Result<Option<TransactionInfo>> {
803 Ok(self.with_store(|store| {
804 let highest_seq = store
805 .get_highest_checkpoint()
806 .map(|cp| *cp.sequence_number())?;
807
808 for seq in (0..=highest_seq).rev() {
809 if let Some(checkpoint) = store.get_checkpoint_by_sequence_number(seq) {
810 if let Some(contents) =
811 store.get_checkpoint_contents_by_digest(&checkpoint.content_digest)
812 {
813 if contents
814 .iter()
815 .any(|exec_digests| exec_digests.transaction == *digest)
816 {
817 return Some(TransactionInfo {
821 checkpoint: *checkpoint.sequence_number(),
822 object_types: HashMap::new(),
823 });
824 }
825 }
826 }
827 }
828 None
829 }))
830 }
831
832 fn account_owned_objects_info_iter(
833 &self,
834 _owner: iota_types::base_types::IotaAddress,
835 _cursor: Option<&iota_types::storage::OwnedObjectCursor>,
836 _object_type: Option<StructTag>,
837 ) -> iota_types::storage::error::Result<
838 Box<dyn Iterator<Item = iota_types::storage::OwnedObjectIteratorItem> + '_>,
839 > {
840 Ok(Box::new(std::iter::empty()))
841 }
842
843 fn dynamic_field_iter(
844 &self,
845 _parent: iota_types::base_types::ObjectID,
846 _cursor: Option<iota_types::base_types::ObjectID>,
847 ) -> iota_types::storage::error::Result<
848 Box<
849 dyn Iterator<
850 Item = Result<
851 iota_types::storage::DynamicFieldKey,
852 typed_store_error::TypedStoreError,
853 >,
854 > + '_,
855 >,
856 > {
857 Ok(Box::new(std::iter::empty()))
858 }
859
860 fn get_coin_info(
861 &self,
862 _coin_type: &StructTag,
863 ) -> iota_types::storage::error::Result<Option<iota_types::storage::CoinInfo>> {
864 Ok(None)
865 }
866
867 fn package_versions_iter(
868 &self,
869 _original_package_id: iota_types::base_types::ObjectID,
870 _cursor: Option<u64>,
871 ) -> iota_types::storage::error::Result<
872 Box<dyn Iterator<Item = iota_types::storage::PackageVersionIteratorItem> + '_>,
873 > {
874 Ok(Box::new(std::iter::empty()))
875 }
876}
877
878impl Simulacrum {
879 pub fn transfer_txn(&self, recipient: IotaAddress) -> (Transaction, u64) {
886 let (sender, key) = self.with_keystore(|keystore| {
887 let (s, k) = keystore.accounts().next().unwrap();
888 (*s, k.copy())
889 });
890
891 let (object, gas_coin_value) = self.with_store(|store| {
892 let object = store
893 .owned_objects(sender)
894 .find(|object| object.is_gas_coin())
895 .unwrap();
896 let gas_coin = GasCoin::try_from(object).unwrap();
897 (object.clone(), gas_coin.value())
898 });
899 let transfer_amount = gas_coin_value / 2;
900
901 let pt = {
902 let mut builder = ProgrammableTransactionBuilder::new();
903 builder.transfer_iota(recipient, Some(transfer_amount));
904 builder.finish()
905 };
906
907 let kind = TransactionKind::Programmable(pt);
908 let gas_data = GasData {
909 objects: vec![object.compute_object_reference()],
910 owner: sender,
911 price: self.reference_gas_price(),
912 budget: 1_000_000_000,
913 };
914 let tx_data = TransactionData::new_with_gas_data(kind, sender, gas_data);
915 let tx = Transaction::from_data_and_signer(tx_data, vec![&key]);
916 (tx, transfer_amount)
917 }
918}
919
920#[cfg(test)]
921mod tests {
922 use std::time::Duration;
923
924 use iota_types::{
925 base_types::IotaAddress, effects::TransactionEffectsAPI, gas_coin::GasCoin,
926 transaction::TransactionDataAPI,
927 };
928 use rand::{SeedableRng, rngs::StdRng};
929
930 use super::*;
931
932 #[test]
933 fn deterministic_genesis() {
934 let rng = StdRng::from_seed([9; 32]);
935 let chain1 = Simulacrum::new_with_rng(rng);
936 let genesis_checkpoint_digest1 = chain1
937 .with_store(|store| *store.get_checkpoint_by_sequence_number(0).unwrap().digest());
938
939 let rng = StdRng::from_seed([9; 32]);
940 let chain2 = Simulacrum::new_with_rng(rng);
941 let genesis_checkpoint_digest2 = chain2
942 .with_store(|store| *store.get_checkpoint_by_sequence_number(0).unwrap().digest());
943
944 assert_eq!(genesis_checkpoint_digest1, genesis_checkpoint_digest2);
945
946 let rng = StdRng::from_seed([0; 32]);
948 let chain3 = Simulacrum::new_with_rng(rng);
949
950 let committee1 = chain1.with_store(|store| store.get_committee_by_epoch(0).cloned());
951 let committee3 = chain3.with_store(|store| store.get_committee_by_epoch(0).cloned());
952 assert_ne!(committee1, committee3);
953 }
954
955 #[test]
956 fn simple() {
957 let steps = 10;
958 let sim = Simulacrum::new();
959
960 let start_time_ms = sim.with_store(|store| {
961 let clock = store.get_clock();
962 println!("clock: {clock:#?}");
963 clock.timestamp_ms()
964 });
965
966 for _ in 0..steps {
967 sim.advance_clock(Duration::from_millis(1));
968 sim.create_checkpoint();
969 sim.with_store(|store| {
970 let clock = store.get_clock();
971 println!("clock: {clock:#?}");
972 });
973 }
974 let end_time_ms = sim.with_store(|store| store.get_clock().timestamp_ms());
975 assert_eq!(end_time_ms - start_time_ms, steps);
976 sim.with_store(|store| {
977 dbg!(store.get_highest_checkpoint());
978 });
979 }
980
981 #[test]
982 fn simple_epoch() {
983 let steps = 10;
984 let sim = Simulacrum::new();
985
986 let start_epoch = sim.with_store(|store| store.get_highest_checkpoint().unwrap().epoch);
987 for i in 0..steps {
988 sim.advance_epoch();
989 sim.advance_clock(Duration::from_millis(1));
990 sim.create_checkpoint();
991 println!("{i}");
992 }
993 let end_epoch = sim.with_store(|store| store.get_highest_checkpoint().unwrap().epoch);
994 assert_eq!(end_epoch - start_epoch, steps);
995 sim.with_store(|store| {
996 dbg!(store.get_highest_checkpoint());
997 });
998 }
999
1000 #[test]
1001 fn transfer() {
1002 let sim = Simulacrum::new();
1003 let recipient = IotaAddress::random();
1004 let (tx, transfer_amount) = sim.transfer_txn(recipient);
1005
1006 let gas_id = tx.data().transaction_data().gas_data().objects[0].object_id;
1007 let effects = sim.execute_transaction(tx).unwrap().0;
1008 let gas_summary = effects.gas_cost_summary();
1009 let gas_paid = gas_summary.net_gas_usage();
1010
1011 sim.with_store(|store| {
1012 assert_eq!(
1013 (transfer_amount as i64 - gas_paid) as u64,
1014 store::SimulatorStore::get_object(store, &gas_id)
1015 .and_then(|object| GasCoin::try_from(&object).ok())
1016 .unwrap()
1017 .value()
1018 );
1019
1020 assert_eq!(
1021 transfer_amount,
1022 store
1023 .owned_objects(recipient)
1024 .next()
1025 .and_then(|object| GasCoin::try_from(object).ok())
1026 .unwrap()
1027 .value()
1028 );
1029 });
1030
1031 let checkpoint = sim.create_checkpoint();
1032
1033 assert_eq!(&checkpoint.epoch_rolling_gas_cost_summary, gas_summary);
1034 assert_eq!(checkpoint.network_total_transactions, 2); }
1036}