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::{
35 Address, CheckpointContentsDigest, CheckpointDigest, ConsensusCommitDigest,
36 EndOfEpochTransactionKind, GasPayment, ObjectId, StructTag, SystemPackage, TransactionDigest,
37 TransactionKind,
38};
39use iota_storage::blob::{Blob, BlobEncoding};
40use iota_swarm_config::{
41 genesis_config::AccountConfig, network_config::NetworkConfig,
42 network_config_builder::ConfigBuilder,
43};
44use iota_types::{
45 base_types::{AuthorityName, VersionNumber},
46 committee::Committee,
47 crypto::{AuthoritySignature, KeypairTraits},
48 effects::TransactionEffects,
49 error::ExecutionError,
50 gas_coin::{GasCoin, NANOS_PER_IOTA},
51 inner_temporary_store::InnerTemporaryStore,
52 iota_system_state::{
53 IotaSystemState, IotaSystemStateTrait, epoch_start_iota_system_state::EpochStartSystemState,
54 },
55 messages_checkpoint::{
56 CheckpointContents, CheckpointContentsExt, CheckpointSequenceNumber, EndOfEpochData,
57 VerifiedCheckpoint,
58 },
59 mock_checkpoint_builder::{MockCheckpointBuilder, ValidatorKeypairProvider},
60 object::Object,
61 programmable_transaction_builder::ProgrammableTransactionBuilder,
62 signature::VerifyParams,
63 storage::{EpochInfoV2, ObjectStore, ReadStore, TransactionInfo},
64 transaction::{Transaction, TransactionData, TransactionDataAPI, VerifiedTransaction},
65};
66use rand::rngs::OsRng;
67
68pub use self::store::{SimulatorStore, in_mem_store::InMemoryStore};
69use self::{epoch_state::EpochState, store::in_mem_store::KeyStore};
70
71pub struct Simulacrum<R = OsRng, Store: SimulatorStore = InMemoryStore> {
81 inner: RwLock<SimulacrumInner<R, Store>>,
83 deny_config: TransactionDenyConfig,
85 verifier_signing_config: VerifierSigningConfig,
86}
87
88struct SimulacrumInner<R, Store: SimulatorStore> {
89 rng: R,
90 keystore: KeyStore,
91 #[expect(unused)]
92 genesis: genesis::Genesis,
93 store: Store,
94 checkpoint_builder: MockCheckpointBuilder,
95
96 epoch_state: EpochState,
98 data_ingestion_path: Option<PathBuf>,
99}
100
101impl Simulacrum {
102 #[expect(clippy::new_without_default)]
105 pub fn new() -> Self {
106 Self::new_with_rng(OsRng)
107 }
108}
109
110impl<R> Simulacrum<R>
111where
112 R: rand::RngCore + rand::CryptoRng,
113{
114 pub fn new_with_rng(mut rng: R) -> Self {
129 let config = ConfigBuilder::new_with_temp_dir()
130 .rng(&mut rng)
131 .with_chain_start_timestamp_ms(1)
132 .deterministic_committee_size(NonZeroUsize::new(1).unwrap())
133 .build();
134 Self::new_with_network_config_in_mem(&config, rng)
135 }
136
137 pub fn new_with_protocol_version_and_accounts(
138 mut rng: R,
139 chain_start_timestamp_ms: u64,
140 protocol_version: ProtocolVersion,
141 account_configs: Vec<AccountConfig>,
142 ) -> Self {
143 let config = ConfigBuilder::new_with_temp_dir()
144 .rng(&mut rng)
145 .with_chain_start_timestamp_ms(chain_start_timestamp_ms)
146 .deterministic_committee_size(NonZeroUsize::new(1).unwrap())
147 .with_protocol_version(protocol_version)
148 .with_accounts(account_configs)
149 .build();
150 Self::new_with_network_config_in_mem(&config, rng)
151 }
152
153 fn new_with_network_config_in_mem(config: &NetworkConfig, rng: R) -> Self {
154 let store = InMemoryStore::new(&config.genesis);
155 Self::new_with_network_config_store(config, rng, store)
156 }
157}
158
159impl<R, S: store::SimulatorStore> Simulacrum<R, S> {
160 pub fn new_with_network_config_store(config: &NetworkConfig, rng: R, store: S) -> Self {
161 let keystore = KeyStore::from_network_config(config);
162 let checkpoint_builder = MockCheckpointBuilder::new(config.genesis.checkpoint());
163
164 let genesis = &config.genesis;
165 let epoch_state = EpochState::new(genesis.iota_system_object());
166
167 Self {
168 deny_config: TransactionDenyConfig::default(),
169 verifier_signing_config: VerifierSigningConfig::default(),
170 inner: RwLock::new(SimulacrumInner {
171 rng,
172 keystore,
173 genesis: genesis.clone(),
174 store,
175 checkpoint_builder,
176 epoch_state,
177 data_ingestion_path: None,
178 }),
179 }
180 }
181
182 pub fn execute_transaction(
196 &self,
197 transaction: Transaction,
198 ) -> anyhow::Result<(TransactionEffects, Option<ExecutionError>)> {
199 let mut inner = self.inner.write().unwrap();
200 let transaction = transaction.try_into_verified_for_testing(&VerifyParams::default())?;
201
202 let (inner_temporary_store, _, effects, execution_error_opt) =
203 inner.epoch_state.execute_transaction(
204 &inner.store,
205 &self.deny_config,
206 &self.verifier_signing_config,
207 &transaction,
208 )?;
209
210 let InnerTemporaryStore {
211 written, events, ..
212 } = inner_temporary_store;
213
214 inner.store.insert_executed_transaction(
215 transaction.clone(),
216 effects.clone(),
217 events,
218 written,
219 );
220
221 inner
223 .checkpoint_builder
224 .push_transaction(transaction, effects.clone());
225 Ok((effects, execution_error_opt.err()))
226 }
227
228 pub fn simulate_transaction(
231 &self,
232 transaction: TransactionData,
233 checks: iota_types::transaction_executor::VmChecks,
234 ) -> iota_types::error::IotaResult<iota_types::transaction_executor::SimulateTransactionResult>
235 {
236 let inner = self.inner.read().unwrap();
237 inner.epoch_state.simulate_transaction(
238 &inner.store,
239 &self.deny_config,
240 &self.verifier_signing_config,
241 transaction,
242 checks,
243 )
244 }
245
246 pub fn create_checkpoint(&self) -> VerifiedCheckpoint {
249 let (checkpoint, contents) = {
250 let mut inner = self.inner.write().unwrap();
251 let committee = CommitteeWithKeys::new(&inner.keystore, inner.epoch_state.committee());
252 let timestamp_ms = inner.store.get_clock().timestamp_ms();
253 let (checkpoint, contents, _) =
254 inner.checkpoint_builder.build(&committee, timestamp_ms);
255 inner.store.insert_checkpoint(checkpoint.clone());
256 inner.store.insert_checkpoint_contents(contents.clone());
257 (checkpoint, contents)
258 };
259 self.process_data_ingestion(checkpoint.clone(), contents)
261 .unwrap();
262 checkpoint
263 }
264
265 pub fn advance_clock(&self, duration: std::time::Duration) -> TransactionEffects {
270 let mut inner = self.inner.write().unwrap();
271 let epoch = inner.epoch_state.epoch();
272 let round = inner.epoch_state.next_consensus_round();
273 let timestamp_ms = inner.store.get_clock().timestamp_ms() + duration.as_millis() as u64;
274 drop(inner);
275
276 let consensus_commit_prologue_transaction =
277 VerifiedTransaction::new_consensus_commit_prologue_v1(
278 epoch,
279 round,
280 timestamp_ms,
281 ConsensusCommitDigest::default(),
282 Vec::new(),
283 );
284
285 self.execute_transaction(consensus_commit_prologue_transaction.into())
286 .expect("advancing the clock cannot fail")
287 .0
288 }
289
290 pub fn advance_epoch(&self) {
300 let inner = self.inner.read().unwrap();
301 let current_epoch = inner.epoch_state.epoch();
302 let next_epoch = current_epoch + 1;
303 let next_epoch_protocol_version = inner.epoch_state.protocol_version();
304 let gas_cost_summary = inner
305 .checkpoint_builder
306 .epoch_rolling_gas_cost_summary()
307 .clone();
308 let epoch_start_timestamp_ms = inner.store.get_clock().timestamp_ms();
309 drop(inner);
310
311 let next_epoch_system_package_bytes: Vec<SystemPackage> = vec![];
312 let kinds = vec![EndOfEpochTransactionKind::new_change_epoch_v3(
313 next_epoch,
314 next_epoch_protocol_version.as_u64(),
315 gas_cost_summary.storage_cost,
316 gas_cost_summary.computation_cost,
317 gas_cost_summary.computation_cost_burned,
318 gas_cost_summary.storage_rebate,
319 gas_cost_summary.non_refundable_storage_fee,
320 epoch_start_timestamp_ms,
321 next_epoch_system_package_bytes,
322 vec![],
323 )];
324
325 let tx = VerifiedTransaction::new_end_of_epoch_transaction(kinds);
326 self.execute_transaction(tx.into())
327 .expect("advancing the epoch cannot fail");
328
329 let (checkpoint, contents, new_epoch_state) = {
330 let mut inner = self.inner.write().unwrap();
331 let new_epoch_state = EpochState::new(inner.store.get_system_state());
332 let end_of_epoch_data = EndOfEpochData {
333 next_epoch_committee: new_epoch_state.committee().committee_members(),
334 next_epoch_protocol_version: next_epoch_protocol_version.as_u64(),
335 epoch_commitments: vec![],
336 epoch_supply_change: 0,
338 };
339 let (checkpoint, contents, _) = {
340 let committee =
341 CommitteeWithKeys::new(&inner.keystore, inner.epoch_state.committee());
342 let timestamp_ms = inner.store.get_clock().timestamp_ms();
343 inner.checkpoint_builder.build_end_of_epoch(
344 &committee,
345 timestamp_ms,
346 next_epoch,
347 end_of_epoch_data,
348 )
349 };
350
351 inner.store.insert_checkpoint(checkpoint.clone());
352 inner.store.insert_checkpoint_contents(contents.clone());
353 inner
354 .store
355 .update_last_checkpoint_of_epoch(current_epoch, checkpoint.sequence_number());
356 (checkpoint, contents, new_epoch_state)
357 };
358
359 self.process_data_ingestion(checkpoint, contents).unwrap();
361
362 let mut inner = self.inner.write().unwrap();
364 inner.epoch_state = new_epoch_state;
365 }
366
367 pub fn with_store<F, T>(&self, f: F) -> T
372 where
373 F: FnOnce(&S) -> T,
374 {
375 let inner = self.inner.read().unwrap();
376 f(&inner.store)
377 }
378
379 pub fn with_keystore<F, T>(&self, f: F) -> T
384 where
385 F: FnOnce(&KeyStore) -> T,
386 {
387 let inner = self.inner.read().unwrap();
388 f(&inner.keystore)
389 }
390
391 pub fn epoch_start_state(&self) -> EpochStartSystemState {
392 let inner = self.inner.read().unwrap();
393 inner.epoch_state.epoch_start_state()
394 }
395
396 pub fn with_rng<F, T>(&self, f: F) -> T
403 where
404 F: FnOnce(&mut R) -> T,
405 {
406 let mut inner = self.inner.write().unwrap();
407 f(&mut inner.rng)
408 }
409
410 pub fn reference_gas_price(&self) -> u64 {
412 self.inner.read().unwrap().epoch_state.reference_gas_price()
413 }
414
415 pub fn request_gas(&self, address: Address, amount: u64) -> Result<TransactionEffects> {
432 let (sender, key) = self.with_keystore(|keystore| -> Result<(Address, _)> {
436 let (s, k) = keystore
437 .accounts()
438 .next()
439 .ok_or_else(|| anyhow!("no accounts available in keystore"))?;
440 Ok((*s, k.copy()))
441 })?;
442
443 let object = self
444 .with_store(|store| {
445 store.owned_objects(sender).find(|object| {
446 object.is_gas_coin()
447 && object.get_coin_value_unchecked() > amount + NANOS_PER_IOTA
448 })
449 })
450 .ok_or_else(|| {
451 anyhow!("unable to find a coin with enough to satisfy request for {amount} Nanos")
452 })?;
453
454 let gas_data = GasPayment {
455 objects: vec![object.object_ref()],
456 owner: sender,
457 price: self.reference_gas_price(),
458 budget: NANOS_PER_IOTA,
459 };
460
461 let pt = {
462 let mut builder =
463 iota_types::programmable_transaction_builder::ProgrammableTransactionBuilder::new();
464 builder.transfer_iota(address, Some(amount));
465 builder.finish()
466 };
467
468 let kind = TransactionKind::Programmable(pt);
469 let tx_data =
470 iota_types::transaction::TransactionData::new_with_gas_data(kind, sender, gas_data);
471 let tx = Transaction::from_data_and_signer(tx_data, vec![&key]);
472
473 self.execute_transaction(tx).map(|x| x.0)
474 }
475
476 pub fn set_data_ingestion_path(&self, data_ingestion_path: PathBuf) {
477 let checkpoint = {
478 let mut inner = self.inner.write().unwrap();
479 inner.data_ingestion_path = Some(data_ingestion_path);
480 let checkpoint = inner.store.get_checkpoint_by_sequence_number(0).unwrap();
481 let contents = inner
482 .store
483 .get_checkpoint_contents_by_digest(&checkpoint.content_digest);
484 (checkpoint, contents)
485 };
486 if let (checkpoint, Some(contents)) = checkpoint {
488 self.process_data_ingestion(checkpoint, contents).unwrap();
489 }
490 }
491
492 pub fn override_next_checkpoint_number(&self, number: CheckpointSequenceNumber) {
498 let mut inner = self.inner.write().unwrap();
499 let committee = CommitteeWithKeys::new(&inner.keystore, inner.epoch_state.committee());
500 inner
501 .checkpoint_builder
502 .override_next_checkpoint_number(number, &committee);
503 }
504
505 fn process_data_ingestion(
508 &self,
509 checkpoint: VerifiedCheckpoint,
510 checkpoint_contents: CheckpointContents,
511 ) -> anyhow::Result<()> {
512 let path = self.inner.read().unwrap().data_ingestion_path.clone();
513 if let Some(data_path) = path {
514 let file_name = format!("{}.chk", checkpoint.sequence_number);
515 let checkpoint_data = self.try_get_checkpoint_data(checkpoint, checkpoint_contents)?;
516 std::fs::create_dir_all(&data_path)?;
517 let blob = Blob::encode(&checkpoint_data, BlobEncoding::Bcs)?;
518 std::fs::write(data_path.join(file_name), blob.to_bytes())?;
519 }
520 Ok(())
521 }
522}
523
524pub struct CommitteeWithKeys {
525 keystore: KeyStore,
526 committee: Committee,
527}
528
529impl CommitteeWithKeys {
530 fn new(keystore: &KeyStore, committee: &Committee) -> Self {
531 Self {
532 keystore: keystore.clone(),
533 committee: committee.clone(),
534 }
535 }
536
537 pub fn keystore(&self) -> &KeyStore {
538 &self.keystore
539 }
540}
541
542impl ValidatorKeypairProvider for CommitteeWithKeys {
543 fn get_validator_key(&self, name: &AuthorityName) -> &dyn Signer<AuthoritySignature> {
544 self.keystore.validator(name).unwrap()
545 }
546
547 fn get_committee(&self) -> &Committee {
548 &self.committee
549 }
550}
551
552impl<T, V: store::SimulatorStore> ObjectStore for Simulacrum<T, V> {
553 fn try_get_object(
554 &self,
555 object_id: &ObjectId,
556 ) -> Result<Option<Object>, iota_types::storage::error::Error> {
557 self.with_store(|store| store.try_get_object(object_id))
558 }
559
560 fn try_get_object_by_key(
561 &self,
562 object_id: &ObjectId,
563 version: VersionNumber,
564 ) -> Result<Option<Object>, iota_types::storage::error::Error> {
565 self.with_store(|store| store.try_get_object_by_key(object_id, version))
566 }
567}
568
569impl<T, V: store::SimulatorStore> ReadStore for Simulacrum<T, V> {
570 fn try_get_committee(
571 &self,
572 epoch: iota_types::committee::EpochId,
573 ) -> iota_types::storage::error::Result<Option<std::sync::Arc<Committee>>> {
574 self.with_store(|store| store.try_get_committee(epoch))
575 }
576
577 fn try_get_latest_checkpoint(&self) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
578 Ok(self.with_store(|store| store.get_highest_checkpoint().unwrap()))
579 }
580
581 fn try_get_highest_verified_checkpoint(
582 &self,
583 ) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
584 Ok(self.with_store(|store| store.get_highest_checkpoint().unwrap()))
585 }
586
587 fn try_get_highest_synced_checkpoint(
588 &self,
589 ) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
590 Ok(self.with_store(|store| store.get_highest_checkpoint().unwrap()))
591 }
592
593 fn try_get_lowest_available_checkpoint(
594 &self,
595 ) -> iota_types::storage::error::Result<iota_types::messages_checkpoint::CheckpointSequenceNumber>
596 {
597 Ok(0)
600 }
601
602 fn try_get_checkpoint_by_digest(
603 &self,
604 digest: &CheckpointDigest,
605 ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
606 Ok(self.with_store(|store| store.get_checkpoint_by_digest(digest)))
607 }
608
609 fn try_get_checkpoint_by_sequence_number(
610 &self,
611 sequence_number: iota_types::messages_checkpoint::CheckpointSequenceNumber,
612 ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
613 Ok(self.with_store(|store| store.get_checkpoint_by_sequence_number(sequence_number)))
614 }
615
616 fn try_get_checkpoint_contents_by_digest(
617 &self,
618 digest: &CheckpointContentsDigest,
619 ) -> iota_types::storage::error::Result<
620 Option<iota_types::messages_checkpoint::CheckpointContents>,
621 > {
622 Ok(self.with_store(|store| store.get_checkpoint_contents_by_digest(digest)))
623 }
624
625 fn try_get_checkpoint_contents_by_sequence_number(
626 &self,
627 sequence_number: iota_types::messages_checkpoint::CheckpointSequenceNumber,
628 ) -> iota_types::storage::error::Result<
629 Option<iota_types::messages_checkpoint::CheckpointContents>,
630 > {
631 Ok(self.with_store(|store| {
632 store
633 .get_checkpoint_by_sequence_number(sequence_number)
634 .and_then(|checkpoint| {
635 store.get_checkpoint_contents_by_digest(&checkpoint.content_digest)
636 })
637 }))
638 }
639
640 fn try_get_transaction(
641 &self,
642 tx_digest: &TransactionDigest,
643 ) -> iota_types::storage::error::Result<Option<Arc<VerifiedTransaction>>> {
644 Ok(self.with_store(|store| store.get_transaction(tx_digest)))
645 }
646
647 fn try_get_transaction_effects(
648 &self,
649 tx_digest: &TransactionDigest,
650 ) -> iota_types::storage::error::Result<Option<TransactionEffects>> {
651 Ok(self.with_store(|store| store.get_transaction_effects(tx_digest)))
652 }
653
654 fn try_get_events(
655 &self,
656 digest: &TransactionDigest,
657 ) -> iota_types::storage::error::Result<Option<iota_types::effects::TransactionEvents>> {
658 Ok(self.with_store(|store| store.get_events(digest)))
659 }
660
661 fn try_get_full_checkpoint_contents_by_sequence_number(
662 &self,
663 sequence_number: iota_types::messages_checkpoint::CheckpointSequenceNumber,
664 ) -> iota_types::storage::error::Result<
665 Option<iota_types::messages_checkpoint::FullCheckpointContents>,
666 > {
667 self.with_store(|store| {
668 store
669 .try_get_checkpoint_by_sequence_number(sequence_number)?
670 .and_then(|chk| store.get_checkpoint_contents_by_digest(&chk.content_digest))
671 .map_or(Ok(None), |contents| {
672 iota_types::messages_checkpoint::FullCheckpointContents::try_from_checkpoint_contents(
673 store,
674 contents,
675 )
676 })
677 })
678 }
679
680 fn try_get_full_checkpoint_contents(
681 &self,
682 digest: &CheckpointContentsDigest,
683 ) -> iota_types::storage::error::Result<
684 Option<iota_types::messages_checkpoint::FullCheckpointContents>,
685 > {
686 self.with_store(|store| {
687 store.get_checkpoint_contents_by_digest(digest)
688 .map_or(Ok(None), |contents| {
689 iota_types::messages_checkpoint::FullCheckpointContents::try_from_checkpoint_contents(
690 store,
691 contents,
692 )
693 })
694 })
695 }
696}
697
698impl<T: Send + Sync, V: store::SimulatorStore + Send + Sync> GrpcStateReader for Simulacrum<T, V> {
699 fn get_lowest_available_checkpoint_objects(
700 &self,
701 ) -> iota_types::storage::error::Result<CheckpointSequenceNumber> {
702 Ok(0)
703 }
704
705 fn get_chain_identifier(
706 &self,
707 ) -> iota_types::storage::error::Result<iota_types::digests::ChainIdentifier> {
708 Ok(self
709 .with_store(|store| store.get_checkpoint_by_sequence_number(0))
710 .expect("lowest available checkpoint should exist")
711 .digest()
712 .to_owned()
713 .into())
714 }
715
716 fn get_epoch_last_checkpoint(
717 &self,
718 epoch_id: iota_types::committee::EpochId,
719 ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
720 Ok(self.with_store(|store| {
721 store
722 .get_last_checkpoint_of_epoch(epoch_id)
723 .and_then(|seq| store.get_checkpoint_by_sequence_number(seq))
724 }))
725 }
726
727 fn get_epoch_info(
728 &self,
729 epoch: iota_types::committee::EpochId,
730 ) -> iota_types::storage::error::Result<Option<EpochInfoV2>> {
731 Ok(self.with_store(|store| {
732 let start_checkpoint_seq = if epoch != 0 {
733 store
734 .get_last_checkpoint_of_epoch(epoch - 1)
735 .map(|seq| Some(seq + 1))
736 .unwrap_or(None)?
737 } else {
738 0
739 };
740
741 let start_checkpoint = store.get_checkpoint_by_sequence_number(start_checkpoint_seq)?;
742
743 let system_state = self.get_system_state_for_epoch(epoch)?;
744
745 Some(EpochInfoV2 {
746 epoch,
747 start_checkpoint: start_checkpoint_seq,
748 start_timestamp_ms: start_checkpoint.data().timestamp_ms,
749 system_state,
750 epoch_close_proof: None,
753 })
754 }))
755 }
756
757 fn grpc_indexes(&self) -> Option<&dyn iota_node_storage::GrpcIndexes> {
758 Some(self)
759 }
760
761 fn get_struct_layout(
762 &self,
763 _: &StructTag,
764 ) -> iota_types::storage::error::Result<Option<move_core_types::annotated_value::MoveTypeLayout>>
765 {
766 Ok(None)
767 }
768}
769
770impl<T: Send + Sync, V: store::SimulatorStore + Send + Sync> Simulacrum<T, V> {
771 fn get_system_state_for_epoch(&self, epoch: u64) -> Option<IotaSystemState> {
772 self.with_store(|store| {
773 if let Some(historical_state) = store.get_system_state_by_epoch(epoch) {
774 return Some(historical_state.clone());
775 }
776 let current_system_state = store.get_system_state();
777 if epoch == current_system_state.epoch() {
778 return Some(current_system_state);
779 }
780 None
781 })
782 }
783}
784
785impl<T: Send + Sync, V: store::SimulatorStore + Send + Sync> GrpcIndexes for Simulacrum<T, V> {
786 fn get_transaction_info(
787 &self,
788 digest: &TransactionDigest,
789 ) -> iota_types::storage::error::Result<Option<TransactionInfo>> {
790 Ok(self.with_store(|store| {
791 let highest_seq = store
792 .get_highest_checkpoint()
793 .map(|cp| cp.sequence_number())?;
794
795 for seq in (0..=highest_seq).rev() {
796 if let Some(checkpoint) = store.get_checkpoint_by_sequence_number(seq) {
797 if let Some(contents) =
798 store.get_checkpoint_contents_by_digest(&checkpoint.content_digest)
799 {
800 if contents
801 .iter()
802 .any(|exec_digests| exec_digests.transaction == *digest)
803 {
804 return Some(TransactionInfo {
808 checkpoint: checkpoint.sequence_number(),
809 object_types: HashMap::new(),
810 });
811 }
812 }
813 }
814 }
815 None
816 }))
817 }
818
819 fn account_owned_objects_info_iter(
820 &self,
821 _owner: Address,
822 _cursor: Option<&iota_types::storage::OwnedObjectCursor>,
823 _object_type: Option<StructTag>,
824 ) -> iota_types::storage::error::Result<
825 Box<dyn Iterator<Item = iota_types::storage::OwnedObjectIteratorItem> + '_>,
826 > {
827 Ok(Box::new(std::iter::empty()))
828 }
829
830 fn dynamic_field_iter(
831 &self,
832 _parent: iota_sdk_types::ObjectId,
833 _cursor: Option<iota_sdk_types::ObjectId>,
834 ) -> iota_types::storage::error::Result<
835 Box<
836 dyn Iterator<
837 Item = Result<
838 iota_types::storage::DynamicFieldKey,
839 typed_store_error::TypedStoreError,
840 >,
841 > + '_,
842 >,
843 > {
844 Ok(Box::new(std::iter::empty()))
845 }
846
847 fn get_coin_info(
848 &self,
849 _coin_type: &StructTag,
850 ) -> iota_types::storage::error::Result<Option<iota_types::storage::CoinInfo>> {
851 Ok(None)
852 }
853
854 fn package_versions_iter(
855 &self,
856 _original_package_id: iota_sdk_types::ObjectId,
857 _cursor: Option<u64>,
858 ) -> iota_types::storage::error::Result<
859 Box<dyn Iterator<Item = iota_types::storage::PackageVersionIteratorItem> + '_>,
860 > {
861 Ok(Box::new(std::iter::empty()))
862 }
863}
864
865impl Simulacrum {
866 pub fn transfer_txn(&self, recipient: Address) -> (Transaction, u64) {
873 let (sender, key) = self.with_keystore(|keystore| {
874 let (s, k) = keystore.accounts().next().unwrap();
875 (*s, k.copy())
876 });
877
878 let (object, gas_coin_value) = self.with_store(|store| {
879 let object = store
880 .owned_objects(sender)
881 .find(|object| object.is_gas_coin())
882 .unwrap();
883 let gas_coin = GasCoin::try_from(object).unwrap();
884 (object.clone(), gas_coin.value())
885 });
886 let transfer_amount = gas_coin_value / 2;
887
888 let pt = {
889 let mut builder = ProgrammableTransactionBuilder::new();
890 builder.transfer_iota(recipient, Some(transfer_amount));
891 builder.finish()
892 };
893
894 let kind = TransactionKind::Programmable(pt);
895 let gas_data = GasPayment {
896 objects: vec![object.object_ref()],
897 owner: sender,
898 price: self.reference_gas_price(),
899 budget: 1_000_000_000,
900 };
901 let tx_data = TransactionData::new_with_gas_data(kind, sender, gas_data);
902 let tx = Transaction::from_data_and_signer(tx_data, vec![&key]);
903 (tx, transfer_amount)
904 }
905}
906
907#[cfg(test)]
908mod tests {
909 use std::time::Duration;
910
911 use iota_types::{
912 effects::TransactionEffectsAPI, gas_coin::GasCoin, transaction::TransactionDataAPI,
913 };
914 use rand::{SeedableRng, rngs::StdRng};
915
916 use super::*;
917
918 #[test]
919 fn deterministic_genesis() {
920 let rng = StdRng::from_seed([9; 32]);
921 let chain1 = Simulacrum::new_with_rng(rng);
922 let genesis_checkpoint_digest1 = chain1
923 .with_store(|store| *store.get_checkpoint_by_sequence_number(0).unwrap().digest());
924
925 let rng = StdRng::from_seed([9; 32]);
926 let chain2 = Simulacrum::new_with_rng(rng);
927 let genesis_checkpoint_digest2 = chain2
928 .with_store(|store| *store.get_checkpoint_by_sequence_number(0).unwrap().digest());
929
930 assert_eq!(genesis_checkpoint_digest1, genesis_checkpoint_digest2);
931
932 let rng = StdRng::from_seed([0; 32]);
934 let chain3 = Simulacrum::new_with_rng(rng);
935
936 let committee1 = chain1.with_store(|store| store.get_committee_by_epoch(0).cloned());
937 let committee3 = chain3.with_store(|store| store.get_committee_by_epoch(0).cloned());
938 assert_ne!(committee1, committee3);
939 }
940
941 #[test]
942 fn simple() {
943 let steps = 10;
944 let sim = Simulacrum::new();
945
946 let start_time_ms = sim.with_store(|store| {
947 let clock = store.get_clock();
948 println!("clock: {clock:#?}");
949 clock.timestamp_ms()
950 });
951
952 for _ in 0..steps {
953 sim.advance_clock(Duration::from_millis(1));
954 sim.create_checkpoint();
955 sim.with_store(|store| {
956 let clock = store.get_clock();
957 println!("clock: {clock:#?}");
958 });
959 }
960 let end_time_ms = sim.with_store(|store| store.get_clock().timestamp_ms());
961 assert_eq!(end_time_ms - start_time_ms, steps);
962 sim.with_store(|store| {
963 dbg!(store.get_highest_checkpoint());
964 });
965 }
966
967 #[test]
968 fn simple_epoch() {
969 let steps = 10;
970 let sim = Simulacrum::new();
971
972 let start_epoch = sim.with_store(|store| store.get_highest_checkpoint().unwrap().epoch);
973 for i in 0..steps {
974 sim.advance_epoch();
975 sim.advance_clock(Duration::from_millis(1));
976 sim.create_checkpoint();
977 println!("{i}");
978 }
979 let end_epoch = sim.with_store(|store| store.get_highest_checkpoint().unwrap().epoch);
980 assert_eq!(end_epoch - start_epoch, steps);
981 sim.with_store(|store| {
982 dbg!(store.get_highest_checkpoint());
983 });
984 }
985
986 #[test]
987 fn transfer() {
988 let sim = Simulacrum::new();
989 let recipient = Address::random();
990 let (tx, transfer_amount) = sim.transfer_txn(recipient);
991
992 let gas_id = tx.data().transaction().gas_data().objects[0].object_id;
993 let effects = sim.execute_transaction(tx).unwrap().0;
994 let gas_summary = effects.gas_cost_summary();
995 let gas_paid = gas_summary.net_gas_usage();
996
997 sim.with_store(|store| {
998 assert_eq!(
999 (transfer_amount as i64 - gas_paid) as u64,
1000 store::SimulatorStore::get_object(store, &gas_id)
1001 .and_then(|object| GasCoin::try_from(&object).ok())
1002 .unwrap()
1003 .value()
1004 );
1005
1006 assert_eq!(
1007 transfer_amount,
1008 store
1009 .owned_objects(recipient)
1010 .next()
1011 .and_then(|object| GasCoin::try_from(object).ok())
1012 .unwrap()
1013 .value()
1014 );
1015 });
1016
1017 let checkpoint = sim.create_checkpoint();
1018
1019 assert_eq!(&checkpoint.epoch_rolling_gas_cost_summary, gas_summary);
1020 assert_eq!(checkpoint.network_total_transactions, 2); }
1022}