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