1mod epoch_state;
16pub mod store;
17pub mod transaction_executor;
18
19use std::{
20 num::NonZeroUsize,
21 path::PathBuf,
22 sync::{Arc, RwLock},
23};
24
25use anyhow::{Result, anyhow};
26use fastcrypto::traits::Signer;
27use iota_config::{
28 genesis, transaction_deny_config::TransactionDenyConfig,
29 verifier_signing_config::VerifierSigningConfig,
30};
31use iota_node_storage::{GrpcIndexes, GrpcStateReader};
32use iota_protocol_config::ProtocolVersion;
33use iota_sdk_types::{
34 Address, CheckpointContents, CheckpointContentsDigest, CheckpointDigest, ConsensusCommitDigest,
35 EndOfEpochData, EndOfEpochTransactionKind, GasPayment, ObjectId, StructTag, SystemPackage,
36 Transaction, TransactionDigest, TransactionEffects, TransactionEvents, TransactionKind,
37};
38use iota_storage::blob::{Blob, BlobEncoding};
39use iota_swarm_config::{
40 genesis_config::AccountConfig, network_config::NetworkConfig,
41 network_config_builder::ConfigBuilder,
42};
43use iota_types::{
44 base_types::{AuthorityName, EpochId, VersionNumber},
45 committee::Committee,
46 crypto::AuthoritySignature,
47 error::ExecutionError,
48 gas_coin::{GasCoin, NANOS_PER_IOTA},
49 inner_temporary_store::InnerTemporaryStore,
50 iota_system_state::{
51 IotaSystemState, IotaSystemStateTrait, epoch_start_iota_system_state::EpochStartSystemState,
52 },
53 messages_checkpoint::{CheckpointContentsExt, CheckpointSequenceNumber, VerifiedCheckpoint},
54 mock_checkpoint_builder::{MockCheckpointBuilder, ValidatorKeypairProvider},
55 object::Object,
56 programmable_transaction_builder::ProgrammableTransactionBuilder,
57 signature::VerifyParams,
58 storage::{EpochInfoV2, ObjectStore, ReadStore},
59 supported_protocol_versions::SupportedProtocolVersions,
60 transaction::{TransactionAPI, TransactionEnvelope, VerifiedTransaction},
61};
62use rand::{rand_core::UnwrapErr, rngs::SysRng};
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 = UnwrapErr<SysRng>, 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(UnwrapErr(SysRng))
103 }
104}
105
106impl<R> Simulacrum<R>
107where
108 R: 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_supported_protocol_versions(SupportedProtocolVersions::new_for_testing(
147 protocol_version.as_u64(),
148 protocol_version.as_u64(),
149 ))
150 .with_accounts(account_configs)
151 .build();
152 Self::new_with_network_config_in_mem(&config, rng)
153 }
154
155 fn new_with_network_config_in_mem(config: &NetworkConfig, rng: R) -> Self {
156 let store = InMemoryStore::new(&config.genesis);
157 Self::new_with_network_config_store(config, rng, store)
158 }
159}
160
161impl<R, S: store::SimulatorStore> Simulacrum<R, S> {
162 pub fn new_with_network_config_store(config: &NetworkConfig, rng: R, store: S) -> Self {
163 let keystore = KeyStore::from_network_config(config);
164 let checkpoint_builder = MockCheckpointBuilder::new(config.genesis.checkpoint());
165
166 let genesis = &config.genesis;
167 let epoch_state = EpochState::new(genesis.iota_system_object());
168
169 Self {
170 deny_config: TransactionDenyConfig::default(),
171 verifier_signing_config: VerifierSigningConfig::default(),
172 inner: RwLock::new(SimulacrumInner {
173 rng,
174 keystore,
175 genesis: genesis.clone(),
176 store,
177 checkpoint_builder,
178 epoch_state,
179 data_ingestion_path: None,
180 }),
181 }
182 }
183
184 pub fn execute_transaction(
198 &self,
199 transaction: TransactionEnvelope,
200 ) -> anyhow::Result<(TransactionEffects, Option<ExecutionError>)> {
201 let mut inner = self.inner.write().unwrap();
202 let transaction = transaction.try_into_verified_for_testing(
203 &VerifyParams::from_protocol_config(inner.epoch_state.protocol_config()),
204 )?;
205
206 let (inner_temporary_store, _, effects, execution_error_opt) =
207 inner.epoch_state.execute_transaction(
208 &inner.store,
209 &self.deny_config,
210 &self.verifier_signing_config,
211 &transaction,
212 )?;
213
214 let InnerTemporaryStore {
215 written, events, ..
216 } = inner_temporary_store;
217
218 inner.store.insert_executed_transaction(
219 transaction.clone(),
220 effects.clone(),
221 events,
222 written,
223 );
224
225 inner
227 .checkpoint_builder
228 .push_transaction(transaction, effects.clone());
229 Ok((effects, execution_error_opt.err()))
230 }
231
232 pub fn simulate_transaction(
235 &self,
236 transaction: Transaction,
237 checks: iota_types::transaction_executor::VmChecks,
238 ) -> iota_types::error::IotaResult<iota_types::transaction_executor::SimulateTransactionResult>
239 {
240 let inner = self.inner.read().unwrap();
241 inner.epoch_state.simulate_transaction(
242 &inner.store,
243 &self.deny_config,
244 &self.verifier_signing_config,
245 transaction,
246 checks,
247 )
248 }
249
250 pub fn create_checkpoint(&self) -> VerifiedCheckpoint {
253 let (checkpoint, contents) = {
254 let mut inner = self.inner.write().unwrap();
255 let committee = CommitteeWithKeys::new(&inner.keystore, inner.epoch_state.committee());
256 let timestamp_ms = inner.store.get_clock().timestamp_ms;
257 let (checkpoint, contents, _) =
258 inner.checkpoint_builder.build(&committee, timestamp_ms);
259 inner.store.insert_checkpoint(checkpoint.clone());
260 inner.store.insert_checkpoint_contents(contents.clone());
261 (checkpoint, contents)
262 };
263 self.process_data_ingestion(checkpoint.clone(), contents)
265 .unwrap();
266 checkpoint
267 }
268
269 pub fn advance_clock(&self, duration: std::time::Duration) -> TransactionEffects {
274 let mut inner = self.inner.write().unwrap();
275 let epoch = inner.epoch_state.epoch();
276 let round = inner.epoch_state.next_consensus_round();
277 let timestamp_ms = inner.store.get_clock().timestamp_ms + duration.as_millis() as u64;
278 drop(inner);
279
280 let consensus_commit_prologue_transaction =
281 VerifiedTransaction::new_consensus_commit_prologue_v1(
282 epoch,
283 round,
284 timestamp_ms,
285 ConsensusCommitDigest::default(),
286 Vec::new(),
287 );
288
289 self.execute_transaction(consensus_commit_prologue_transaction.into())
290 .expect("advancing the clock cannot fail")
291 .0
292 }
293
294 pub fn advance_epoch(&self, create_deny_rules_object: bool) -> TransactionEffects {
314 let inner = self.inner.read().unwrap();
315 let current_epoch = inner.epoch_state.epoch();
316 let next_epoch = current_epoch + 1;
317 let next_epoch_protocol_version = inner.epoch_state.protocol_version();
318 let gas_cost_summary = inner
319 .checkpoint_builder
320 .epoch_rolling_gas_cost_summary()
321 .clone();
322 let epoch_start_timestamp_ms = inner.store.get_clock().timestamp_ms;
323 let pass_validator_scores = inner
324 .epoch_state
325 .protocol_config()
326 .pass_validator_scores_to_advance_epoch();
327 let adjust_rewards_by_score = inner
328 .epoch_state
329 .protocol_config()
330 .adjust_rewards_by_score();
331 let committee_size = inner.epoch_state.committee().num_members();
332 drop(inner);
333
334 const MAX_SCORE: u64 = u16::MAX as u64 + 1;
338 let scores = vec![MAX_SCORE; committee_size];
339
340 let next_epoch_system_package_bytes: Vec<SystemPackage> = vec![];
341 let mut kinds = Vec::new();
342 if create_deny_rules_object {
343 kinds.push(EndOfEpochTransactionKind::TransactionDenyRulesCreate);
344 }
345 kinds.push(if pass_validator_scores {
348 EndOfEpochTransactionKind::new_change_epoch_v4(
349 next_epoch,
350 next_epoch_protocol_version.as_u64(),
351 gas_cost_summary.storage_cost,
352 gas_cost_summary.computation_cost,
353 gas_cost_summary.computation_cost_burned,
354 gas_cost_summary.storage_rebate,
355 gas_cost_summary.non_refundable_storage_fee,
356 epoch_start_timestamp_ms,
357 next_epoch_system_package_bytes,
358 vec![],
359 scores,
360 adjust_rewards_by_score,
361 )
362 } else {
363 EndOfEpochTransactionKind::new_change_epoch_v3(
364 next_epoch,
365 next_epoch_protocol_version.as_u64(),
366 gas_cost_summary.storage_cost,
367 gas_cost_summary.computation_cost,
368 gas_cost_summary.computation_cost_burned,
369 gas_cost_summary.storage_rebate,
370 gas_cost_summary.non_refundable_storage_fee,
371 epoch_start_timestamp_ms,
372 next_epoch_system_package_bytes,
373 vec![],
374 )
375 });
376
377 let tx = VerifiedTransaction::new_end_of_epoch_transaction(kinds);
378 let (effects, execution_error) = self
379 .execute_transaction(tx.into())
380 .expect("advancing the epoch cannot fail");
381 assert!(
382 execution_error.is_none(),
383 "the end-of-epoch transaction failed: {execution_error:?}, {effects:?}"
384 );
385
386 let (checkpoint, contents, new_epoch_state) = {
387 let mut inner = self.inner.write().unwrap();
388 let system_state = inner.store.get_system_state();
389 let prev_protocol_config = inner.epoch_state.protocol_config();
393 let new_epoch_state =
394 if system_state.protocol_version() == prev_protocol_config.version.as_u64() {
395 EpochState::new_with_config(prev_protocol_config.clone(), system_state)
396 } else {
397 EpochState::new(system_state)
398 };
399 let end_of_epoch_data = EndOfEpochData {
400 next_epoch_committee: new_epoch_state.committee().committee_members(),
401 next_epoch_protocol_version: next_epoch_protocol_version.as_u64(),
402 epoch_commitments: vec![],
403 epoch_supply_change: 0,
405 };
406 let (checkpoint, contents, _) = {
407 let committee =
408 CommitteeWithKeys::new(&inner.keystore, inner.epoch_state.committee());
409 let timestamp_ms = inner.store.get_clock().timestamp_ms;
410 inner.checkpoint_builder.build_end_of_epoch(
411 &committee,
412 timestamp_ms,
413 next_epoch,
414 end_of_epoch_data,
415 )
416 };
417
418 inner.store.insert_checkpoint(checkpoint.clone());
419 inner.store.insert_checkpoint_contents(contents.clone());
420 inner
421 .store
422 .update_last_checkpoint_of_epoch(current_epoch, checkpoint.sequence_number());
423 (checkpoint, contents, new_epoch_state)
424 };
425
426 self.process_data_ingestion(checkpoint, contents).unwrap();
428
429 let mut inner = self.inner.write().unwrap();
431 inner.epoch_state = new_epoch_state;
432
433 effects
434 }
435
436 pub fn with_store<F, T>(&self, f: F) -> T
441 where
442 F: FnOnce(&S) -> T,
443 {
444 let inner = self.inner.read().unwrap();
445 f(&inner.store)
446 }
447
448 pub fn with_keystore<F, T>(&self, f: F) -> T
453 where
454 F: FnOnce(&KeyStore) -> T,
455 {
456 let inner = self.inner.read().unwrap();
457 f(&inner.keystore)
458 }
459
460 pub fn epoch_start_state(&self) -> EpochStartSystemState {
461 let inner = self.inner.read().unwrap();
462 inner.epoch_state.epoch_start_state()
463 }
464
465 pub fn with_rng<F, T>(&self, f: F) -> T
472 where
473 F: FnOnce(&mut R) -> T,
474 {
475 let mut inner = self.inner.write().unwrap();
476 f(&mut inner.rng)
477 }
478
479 pub fn reference_gas_price(&self) -> u64 {
481 self.inner.read().unwrap().epoch_state.reference_gas_price()
482 }
483
484 pub fn request_gas(&self, address: Address, amount: u64) -> Result<TransactionEffects> {
501 let (sender, key) = self.with_keystore(|keystore| -> Result<(Address, _)> {
505 let (s, k) = keystore
506 .accounts()
507 .next()
508 .ok_or_else(|| anyhow!("no accounts available in keystore"))?;
509 Ok((*s, k.clone()))
510 })?;
511
512 let object = self
513 .with_store(|store| {
514 store.owned_objects(sender).find(|object| {
515 object.is_gas_coin()
516 && object.get_coin_value_unchecked() > amount + NANOS_PER_IOTA
517 })
518 })
519 .ok_or_else(|| {
520 anyhow!("unable to find a coin with enough to satisfy request for {amount} Nanos")
521 })?;
522
523 let gas_data = GasPayment {
524 objects: vec![object.object_ref()],
525 owner: sender,
526 price: self.reference_gas_price(),
527 budget: NANOS_PER_IOTA,
528 };
529
530 let pt = {
531 let mut builder =
532 iota_types::programmable_transaction_builder::ProgrammableTransactionBuilder::new();
533 builder.transfer_iota(address, Some(amount));
534 builder.finish()
535 };
536
537 let kind = TransactionKind::Programmable(pt);
538 let tx = iota_sdk_types::Transaction::new_with_gas_data(kind, sender, gas_data);
539 let tx = TransactionEnvelope::from_data_and_signer(tx, vec![&key]);
540
541 self.execute_transaction(tx).map(|x| x.0)
542 }
543
544 pub fn set_data_ingestion_path(&self, data_ingestion_path: PathBuf) {
545 let checkpoint = {
546 let mut inner = self.inner.write().unwrap();
547 inner.data_ingestion_path = Some(data_ingestion_path);
548 let checkpoint = inner.store.get_checkpoint_by_sequence_number(0).unwrap();
549 let contents = inner
550 .store
551 .get_checkpoint_contents_by_digest(&checkpoint.contents_digest);
552 (checkpoint, contents)
553 };
554 if let (checkpoint, Some(contents)) = checkpoint {
556 self.process_data_ingestion(checkpoint, contents).unwrap();
557 }
558 }
559
560 pub fn override_next_checkpoint_number(&self, number: CheckpointSequenceNumber) {
566 let mut inner = self.inner.write().unwrap();
567 let committee = CommitteeWithKeys::new(&inner.keystore, inner.epoch_state.committee());
568 inner
569 .checkpoint_builder
570 .override_next_checkpoint_number(number, &committee);
571 }
572
573 fn process_data_ingestion(
576 &self,
577 checkpoint: VerifiedCheckpoint,
578 checkpoint_contents: CheckpointContents,
579 ) -> anyhow::Result<()> {
580 let path = self.inner.read().unwrap().data_ingestion_path.clone();
581 if let Some(data_path) = path {
582 let file_name = format!("{}.chk", checkpoint.sequence_number);
583 let checkpoint_data = self.try_get_checkpoint_data(checkpoint, checkpoint_contents)?;
584 std::fs::create_dir_all(&data_path)?;
585 let blob = Blob::encode(&checkpoint_data, BlobEncoding::Bcs)?;
586 std::fs::write(data_path.join(file_name), blob.to_bytes())?;
587 }
588 Ok(())
589 }
590}
591
592pub struct CommitteeWithKeys {
593 keystore: KeyStore,
594 committee: Committee,
595}
596
597impl CommitteeWithKeys {
598 fn new(keystore: &KeyStore, committee: &Committee) -> Self {
599 Self {
600 keystore: keystore.clone(),
601 committee: committee.clone(),
602 }
603 }
604
605 pub fn keystore(&self) -> &KeyStore {
606 &self.keystore
607 }
608}
609
610impl ValidatorKeypairProvider for CommitteeWithKeys {
611 fn get_validator_key(&self, name: &AuthorityName) -> &dyn Signer<AuthoritySignature> {
612 self.keystore.validator(name).unwrap()
613 }
614
615 fn get_committee(&self) -> &Committee {
616 &self.committee
617 }
618}
619
620impl<T, V: store::SimulatorStore> ObjectStore for Simulacrum<T, V> {
621 fn try_get_object(
622 &self,
623 object_id: &ObjectId,
624 ) -> Result<Option<Object>, iota_types::storage::error::Error> {
625 self.with_store(|store| store.try_get_object(object_id))
626 }
627
628 fn try_get_object_by_key(
629 &self,
630 object_id: &ObjectId,
631 version: VersionNumber,
632 ) -> Result<Option<Object>, iota_types::storage::error::Error> {
633 self.with_store(|store| store.try_get_object_by_key(object_id, version))
634 }
635}
636
637impl<T, V: store::SimulatorStore> ReadStore for Simulacrum<T, V> {
638 fn try_get_committee(
639 &self,
640 epoch: iota_types::committee::EpochId,
641 ) -> iota_types::storage::error::Result<Option<std::sync::Arc<Committee>>> {
642 self.with_store(|store| store.try_get_committee(epoch))
643 }
644
645 fn try_get_latest_checkpoint(&self) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
646 Ok(self.with_store(|store| store.get_highest_checkpoint().unwrap()))
647 }
648
649 fn try_get_latest_epoch_id(&self) -> iota_types::storage::error::Result<EpochId> {
650 Ok(self.inner.read().unwrap().epoch_state.epoch())
651 }
652
653 fn try_get_highest_verified_checkpoint(
654 &self,
655 ) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
656 Ok(self.with_store(|store| store.get_highest_checkpoint().unwrap()))
657 }
658
659 fn try_get_highest_synced_checkpoint(
660 &self,
661 ) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
662 Ok(self.with_store(|store| store.get_highest_checkpoint().unwrap()))
663 }
664
665 fn try_get_lowest_available_checkpoint(
666 &self,
667 ) -> iota_types::storage::error::Result<iota_types::messages_checkpoint::CheckpointSequenceNumber>
668 {
669 Ok(0)
672 }
673
674 fn try_get_checkpoint_by_digest(
675 &self,
676 digest: &CheckpointDigest,
677 ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
678 Ok(self.with_store(|store| store.get_checkpoint_by_digest(digest)))
679 }
680
681 fn try_get_checkpoint_by_sequence_number(
682 &self,
683 sequence_number: iota_types::messages_checkpoint::CheckpointSequenceNumber,
684 ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
685 Ok(self.with_store(|store| store.get_checkpoint_by_sequence_number(sequence_number)))
686 }
687
688 fn try_get_checkpoint_contents_by_digest(
689 &self,
690 digest: &CheckpointContentsDigest,
691 ) -> iota_types::storage::error::Result<Option<CheckpointContents>> {
692 Ok(self.with_store(|store| store.get_checkpoint_contents_by_digest(digest)))
693 }
694
695 fn try_get_checkpoint_contents_by_sequence_number(
696 &self,
697 sequence_number: iota_types::messages_checkpoint::CheckpointSequenceNumber,
698 ) -> iota_types::storage::error::Result<Option<CheckpointContents>> {
699 Ok(self.with_store(|store| {
700 store
701 .get_checkpoint_by_sequence_number(sequence_number)
702 .and_then(|checkpoint| {
703 store.get_checkpoint_contents_by_digest(&checkpoint.contents_digest)
704 })
705 }))
706 }
707
708 fn try_get_transaction(
709 &self,
710 tx_digest: &TransactionDigest,
711 ) -> iota_types::storage::error::Result<Option<Arc<VerifiedTransaction>>> {
712 Ok(self.with_store(|store| store.get_transaction(tx_digest)))
713 }
714
715 fn try_get_transaction_effects(
716 &self,
717 tx_digest: &TransactionDigest,
718 ) -> iota_types::storage::error::Result<Option<TransactionEffects>> {
719 Ok(self.with_store(|store| store.get_transaction_effects(tx_digest)))
720 }
721
722 fn try_get_events(
723 &self,
724 digest: &TransactionDigest,
725 ) -> iota_types::storage::error::Result<Option<TransactionEvents>> {
726 Ok(self.with_store(|store| store.get_events(digest)))
727 }
728
729 fn try_get_full_checkpoint_contents_by_sequence_number(
730 &self,
731 sequence_number: iota_types::messages_checkpoint::CheckpointSequenceNumber,
732 ) -> iota_types::storage::error::Result<
733 Option<iota_types::messages_checkpoint::FullCheckpointContents>,
734 > {
735 self.with_store(|store| {
736 store
737 .try_get_checkpoint_by_sequence_number(sequence_number)?
738 .and_then(|chk| store.get_checkpoint_contents_by_digest(&chk.contents_digest))
739 .map_or(Ok(None), |contents| {
740 iota_types::messages_checkpoint::FullCheckpointContents::try_from_checkpoint_contents(
741 store,
742 contents,
743 )
744 })
745 })
746 }
747
748 fn try_get_full_checkpoint_contents(
749 &self,
750 digest: &CheckpointContentsDigest,
751 ) -> iota_types::storage::error::Result<
752 Option<iota_types::messages_checkpoint::FullCheckpointContents>,
753 > {
754 self.with_store(|store| {
755 store.get_checkpoint_contents_by_digest(digest)
756 .map_or(Ok(None), |contents| {
757 iota_types::messages_checkpoint::FullCheckpointContents::try_from_checkpoint_contents(
758 store,
759 contents,
760 )
761 })
762 })
763 }
764}
765
766impl<T: Send + Sync, V: store::SimulatorStore + Send + Sync> GrpcStateReader for Simulacrum<T, V> {
767 fn get_lowest_available_checkpoint_objects(
768 &self,
769 ) -> iota_types::storage::error::Result<CheckpointSequenceNumber> {
770 Ok(0)
771 }
772
773 fn get_chain_identifier(
774 &self,
775 ) -> iota_types::storage::error::Result<iota_types::digests::ChainIdentifier> {
776 Ok(self
777 .with_store(|store| store.get_checkpoint_by_sequence_number(0))
778 .expect("lowest available checkpoint should exist")
779 .digest()
780 .to_owned()
781 .into())
782 }
783
784 fn get_epoch_last_checkpoint(
785 &self,
786 epoch_id: iota_types::committee::EpochId,
787 ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
788 Ok(self.with_store(|store| {
789 store
790 .get_last_checkpoint_of_epoch(epoch_id)
791 .and_then(|seq| store.get_checkpoint_by_sequence_number(seq))
792 }))
793 }
794
795 fn get_epoch_info(
796 &self,
797 epoch: iota_types::committee::EpochId,
798 ) -> iota_types::storage::error::Result<Option<EpochInfoV2>> {
799 Ok(self.with_store(|store| {
800 let start_checkpoint_seq = if epoch != 0 {
801 store
802 .get_last_checkpoint_of_epoch(epoch - 1)
803 .map(|seq| Some(seq + 1))
804 .unwrap_or(None)?
805 } else {
806 0
807 };
808
809 let start_checkpoint = store.get_checkpoint_by_sequence_number(start_checkpoint_seq)?;
810
811 let system_state = self.get_system_state_for_epoch(epoch)?;
812
813 Some(EpochInfoV2 {
814 epoch,
815 start_checkpoint: start_checkpoint_seq,
816 start_timestamp_ms: start_checkpoint.data().timestamp_ms,
817 system_state,
818 epoch_close_proof: None,
821 })
822 }))
823 }
824
825 fn grpc_indexes(&self) -> Option<&dyn iota_node_storage::GrpcIndexes> {
826 Some(self)
827 }
828
829 fn get_transaction_checkpoint(
830 &self,
831 digest: &TransactionDigest,
832 ) -> iota_types::storage::error::Result<Option<CheckpointSequenceNumber>> {
833 Ok(self.with_store(|store| {
834 let highest_seq = store
835 .get_highest_checkpoint()
836 .map(|cp| cp.sequence_number())?;
837
838 for seq in (0..=highest_seq).rev() {
839 if let Some(checkpoint) = store.get_checkpoint_by_sequence_number(seq) {
840 if let Some(contents) =
841 store.get_checkpoint_contents_by_digest(&checkpoint.contents_digest)
842 {
843 if contents
844 .iter()
845 .any(|exec_digests| exec_digests.transaction == *digest)
846 {
847 return Some(checkpoint.sequence_number());
848 }
849 }
850 }
851 }
852 None
853 }))
854 }
855
856 fn get_struct_layout(
857 &self,
858 _: &StructTag,
859 ) -> iota_types::storage::error::Result<Option<move_core_types::annotated_value::MoveTypeLayout>>
860 {
861 Ok(None)
862 }
863}
864
865impl<T: Send + Sync, V: store::SimulatorStore + Send + Sync> Simulacrum<T, V> {
866 fn get_system_state_for_epoch(&self, epoch: u64) -> Option<IotaSystemState> {
867 self.with_store(|store| {
868 if let Some(historical_state) = store.get_system_state_by_epoch(epoch) {
869 return Some(historical_state.clone());
870 }
871 let current_system_state = store.get_system_state();
872 if epoch == current_system_state.epoch() {
873 return Some(current_system_state);
874 }
875 None
876 })
877 }
878}
879
880impl<T: Send + Sync, V: store::SimulatorStore + Send + Sync> GrpcIndexes for Simulacrum<T, V> {
881 fn account_owned_objects_info_iter(
882 &self,
883 _owner: Address,
884 _cursor: Option<&iota_types::storage::OwnedObjectCursor>,
885 _object_type: Option<StructTag>,
886 ) -> iota_types::storage::error::Result<
887 Box<dyn Iterator<Item = iota_types::storage::OwnedObjectIteratorItem> + '_>,
888 > {
889 Ok(Box::new(std::iter::empty()))
890 }
891
892 fn dynamic_field_iter(
893 &self,
894 _parent: iota_sdk_types::ObjectId,
895 _cursor: Option<iota_sdk_types::ObjectId>,
896 ) -> iota_types::storage::error::Result<
897 Box<
898 dyn Iterator<
899 Item = Result<
900 iota_types::storage::DynamicFieldKey,
901 typed_store_error::TypedStoreError,
902 >,
903 > + '_,
904 >,
905 > {
906 Ok(Box::new(std::iter::empty()))
907 }
908
909 fn get_coin_info(
910 &self,
911 _coin_type: &StructTag,
912 ) -> iota_types::storage::error::Result<Option<iota_types::storage::CoinInfo>> {
913 Ok(None)
914 }
915
916 fn package_versions_iter(
917 &self,
918 _original_package_id: iota_sdk_types::ObjectId,
919 _cursor: Option<u64>,
920 ) -> iota_types::storage::error::Result<
921 Box<dyn Iterator<Item = iota_types::storage::PackageVersionIteratorItem> + '_>,
922 > {
923 Ok(Box::new(std::iter::empty()))
924 }
925}
926
927impl Simulacrum {
928 pub fn transfer_txn(&self, recipient: Address) -> (TransactionEnvelope, u64) {
935 let (sender, key) = self.with_keystore(|keystore| {
936 let (s, k) = keystore.accounts().next().unwrap();
937 (*s, k.clone())
938 });
939
940 let (object, gas_coin_value) = self.with_store(|store| {
941 let object = store
942 .owned_objects(sender)
943 .find(|object| object.is_gas_coin())
944 .unwrap();
945 let gas_coin = GasCoin::try_from(object).unwrap();
946 (object.clone(), gas_coin.value())
947 });
948 let transfer_amount = gas_coin_value / 2;
949
950 let pt = {
951 let mut builder = ProgrammableTransactionBuilder::new();
952 builder.transfer_iota(recipient, Some(transfer_amount));
953 builder.finish()
954 };
955
956 let kind = TransactionKind::Programmable(pt);
957 let gas_data = GasPayment {
958 objects: vec![object.object_ref()],
959 owner: sender,
960 price: self.reference_gas_price(),
961 budget: 1_000_000_000,
962 };
963 let tx = Transaction::new_with_gas_data(kind, sender, gas_data);
964 let tx = TransactionEnvelope::from_data_and_signer(tx, vec![&key]);
965 (tx, transfer_amount)
966 }
967}
968
969#[cfg(test)]
970mod tests {
971 use std::time::Duration;
972
973 use iota_types::{
974 effects::TransactionEffectsAPI,
975 error::IotaError,
976 gas_coin::GasCoin,
977 transaction::TransactionAPI,
978 transaction_executor::VmChecks,
979 utils::{assert_size_limit_err, ptb_above_max_tx_size},
980 };
981 use rand::{SeedableRng, rngs::StdRng};
982
983 use super::*;
984
985 #[test]
986 fn deterministic_genesis() {
987 let rng = StdRng::from_seed([9; 32]);
988 let chain1 = Simulacrum::new_with_rng(rng);
989 let genesis_checkpoint_digest1 = chain1
990 .with_store(|store| *store.get_checkpoint_by_sequence_number(0).unwrap().digest());
991
992 let rng = StdRng::from_seed([9; 32]);
993 let chain2 = Simulacrum::new_with_rng(rng);
994 let genesis_checkpoint_digest2 = chain2
995 .with_store(|store| *store.get_checkpoint_by_sequence_number(0).unwrap().digest());
996
997 assert_eq!(genesis_checkpoint_digest1, genesis_checkpoint_digest2);
998
999 let rng = StdRng::from_seed([0; 32]);
1001 let chain3 = Simulacrum::new_with_rng(rng);
1002
1003 let committee1 = chain1.with_store(|store| store.get_committee_by_epoch(0).cloned());
1004 let committee3 = chain3.with_store(|store| store.get_committee_by_epoch(0).cloned());
1005 assert_ne!(committee1, committee3);
1006 }
1007
1008 #[test]
1009 fn simple() {
1010 let steps = 10;
1011 let sim = Simulacrum::new();
1012
1013 let start_time_ms = sim.with_store(|store| {
1014 let clock = store.get_clock();
1015 println!("clock: {clock:#?}");
1016 clock.timestamp_ms
1017 });
1018
1019 for _ in 0..steps {
1020 sim.advance_clock(Duration::from_millis(1));
1021 sim.create_checkpoint();
1022 sim.with_store(|store| {
1023 let clock = store.get_clock();
1024 println!("clock: {clock:#?}");
1025 });
1026 }
1027 let end_time_ms = sim.with_store(|store| store.get_clock().timestamp_ms);
1028 assert_eq!(end_time_ms - start_time_ms, steps);
1029 sim.with_store(|store| {
1030 dbg!(store.get_highest_checkpoint());
1031 });
1032 }
1033
1034 #[test]
1035 fn advance_epoch_creates_deny_rules_object() {
1036 let sim = Simulacrum::new();
1037
1038 assert!(sim.with_store(|store| {
1039 store
1040 .get_object(&iota_types::IOTA_TRANSACTION_DENY_RULES_OBJECT_ID)
1041 .is_none()
1042 }));
1043
1044 sim.advance_epoch(true);
1045
1046 let object = sim
1047 .with_store(|store| {
1048 store
1049 .get_object(&iota_types::IOTA_TRANSACTION_DENY_RULES_OBJECT_ID)
1050 .cloned()
1051 })
1052 .expect("the TransactionDenyRules object must exist after the create");
1053 assert!(object.owner().is_shared());
1054 }
1055
1056 #[test]
1059 fn walked_object_state_matches_applied_deltas() {
1060 use std::collections::BTreeSet;
1061
1062 use iota_sdk_types::TransactionDenyRulesUpdate;
1063 use iota_types::transaction_deny_rules::{
1064 get_transaction_deny_rules, get_transaction_deny_rules_obj_initial_shared_version,
1065 };
1066
1067 let sim = Simulacrum::new();
1068 sim.advance_epoch(true);
1069
1070 let initial_shared_version = sim
1071 .with_store(|store| get_transaction_deny_rules_obj_initial_shared_version(store))
1072 .unwrap()
1073 .expect("object must exist after the create");
1074
1075 let denied_address = Address::new([0xAA; 32]);
1076 let removed_address = Address::new([0xBB; 32]);
1077 let denied_package = ObjectId::new([0x2B; 32]);
1078 let update = |round, added_addresses: BTreeSet<Address>, removed_addresses| {
1079 VerifiedTransaction::new_transaction_deny_rules_update(TransactionDenyRulesUpdate {
1080 epoch: 1,
1081 round,
1082 added_addresses,
1083 removed_addresses,
1084 added_objects: BTreeSet::new(),
1085 removed_objects: BTreeSet::new(),
1086 added_packages: [denied_package].into(),
1087 removed_packages: BTreeSet::new(),
1088 package_publish_disabled: false,
1089 package_upgrade_disabled: false,
1090 shared_object_disabled: true,
1091 user_transaction_disabled: false,
1092 receiving_objects_disabled: false,
1093 move_authenticator_disabled: false,
1094 deny_rules_obj_initial_shared_version: initial_shared_version,
1095 })
1096 };
1097
1098 let (_, error) = sim
1101 .execute_transaction(
1102 update(0, [denied_address, removed_address].into(), BTreeSet::new()).into(),
1103 )
1104 .unwrap();
1105 assert!(error.is_none());
1106 let (_, error) = sim
1107 .execute_transaction(update(1, BTreeSet::new(), [removed_address].into()).into())
1108 .unwrap();
1109 assert!(error.is_none());
1110
1111 let walked = sim
1112 .with_store(|store| get_transaction_deny_rules(store))
1113 .unwrap()
1114 .expect("object must exist");
1115 assert_eq!(walked.denied_addresses, [denied_address].into());
1116 assert!(walked.denied_objects.is_empty());
1117 assert_eq!(walked.denied_packages, [denied_package].into());
1118 assert!(walked.shared_object_disabled);
1119 assert!(!walked.user_transaction_disabled);
1120 }
1121
1122 #[test]
1126 fn deny_rule_update_executes_at_the_chunk_ceiling() {
1127 use std::collections::BTreeSet;
1128
1129 use iota_sdk_types::TransactionDenyRulesUpdate;
1130 use iota_types::transaction_deny_rules::{
1131 get_transaction_deny_rules, get_transaction_deny_rules_obj_initial_shared_version,
1132 };
1133
1134 const CEILING: u64 = 2048;
1135
1136 let _guard =
1137 iota_protocol_config::ProtocolConfig::apply_overrides_for_testing(|_, mut config| {
1138 config.set_deny_rule_update_max_entries_per_tx_for_testing(CEILING);
1139 config
1140 });
1141 let sim = Simulacrum::new();
1142 sim.advance_epoch(true);
1143
1144 let initial_shared_version = sim
1145 .with_store(|store| get_transaction_deny_rules_obj_initial_shared_version(store))
1146 .unwrap()
1147 .expect("object must exist after the create");
1148
1149 let addresses: BTreeSet<Address> = (0..CEILING)
1150 .map(|i| {
1151 let mut bytes = [0u8; 32];
1152 bytes[..8].copy_from_slice(&i.to_be_bytes());
1153 Address::new(bytes)
1154 })
1155 .collect();
1156 let update = |round, added_addresses, removed_addresses| {
1157 VerifiedTransaction::new_transaction_deny_rules_update(TransactionDenyRulesUpdate {
1158 epoch: 1,
1159 round,
1160 added_addresses,
1161 removed_addresses,
1162 added_objects: BTreeSet::new(),
1163 removed_objects: BTreeSet::new(),
1164 added_packages: BTreeSet::new(),
1165 removed_packages: BTreeSet::new(),
1166 package_publish_disabled: false,
1167 package_upgrade_disabled: false,
1168 shared_object_disabled: false,
1169 user_transaction_disabled: false,
1170 receiving_objects_disabled: false,
1171 move_authenticator_disabled: false,
1172 deny_rules_obj_initial_shared_version: initial_shared_version,
1173 })
1174 };
1175
1176 let (_, error) = sim
1177 .execute_transaction(update(0, addresses.clone(), BTreeSet::new()).into())
1178 .unwrap();
1179 assert!(error.is_none());
1180 let walked = sim
1181 .with_store(|store| get_transaction_deny_rules(store))
1182 .unwrap()
1183 .expect("object must exist");
1184 assert_eq!(walked.denied_addresses.len(), CEILING as usize);
1185
1186 let (_, error) = sim
1187 .execute_transaction(update(1, BTreeSet::new(), addresses).into())
1188 .unwrap();
1189 assert!(error.is_none());
1190 let walked = sim
1191 .with_store(|store| get_transaction_deny_rules(store))
1192 .unwrap()
1193 .expect("object must exist");
1194 assert!(walked.denied_addresses.is_empty());
1195 }
1196
1197 #[test]
1198 fn simple_epoch() {
1199 let steps = 10;
1200 let sim = Simulacrum::new();
1201
1202 let start_epoch = sim.with_store(|store| store.get_highest_checkpoint().unwrap().epoch);
1203 for i in 0..steps {
1204 sim.advance_epoch(false);
1205 sim.advance_clock(Duration::from_millis(1));
1206 sim.create_checkpoint();
1207 println!("{i}");
1208 }
1209 let end_epoch = sim.with_store(|store| store.get_highest_checkpoint().unwrap().epoch);
1210 assert_eq!(end_epoch - start_epoch, steps);
1211 sim.with_store(|store| {
1212 dbg!(store.get_highest_checkpoint());
1213 });
1214 }
1215
1216 #[test]
1217 fn transfer() {
1218 let sim = Simulacrum::new();
1219 let recipient = Address::random();
1220 let (tx, transfer_amount) = sim.transfer_txn(recipient);
1221
1222 let gas_id = tx.data().transaction().gas_data().objects[0].object_id;
1223 let effects = sim.execute_transaction(tx).unwrap().0;
1224 let gas_summary = effects.gas_cost_summary();
1225 let gas_paid = gas_summary.net_gas_usage();
1226
1227 sim.with_store(|store| {
1228 assert_eq!(
1229 (transfer_amount as i64 - gas_paid) as u64,
1230 store::SimulatorStore::get_object(store, &gas_id)
1231 .and_then(|object| GasCoin::try_from(&object).ok())
1232 .unwrap()
1233 .value()
1234 );
1235
1236 assert_eq!(
1237 transfer_amount,
1238 store
1239 .owned_objects(recipient)
1240 .next()
1241 .and_then(|object| GasCoin::try_from(object).ok())
1242 .unwrap()
1243 .value()
1244 );
1245 });
1246
1247 let checkpoint = sim.create_checkpoint();
1248
1249 assert_eq!(&checkpoint.epoch_rolling_gas_cost_summary, gas_summary);
1250 assert_eq!(checkpoint.network_total_transactions, 2); }
1252
1253 #[test]
1260 fn simulate_rejects_system_transactions() {
1261 use iota_types::{error::IotaError, transaction_executor::VmChecks};
1262
1263 let sim = Simulacrum::new();
1264 let (tx, _) = sim.transfer_txn(Address::random());
1265 let mut transaction = tx.data().transaction().clone();
1266 *transaction.kind_mut() = TransactionKind::EndOfEpoch(vec![]);
1267
1268 for checks in [VmChecks::Enabled, VmChecks::Disabled] {
1269 let Err(error) = sim.simulate_transaction(transaction.clone(), checks) else {
1270 panic!("{checks:?} must not simulate a system transaction");
1271 };
1272 assert!(
1273 matches!(error, IotaError::UnsupportedFeature { .. }),
1274 "unexpected error for {checks:?}: {error:?}"
1275 );
1276 }
1277 }
1278
1279 #[test]
1282 fn simulate_distinguishes_owned_input_owner_by_check_mode() {
1283 use iota_sdk_types::ExecutionStatus;
1284 use iota_types::{
1285 error::{IotaError, UserInputError},
1286 transaction::CallArg,
1287 transaction_executor::VmChecks,
1288 };
1289
1290 let sim = Simulacrum::new();
1291 let (sender, other) = sim.with_keystore(|keystore| {
1292 let mut accounts = keystore.accounts();
1293 let (sender, _) = accounts.next().unwrap();
1294 let (other, _) = accounts.next().unwrap();
1295 (*sender, *other)
1296 });
1297
1298 let (foreign_object, gas_object) = sim.with_store(|store| {
1299 let foreign = store
1300 .owned_objects(other)
1301 .find(|object| object.is_gas_coin())
1302 .unwrap()
1303 .clone();
1304 let gas = store
1305 .owned_objects(sender)
1306 .find(|object| object.is_gas_coin())
1307 .unwrap()
1308 .clone();
1309 (foreign, gas)
1310 });
1311
1312 let pt = {
1313 let mut builder = ProgrammableTransactionBuilder::new();
1314 builder
1315 .input(CallArg::ImmutableOrOwned(foreign_object.object_ref()))
1316 .unwrap();
1317 builder.transfer_iota(Address::random(), Some(1000));
1318 builder.finish()
1319 };
1320 let kind = TransactionKind::Programmable(pt);
1321 let gas_data = GasPayment {
1322 objects: vec![gas_object.object_ref()],
1323 owner: sender,
1324 price: sim.reference_gas_price(),
1325 budget: 1_000_000_000,
1326 };
1327 let transaction = Transaction::new_with_gas_data(kind, sender, gas_data);
1328
1329 let Err(error) = sim.simulate_transaction(transaction.clone(), VmChecks::Enabled) else {
1330 panic!("a dry run must reject an owned input owned by a different address");
1331 };
1332 assert!(
1333 matches!(
1334 error,
1335 IotaError::UserInput {
1336 error: UserInputError::IncorrectUserSignature { .. }
1337 }
1338 ),
1339 "unexpected error: {error:?}"
1340 );
1341
1342 let result = sim
1343 .simulate_transaction(transaction, VmChecks::Disabled)
1344 .expect("a dev inspect must accept an owned input owned by a different address");
1345 assert_eq!(result.effects.status(), &ExecutionStatus::Success);
1346 }
1347
1348 #[test]
1349 fn simulate_rejects_a_transaction_above_the_size_limit() {
1350 let sim = Simulacrum::new();
1351 let pt = {
1352 let inner = sim.inner.read().unwrap();
1353 ptb_above_max_tx_size(inner.epoch_state.protocol_config())
1354 };
1355 let transaction =
1356 Transaction::new_programmable(Address::random(), vec![], pt, 10_000_000, 1000);
1357
1358 let Err(err) = sim.simulate_transaction(transaction, VmChecks::Enabled) else {
1359 panic!("a transaction above the size limit must be rejected");
1360 };
1361 let IotaError::UserInput { error } = &err else {
1362 panic!("got {err:?}");
1363 };
1364 assert_size_limit_err(error, "serialized transaction size exceeded maximum");
1365 }
1366}