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 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_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: TransactionEnvelope,
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: Transaction,
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, create_deny_rules_object: bool) -> TransactionEffects {
306 let inner = self.inner.read().unwrap();
307 let current_epoch = inner.epoch_state.epoch();
308 let next_epoch = current_epoch + 1;
309 let next_epoch_protocol_version = inner.epoch_state.protocol_version();
310 let gas_cost_summary = inner
311 .checkpoint_builder
312 .epoch_rolling_gas_cost_summary()
313 .clone();
314 let epoch_start_timestamp_ms = inner.store.get_clock().timestamp_ms();
315 let pass_validator_scores = inner
316 .epoch_state
317 .protocol_config()
318 .pass_validator_scores_to_advance_epoch();
319 let adjust_rewards_by_score = inner
320 .epoch_state
321 .protocol_config()
322 .adjust_rewards_by_score();
323 let committee_size = inner.epoch_state.committee().num_members();
324 drop(inner);
325
326 const MAX_SCORE: u64 = u16::MAX as u64 + 1;
330 let scores = vec![MAX_SCORE; committee_size];
331
332 let next_epoch_system_package_bytes: Vec<SystemPackage> = vec![];
333 let mut kinds = Vec::new();
334 if create_deny_rules_object {
335 kinds.push(EndOfEpochTransactionKind::TransactionDenyRulesCreate);
336 }
337 kinds.push(if pass_validator_scores {
340 EndOfEpochTransactionKind::new_change_epoch_v4(
341 next_epoch,
342 next_epoch_protocol_version.as_u64(),
343 gas_cost_summary.storage_cost,
344 gas_cost_summary.computation_cost,
345 gas_cost_summary.computation_cost_burned,
346 gas_cost_summary.storage_rebate,
347 gas_cost_summary.non_refundable_storage_fee,
348 epoch_start_timestamp_ms,
349 next_epoch_system_package_bytes,
350 vec![],
351 scores,
352 adjust_rewards_by_score,
353 )
354 } else {
355 EndOfEpochTransactionKind::new_change_epoch_v3(
356 next_epoch,
357 next_epoch_protocol_version.as_u64(),
358 gas_cost_summary.storage_cost,
359 gas_cost_summary.computation_cost,
360 gas_cost_summary.computation_cost_burned,
361 gas_cost_summary.storage_rebate,
362 gas_cost_summary.non_refundable_storage_fee,
363 epoch_start_timestamp_ms,
364 next_epoch_system_package_bytes,
365 vec![],
366 )
367 });
368
369 let tx = VerifiedTransaction::new_end_of_epoch_transaction(kinds);
370 let (effects, execution_error) = self
371 .execute_transaction(tx.into())
372 .expect("advancing the epoch cannot fail");
373 assert!(
374 execution_error.is_none(),
375 "the end-of-epoch transaction failed: {execution_error:?}, {effects:?}"
376 );
377
378 let (checkpoint, contents, new_epoch_state) = {
379 let mut inner = self.inner.write().unwrap();
380 let system_state = inner.store.get_system_state();
381 let prev_protocol_config = inner.epoch_state.protocol_config();
385 let new_epoch_state =
386 if system_state.protocol_version() == prev_protocol_config.version.as_u64() {
387 EpochState::new_with_config(prev_protocol_config.clone(), system_state)
388 } else {
389 EpochState::new(system_state)
390 };
391 let end_of_epoch_data = EndOfEpochData {
392 next_epoch_committee: new_epoch_state.committee().committee_members(),
393 next_epoch_protocol_version: next_epoch_protocol_version.as_u64(),
394 epoch_commitments: vec![],
395 epoch_supply_change: 0,
397 };
398 let (checkpoint, contents, _) = {
399 let committee =
400 CommitteeWithKeys::new(&inner.keystore, inner.epoch_state.committee());
401 let timestamp_ms = inner.store.get_clock().timestamp_ms();
402 inner.checkpoint_builder.build_end_of_epoch(
403 &committee,
404 timestamp_ms,
405 next_epoch,
406 end_of_epoch_data,
407 )
408 };
409
410 inner.store.insert_checkpoint(checkpoint.clone());
411 inner.store.insert_checkpoint_contents(contents.clone());
412 inner
413 .store
414 .update_last_checkpoint_of_epoch(current_epoch, checkpoint.sequence_number());
415 (checkpoint, contents, new_epoch_state)
416 };
417
418 self.process_data_ingestion(checkpoint, contents).unwrap();
420
421 let mut inner = self.inner.write().unwrap();
423 inner.epoch_state = new_epoch_state;
424
425 effects
426 }
427
428 pub fn with_store<F, T>(&self, f: F) -> T
433 where
434 F: FnOnce(&S) -> T,
435 {
436 let inner = self.inner.read().unwrap();
437 f(&inner.store)
438 }
439
440 pub fn with_keystore<F, T>(&self, f: F) -> T
445 where
446 F: FnOnce(&KeyStore) -> T,
447 {
448 let inner = self.inner.read().unwrap();
449 f(&inner.keystore)
450 }
451
452 pub fn epoch_start_state(&self) -> EpochStartSystemState {
453 let inner = self.inner.read().unwrap();
454 inner.epoch_state.epoch_start_state()
455 }
456
457 pub fn with_rng<F, T>(&self, f: F) -> T
464 where
465 F: FnOnce(&mut R) -> T,
466 {
467 let mut inner = self.inner.write().unwrap();
468 f(&mut inner.rng)
469 }
470
471 pub fn reference_gas_price(&self) -> u64 {
473 self.inner.read().unwrap().epoch_state.reference_gas_price()
474 }
475
476 pub fn request_gas(&self, address: Address, amount: u64) -> Result<TransactionEffects> {
493 let (sender, key) = self.with_keystore(|keystore| -> Result<(Address, _)> {
497 let (s, k) = keystore
498 .accounts()
499 .next()
500 .ok_or_else(|| anyhow!("no accounts available in keystore"))?;
501 Ok((*s, k.clone()))
502 })?;
503
504 let object = self
505 .with_store(|store| {
506 store.owned_objects(sender).find(|object| {
507 object.is_gas_coin()
508 && object.get_coin_value_unchecked() > amount + NANOS_PER_IOTA
509 })
510 })
511 .ok_or_else(|| {
512 anyhow!("unable to find a coin with enough to satisfy request for {amount} Nanos")
513 })?;
514
515 let gas_data = GasPayment {
516 objects: vec![object.object_ref()],
517 owner: sender,
518 price: self.reference_gas_price(),
519 budget: NANOS_PER_IOTA,
520 };
521
522 let pt = {
523 let mut builder =
524 iota_types::programmable_transaction_builder::ProgrammableTransactionBuilder::new();
525 builder.transfer_iota(address, Some(amount));
526 builder.finish()
527 };
528
529 let kind = TransactionKind::Programmable(pt);
530 let tx = iota_sdk_types::Transaction::new_with_gas_data(kind, sender, gas_data);
531 let tx = TransactionEnvelope::from_data_and_signer(tx, vec![&key]);
532
533 self.execute_transaction(tx).map(|x| x.0)
534 }
535
536 pub fn set_data_ingestion_path(&self, data_ingestion_path: PathBuf) {
537 let checkpoint = {
538 let mut inner = self.inner.write().unwrap();
539 inner.data_ingestion_path = Some(data_ingestion_path);
540 let checkpoint = inner.store.get_checkpoint_by_sequence_number(0).unwrap();
541 let contents = inner
542 .store
543 .get_checkpoint_contents_by_digest(&checkpoint.contents_digest);
544 (checkpoint, contents)
545 };
546 if let (checkpoint, Some(contents)) = checkpoint {
548 self.process_data_ingestion(checkpoint, contents).unwrap();
549 }
550 }
551
552 pub fn override_next_checkpoint_number(&self, number: CheckpointSequenceNumber) {
558 let mut inner = self.inner.write().unwrap();
559 let committee = CommitteeWithKeys::new(&inner.keystore, inner.epoch_state.committee());
560 inner
561 .checkpoint_builder
562 .override_next_checkpoint_number(number, &committee);
563 }
564
565 fn process_data_ingestion(
568 &self,
569 checkpoint: VerifiedCheckpoint,
570 checkpoint_contents: CheckpointContents,
571 ) -> anyhow::Result<()> {
572 let path = self.inner.read().unwrap().data_ingestion_path.clone();
573 if let Some(data_path) = path {
574 let file_name = format!("{}.chk", checkpoint.sequence_number);
575 let checkpoint_data = self.try_get_checkpoint_data(checkpoint, checkpoint_contents)?;
576 std::fs::create_dir_all(&data_path)?;
577 let blob = Blob::encode(&checkpoint_data, BlobEncoding::Bcs)?;
578 std::fs::write(data_path.join(file_name), blob.to_bytes())?;
579 }
580 Ok(())
581 }
582}
583
584pub struct CommitteeWithKeys {
585 keystore: KeyStore,
586 committee: Committee,
587}
588
589impl CommitteeWithKeys {
590 fn new(keystore: &KeyStore, committee: &Committee) -> Self {
591 Self {
592 keystore: keystore.clone(),
593 committee: committee.clone(),
594 }
595 }
596
597 pub fn keystore(&self) -> &KeyStore {
598 &self.keystore
599 }
600}
601
602impl ValidatorKeypairProvider for CommitteeWithKeys {
603 fn get_validator_key(&self, name: &AuthorityName) -> &dyn Signer<AuthoritySignature> {
604 self.keystore.validator(name).unwrap()
605 }
606
607 fn get_committee(&self) -> &Committee {
608 &self.committee
609 }
610}
611
612impl<T, V: store::SimulatorStore> ObjectStore for Simulacrum<T, V> {
613 fn try_get_object(
614 &self,
615 object_id: &ObjectId,
616 ) -> Result<Option<Object>, iota_types::storage::error::Error> {
617 self.with_store(|store| store.try_get_object(object_id))
618 }
619
620 fn try_get_object_by_key(
621 &self,
622 object_id: &ObjectId,
623 version: VersionNumber,
624 ) -> Result<Option<Object>, iota_types::storage::error::Error> {
625 self.with_store(|store| store.try_get_object_by_key(object_id, version))
626 }
627}
628
629impl<T, V: store::SimulatorStore> ReadStore for Simulacrum<T, V> {
630 fn try_get_committee(
631 &self,
632 epoch: iota_types::committee::EpochId,
633 ) -> iota_types::storage::error::Result<Option<std::sync::Arc<Committee>>> {
634 self.with_store(|store| store.try_get_committee(epoch))
635 }
636
637 fn try_get_latest_checkpoint(&self) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
638 Ok(self.with_store(|store| store.get_highest_checkpoint().unwrap()))
639 }
640
641 fn try_get_latest_epoch_id(&self) -> iota_types::storage::error::Result<EpochId> {
642 Ok(self.inner.read().unwrap().epoch_state.epoch())
643 }
644
645 fn try_get_highest_verified_checkpoint(
646 &self,
647 ) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
648 Ok(self.with_store(|store| store.get_highest_checkpoint().unwrap()))
649 }
650
651 fn try_get_highest_synced_checkpoint(
652 &self,
653 ) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
654 Ok(self.with_store(|store| store.get_highest_checkpoint().unwrap()))
655 }
656
657 fn try_get_lowest_available_checkpoint(
658 &self,
659 ) -> iota_types::storage::error::Result<iota_types::messages_checkpoint::CheckpointSequenceNumber>
660 {
661 Ok(0)
664 }
665
666 fn try_get_checkpoint_by_digest(
667 &self,
668 digest: &CheckpointDigest,
669 ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
670 Ok(self.with_store(|store| store.get_checkpoint_by_digest(digest)))
671 }
672
673 fn try_get_checkpoint_by_sequence_number(
674 &self,
675 sequence_number: iota_types::messages_checkpoint::CheckpointSequenceNumber,
676 ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
677 Ok(self.with_store(|store| store.get_checkpoint_by_sequence_number(sequence_number)))
678 }
679
680 fn try_get_checkpoint_contents_by_digest(
681 &self,
682 digest: &CheckpointContentsDigest,
683 ) -> iota_types::storage::error::Result<Option<CheckpointContents>> {
684 Ok(self.with_store(|store| store.get_checkpoint_contents_by_digest(digest)))
685 }
686
687 fn try_get_checkpoint_contents_by_sequence_number(
688 &self,
689 sequence_number: iota_types::messages_checkpoint::CheckpointSequenceNumber,
690 ) -> iota_types::storage::error::Result<Option<CheckpointContents>> {
691 Ok(self.with_store(|store| {
692 store
693 .get_checkpoint_by_sequence_number(sequence_number)
694 .and_then(|checkpoint| {
695 store.get_checkpoint_contents_by_digest(&checkpoint.contents_digest)
696 })
697 }))
698 }
699
700 fn try_get_transaction(
701 &self,
702 tx_digest: &TransactionDigest,
703 ) -> iota_types::storage::error::Result<Option<Arc<VerifiedTransaction>>> {
704 Ok(self.with_store(|store| store.get_transaction(tx_digest)))
705 }
706
707 fn try_get_transaction_effects(
708 &self,
709 tx_digest: &TransactionDigest,
710 ) -> iota_types::storage::error::Result<Option<TransactionEffects>> {
711 Ok(self.with_store(|store| store.get_transaction_effects(tx_digest)))
712 }
713
714 fn try_get_events(
715 &self,
716 digest: &TransactionDigest,
717 ) -> iota_types::storage::error::Result<Option<TransactionEvents>> {
718 Ok(self.with_store(|store| store.get_events(digest)))
719 }
720
721 fn try_get_full_checkpoint_contents_by_sequence_number(
722 &self,
723 sequence_number: iota_types::messages_checkpoint::CheckpointSequenceNumber,
724 ) -> iota_types::storage::error::Result<
725 Option<iota_types::messages_checkpoint::FullCheckpointContents>,
726 > {
727 self.with_store(|store| {
728 store
729 .try_get_checkpoint_by_sequence_number(sequence_number)?
730 .and_then(|chk| store.get_checkpoint_contents_by_digest(&chk.contents_digest))
731 .map_or(Ok(None), |contents| {
732 iota_types::messages_checkpoint::FullCheckpointContents::try_from_checkpoint_contents(
733 store,
734 contents,
735 )
736 })
737 })
738 }
739
740 fn try_get_full_checkpoint_contents(
741 &self,
742 digest: &CheckpointContentsDigest,
743 ) -> iota_types::storage::error::Result<
744 Option<iota_types::messages_checkpoint::FullCheckpointContents>,
745 > {
746 self.with_store(|store| {
747 store.get_checkpoint_contents_by_digest(digest)
748 .map_or(Ok(None), |contents| {
749 iota_types::messages_checkpoint::FullCheckpointContents::try_from_checkpoint_contents(
750 store,
751 contents,
752 )
753 })
754 })
755 }
756}
757
758impl<T: Send + Sync, V: store::SimulatorStore + Send + Sync> GrpcStateReader for Simulacrum<T, V> {
759 fn get_lowest_available_checkpoint_objects(
760 &self,
761 ) -> iota_types::storage::error::Result<CheckpointSequenceNumber> {
762 Ok(0)
763 }
764
765 fn get_chain_identifier(
766 &self,
767 ) -> iota_types::storage::error::Result<iota_types::digests::ChainIdentifier> {
768 Ok(self
769 .with_store(|store| store.get_checkpoint_by_sequence_number(0))
770 .expect("lowest available checkpoint should exist")
771 .digest()
772 .to_owned()
773 .into())
774 }
775
776 fn get_epoch_last_checkpoint(
777 &self,
778 epoch_id: iota_types::committee::EpochId,
779 ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
780 Ok(self.with_store(|store| {
781 store
782 .get_last_checkpoint_of_epoch(epoch_id)
783 .and_then(|seq| store.get_checkpoint_by_sequence_number(seq))
784 }))
785 }
786
787 fn get_epoch_info(
788 &self,
789 epoch: iota_types::committee::EpochId,
790 ) -> iota_types::storage::error::Result<Option<EpochInfoV2>> {
791 Ok(self.with_store(|store| {
792 let start_checkpoint_seq = if epoch != 0 {
793 store
794 .get_last_checkpoint_of_epoch(epoch - 1)
795 .map(|seq| Some(seq + 1))
796 .unwrap_or(None)?
797 } else {
798 0
799 };
800
801 let start_checkpoint = store.get_checkpoint_by_sequence_number(start_checkpoint_seq)?;
802
803 let system_state = self.get_system_state_for_epoch(epoch)?;
804
805 Some(EpochInfoV2 {
806 epoch,
807 start_checkpoint: start_checkpoint_seq,
808 start_timestamp_ms: start_checkpoint.data().timestamp_ms,
809 system_state,
810 epoch_close_proof: None,
813 })
814 }))
815 }
816
817 fn grpc_indexes(&self) -> Option<&dyn iota_node_storage::GrpcIndexes> {
818 Some(self)
819 }
820
821 fn get_struct_layout(
822 &self,
823 _: &StructTag,
824 ) -> iota_types::storage::error::Result<Option<move_core_types::annotated_value::MoveTypeLayout>>
825 {
826 Ok(None)
827 }
828}
829
830impl<T: Send + Sync, V: store::SimulatorStore + Send + Sync> Simulacrum<T, V> {
831 fn get_system_state_for_epoch(&self, epoch: u64) -> Option<IotaSystemState> {
832 self.with_store(|store| {
833 if let Some(historical_state) = store.get_system_state_by_epoch(epoch) {
834 return Some(historical_state.clone());
835 }
836 let current_system_state = store.get_system_state();
837 if epoch == current_system_state.epoch() {
838 return Some(current_system_state);
839 }
840 None
841 })
842 }
843}
844
845impl<T: Send + Sync, V: store::SimulatorStore + Send + Sync> GrpcIndexes for Simulacrum<T, V> {
846 fn get_transaction_info(
847 &self,
848 digest: &TransactionDigest,
849 ) -> iota_types::storage::error::Result<Option<TransactionInfo>> {
850 Ok(self.with_store(|store| {
851 let highest_seq = store
852 .get_highest_checkpoint()
853 .map(|cp| cp.sequence_number())?;
854
855 for seq in (0..=highest_seq).rev() {
856 if let Some(checkpoint) = store.get_checkpoint_by_sequence_number(seq) {
857 if let Some(contents) =
858 store.get_checkpoint_contents_by_digest(&checkpoint.contents_digest)
859 {
860 if contents
861 .iter()
862 .any(|exec_digests| exec_digests.transaction == *digest)
863 {
864 return Some(TransactionInfo {
868 checkpoint: checkpoint.sequence_number(),
869 object_types: HashMap::new(),
870 });
871 }
872 }
873 }
874 }
875 None
876 }))
877 }
878
879 fn account_owned_objects_info_iter(
880 &self,
881 _owner: Address,
882 _cursor: Option<&iota_types::storage::OwnedObjectCursor>,
883 _object_type: Option<StructTag>,
884 ) -> iota_types::storage::error::Result<
885 Box<dyn Iterator<Item = iota_types::storage::OwnedObjectIteratorItem> + '_>,
886 > {
887 Ok(Box::new(std::iter::empty()))
888 }
889
890 fn dynamic_field_iter(
891 &self,
892 _parent: iota_sdk_types::ObjectId,
893 _cursor: Option<iota_sdk_types::ObjectId>,
894 ) -> iota_types::storage::error::Result<
895 Box<
896 dyn Iterator<
897 Item = Result<
898 iota_types::storage::DynamicFieldKey,
899 typed_store_error::TypedStoreError,
900 >,
901 > + '_,
902 >,
903 > {
904 Ok(Box::new(std::iter::empty()))
905 }
906
907 fn get_coin_info(
908 &self,
909 _coin_type: &StructTag,
910 ) -> iota_types::storage::error::Result<Option<iota_types::storage::CoinInfo>> {
911 Ok(None)
912 }
913
914 fn package_versions_iter(
915 &self,
916 _original_package_id: iota_sdk_types::ObjectId,
917 _cursor: Option<u64>,
918 ) -> iota_types::storage::error::Result<
919 Box<dyn Iterator<Item = iota_types::storage::PackageVersionIteratorItem> + '_>,
920 > {
921 Ok(Box::new(std::iter::empty()))
922 }
923}
924
925impl Simulacrum {
926 pub fn transfer_txn(&self, recipient: Address) -> (TransactionEnvelope, u64) {
933 let (sender, key) = self.with_keystore(|keystore| {
934 let (s, k) = keystore.accounts().next().unwrap();
935 (*s, k.clone())
936 });
937
938 let (object, gas_coin_value) = self.with_store(|store| {
939 let object = store
940 .owned_objects(sender)
941 .find(|object| object.is_gas_coin())
942 .unwrap();
943 let gas_coin = GasCoin::try_from(object).unwrap();
944 (object.clone(), gas_coin.value())
945 });
946 let transfer_amount = gas_coin_value / 2;
947
948 let pt = {
949 let mut builder = ProgrammableTransactionBuilder::new();
950 builder.transfer_iota(recipient, Some(transfer_amount));
951 builder.finish()
952 };
953
954 let kind = TransactionKind::Programmable(pt);
955 let gas_data = GasPayment {
956 objects: vec![object.object_ref()],
957 owner: sender,
958 price: self.reference_gas_price(),
959 budget: 1_000_000_000,
960 };
961 let tx = Transaction::new_with_gas_data(kind, sender, gas_data);
962 let tx = TransactionEnvelope::from_data_and_signer(tx, vec![&key]);
963 (tx, transfer_amount)
964 }
965}
966
967#[cfg(test)]
968mod tests {
969 use std::time::Duration;
970
971 use iota_types::{
972 effects::TransactionEffectsAPI,
973 error::IotaError,
974 gas_coin::GasCoin,
975 transaction::TransactionAPI,
976 transaction_executor::VmChecks,
977 utils::{assert_size_limit_err, ptb_above_max_tx_size},
978 };
979 use rand::{SeedableRng, rngs::StdRng};
980
981 use super::*;
982
983 #[test]
984 fn deterministic_genesis() {
985 let rng = StdRng::from_seed([9; 32]);
986 let chain1 = Simulacrum::new_with_rng(rng);
987 let genesis_checkpoint_digest1 = chain1
988 .with_store(|store| *store.get_checkpoint_by_sequence_number(0).unwrap().digest());
989
990 let rng = StdRng::from_seed([9; 32]);
991 let chain2 = Simulacrum::new_with_rng(rng);
992 let genesis_checkpoint_digest2 = chain2
993 .with_store(|store| *store.get_checkpoint_by_sequence_number(0).unwrap().digest());
994
995 assert_eq!(genesis_checkpoint_digest1, genesis_checkpoint_digest2);
996
997 let rng = StdRng::from_seed([0; 32]);
999 let chain3 = Simulacrum::new_with_rng(rng);
1000
1001 let committee1 = chain1.with_store(|store| store.get_committee_by_epoch(0).cloned());
1002 let committee3 = chain3.with_store(|store| store.get_committee_by_epoch(0).cloned());
1003 assert_ne!(committee1, committee3);
1004 }
1005
1006 #[test]
1007 fn simple() {
1008 let steps = 10;
1009 let sim = Simulacrum::new();
1010
1011 let start_time_ms = sim.with_store(|store| {
1012 let clock = store.get_clock();
1013 println!("clock: {clock:#?}");
1014 clock.timestamp_ms()
1015 });
1016
1017 for _ in 0..steps {
1018 sim.advance_clock(Duration::from_millis(1));
1019 sim.create_checkpoint();
1020 sim.with_store(|store| {
1021 let clock = store.get_clock();
1022 println!("clock: {clock:#?}");
1023 });
1024 }
1025 let end_time_ms = sim.with_store(|store| store.get_clock().timestamp_ms());
1026 assert_eq!(end_time_ms - start_time_ms, steps);
1027 sim.with_store(|store| {
1028 dbg!(store.get_highest_checkpoint());
1029 });
1030 }
1031
1032 #[test]
1033 fn advance_epoch_creates_deny_rules_object() {
1034 let _guard =
1035 iota_protocol_config::ProtocolConfig::apply_overrides_for_testing(|_, mut config| {
1036 config.set_deny_rule_governance_for_testing(true);
1037 config.set_deny_rule_governance_on_chain_for_testing(true);
1038 config.set_deny_rule_update_max_entries_per_tx_for_testing(1000);
1039 config.set_deny_rule_removal_grace_round_floor_for_testing(0);
1040 config
1041 });
1042 let sim = Simulacrum::new();
1043
1044 assert!(sim.with_store(|store| {
1045 store
1046 .get_object(&iota_types::IOTA_TRANSACTION_DENY_RULES_OBJECT_ID)
1047 .is_none()
1048 }));
1049
1050 sim.advance_epoch(true);
1051
1052 let object = sim
1053 .with_store(|store| {
1054 store
1055 .get_object(&iota_types::IOTA_TRANSACTION_DENY_RULES_OBJECT_ID)
1056 .cloned()
1057 })
1058 .expect("the TransactionDenyRules object must exist after the create");
1059 assert!(object.owner().is_shared());
1060 }
1061
1062 #[test]
1065 fn walked_object_state_matches_applied_deltas() {
1066 use std::collections::BTreeSet;
1067
1068 use iota_sdk_types::TransactionDenyRulesUpdate;
1069 use iota_types::transaction_deny_rules::{
1070 get_transaction_deny_rules, get_transaction_deny_rules_obj_initial_shared_version,
1071 };
1072
1073 let _guard =
1074 iota_protocol_config::ProtocolConfig::apply_overrides_for_testing(|_, mut config| {
1075 config.set_deny_rule_governance_for_testing(true);
1076 config.set_deny_rule_governance_on_chain_for_testing(true);
1077 config.set_deny_rule_update_max_entries_per_tx_for_testing(1000);
1078 config.set_deny_rule_removal_grace_round_floor_for_testing(0);
1079 config
1080 });
1081 let sim = Simulacrum::new();
1082 sim.advance_epoch(true);
1083
1084 let initial_shared_version = sim
1085 .with_store(|store| get_transaction_deny_rules_obj_initial_shared_version(store))
1086 .unwrap()
1087 .expect("object must exist after the create");
1088
1089 let denied_address = Address::new([0xAA; 32]);
1090 let removed_address = Address::new([0xBB; 32]);
1091 let denied_package = ObjectId::new([0x2B; 32]);
1092 let update = |round, added_addresses: BTreeSet<Address>, removed_addresses| {
1093 VerifiedTransaction::new_transaction_deny_rules_update(TransactionDenyRulesUpdate {
1094 epoch: 1,
1095 round,
1096 added_addresses,
1097 removed_addresses,
1098 added_objects: BTreeSet::new(),
1099 removed_objects: BTreeSet::new(),
1100 added_packages: [denied_package].into(),
1101 removed_packages: BTreeSet::new(),
1102 package_publish_disabled: false,
1103 package_upgrade_disabled: false,
1104 shared_object_disabled: true,
1105 user_transaction_disabled: false,
1106 receiving_objects_disabled: false,
1107 move_authenticator_disabled: false,
1108 deny_rules_obj_initial_shared_version: initial_shared_version,
1109 })
1110 };
1111
1112 let (_, error) = sim
1115 .execute_transaction(
1116 update(0, [denied_address, removed_address].into(), BTreeSet::new()).into(),
1117 )
1118 .unwrap();
1119 assert!(error.is_none());
1120 let (_, error) = sim
1121 .execute_transaction(update(1, BTreeSet::new(), [removed_address].into()).into())
1122 .unwrap();
1123 assert!(error.is_none());
1124
1125 let walked = sim
1126 .with_store(|store| get_transaction_deny_rules(store))
1127 .unwrap()
1128 .expect("object must exist");
1129 assert_eq!(walked.denied_addresses, [denied_address].into());
1130 assert!(walked.denied_objects.is_empty());
1131 assert_eq!(walked.denied_packages, [denied_package].into());
1132 assert!(walked.shared_object_disabled);
1133 assert!(!walked.user_transaction_disabled);
1134 }
1135
1136 #[test]
1140 fn deny_rule_update_executes_at_the_chunk_ceiling() {
1141 use std::collections::BTreeSet;
1142
1143 use iota_sdk_types::TransactionDenyRulesUpdate;
1144 use iota_types::transaction_deny_rules::{
1145 get_transaction_deny_rules, get_transaction_deny_rules_obj_initial_shared_version,
1146 };
1147
1148 const CEILING: u64 = 2048;
1149
1150 let _guard =
1151 iota_protocol_config::ProtocolConfig::apply_overrides_for_testing(|_, mut config| {
1152 config.set_deny_rule_governance_for_testing(true);
1153 config.set_deny_rule_governance_on_chain_for_testing(true);
1154 config.set_deny_rule_update_max_entries_per_tx_for_testing(CEILING);
1155 config.set_deny_rule_removal_grace_round_floor_for_testing(0);
1156 config
1157 });
1158 let sim = Simulacrum::new();
1159 sim.advance_epoch(true);
1160
1161 let initial_shared_version = sim
1162 .with_store(|store| get_transaction_deny_rules_obj_initial_shared_version(store))
1163 .unwrap()
1164 .expect("object must exist after the create");
1165
1166 let addresses: BTreeSet<Address> = (0..CEILING)
1167 .map(|i| {
1168 let mut bytes = [0u8; 32];
1169 bytes[..8].copy_from_slice(&i.to_be_bytes());
1170 Address::new(bytes)
1171 })
1172 .collect();
1173 let update = |round, added_addresses, removed_addresses| {
1174 VerifiedTransaction::new_transaction_deny_rules_update(TransactionDenyRulesUpdate {
1175 epoch: 1,
1176 round,
1177 added_addresses,
1178 removed_addresses,
1179 added_objects: BTreeSet::new(),
1180 removed_objects: BTreeSet::new(),
1181 added_packages: BTreeSet::new(),
1182 removed_packages: BTreeSet::new(),
1183 package_publish_disabled: false,
1184 package_upgrade_disabled: false,
1185 shared_object_disabled: false,
1186 user_transaction_disabled: false,
1187 receiving_objects_disabled: false,
1188 move_authenticator_disabled: false,
1189 deny_rules_obj_initial_shared_version: initial_shared_version,
1190 })
1191 };
1192
1193 let (_, error) = sim
1194 .execute_transaction(update(0, addresses.clone(), BTreeSet::new()).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_eq!(walked.denied_addresses.len(), CEILING as usize);
1202
1203 let (_, error) = sim
1204 .execute_transaction(update(1, BTreeSet::new(), addresses).into())
1205 .unwrap();
1206 assert!(error.is_none());
1207 let walked = sim
1208 .with_store(|store| get_transaction_deny_rules(store))
1209 .unwrap()
1210 .expect("object must exist");
1211 assert!(walked.denied_addresses.is_empty());
1212 }
1213
1214 #[test]
1215 fn simple_epoch() {
1216 let steps = 10;
1217 let sim = Simulacrum::new();
1218
1219 let start_epoch = sim.with_store(|store| store.get_highest_checkpoint().unwrap().epoch);
1220 for i in 0..steps {
1221 sim.advance_epoch(false);
1222 sim.advance_clock(Duration::from_millis(1));
1223 sim.create_checkpoint();
1224 println!("{i}");
1225 }
1226 let end_epoch = sim.with_store(|store| store.get_highest_checkpoint().unwrap().epoch);
1227 assert_eq!(end_epoch - start_epoch, steps);
1228 sim.with_store(|store| {
1229 dbg!(store.get_highest_checkpoint());
1230 });
1231 }
1232
1233 #[test]
1234 fn transfer() {
1235 let sim = Simulacrum::new();
1236 let recipient = Address::random();
1237 let (tx, transfer_amount) = sim.transfer_txn(recipient);
1238
1239 let gas_id = tx.data().transaction().gas_data().objects[0].object_id;
1240 let effects = sim.execute_transaction(tx).unwrap().0;
1241 let gas_summary = effects.gas_cost_summary();
1242 let gas_paid = gas_summary.net_gas_usage();
1243
1244 sim.with_store(|store| {
1245 assert_eq!(
1246 (transfer_amount as i64 - gas_paid) as u64,
1247 store::SimulatorStore::get_object(store, &gas_id)
1248 .and_then(|object| GasCoin::try_from(&object).ok())
1249 .unwrap()
1250 .value()
1251 );
1252
1253 assert_eq!(
1254 transfer_amount,
1255 store
1256 .owned_objects(recipient)
1257 .next()
1258 .and_then(|object| GasCoin::try_from(object).ok())
1259 .unwrap()
1260 .value()
1261 );
1262 });
1263
1264 let checkpoint = sim.create_checkpoint();
1265
1266 assert_eq!(&checkpoint.epoch_rolling_gas_cost_summary, gas_summary);
1267 assert_eq!(checkpoint.network_total_transactions, 2); }
1269
1270 #[test]
1271 fn simulate_rejects_a_transaction_above_the_size_limit() {
1272 let sim = Simulacrum::new();
1273 let pt = {
1274 let inner = sim.inner.read().unwrap();
1275 ptb_above_max_tx_size(inner.epoch_state.protocol_config())
1276 };
1277 let transaction =
1278 Transaction::new_programmable(Address::random(), vec![], pt, 10_000_000, 1000);
1279
1280 let Err(err) = sim.simulate_transaction(transaction, VmChecks::Enabled) else {
1281 panic!("a transaction above the size limit must be rejected");
1282 };
1283 let IotaError::UserInput { error } = &err else {
1284 panic!("got {err:?}");
1285 };
1286 assert_size_limit_err(error, "serialized transaction size exceeded maximum");
1287 }
1288}