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