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::rngs::OsRng;
63
64pub use self::store::{SimulatorStore, in_mem_store::InMemoryStore};
65use self::{epoch_state::EpochState, store::in_mem_store::KeyStore};
66
67pub struct Simulacrum<R = OsRng, Store: SimulatorStore = InMemoryStore> {
77 inner: RwLock<SimulacrumInner<R, Store>>,
79 deny_config: TransactionDenyConfig,
81 verifier_signing_config: VerifierSigningConfig,
82}
83
84struct SimulacrumInner<R, Store: SimulatorStore> {
85 rng: R,
86 keystore: KeyStore,
87 #[expect(unused)]
88 genesis: genesis::Genesis,
89 store: Store,
90 checkpoint_builder: MockCheckpointBuilder,
91
92 epoch_state: EpochState,
94 data_ingestion_path: Option<PathBuf>,
95}
96
97impl Simulacrum {
98 #[expect(clippy::new_without_default)]
101 pub fn new() -> Self {
102 Self::new_with_rng(OsRng)
103 }
104}
105
106impl<R> Simulacrum<R>
107where
108 R: rand::RngCore + rand::CryptoRng,
109{
110 pub fn new_with_rng(mut rng: R) -> Self {
125 let config = ConfigBuilder::new_with_temp_dir()
126 .rng(&mut rng)
127 .with_chain_start_timestamp_ms(1)
128 .deterministic_committee_size(NonZeroUsize::new(1).unwrap())
129 .build();
130 Self::new_with_network_config_in_mem(&config, rng)
131 }
132
133 pub fn new_with_protocol_version_and_accounts(
134 mut rng: R,
135 chain_start_timestamp_ms: u64,
136 protocol_version: ProtocolVersion,
137 account_configs: Vec<AccountConfig>,
138 ) -> Self {
139 let config = ConfigBuilder::new_with_temp_dir()
140 .rng(&mut rng)
141 .with_chain_start_timestamp_ms(chain_start_timestamp_ms)
142 .deterministic_committee_size(NonZeroUsize::new(1).unwrap())
143 .with_protocol_version(protocol_version)
144 .with_accounts(account_configs)
145 .build();
146 Self::new_with_network_config_in_mem(&config, rng)
147 }
148
149 fn new_with_network_config_in_mem(config: &NetworkConfig, rng: R) -> Self {
150 let store = InMemoryStore::new(&config.genesis);
151 Self::new_with_network_config_store(config, rng, store)
152 }
153}
154
155impl<R, S: store::SimulatorStore> Simulacrum<R, S> {
156 pub fn new_with_network_config_store(config: &NetworkConfig, rng: R, store: S) -> Self {
157 let keystore = KeyStore::from_network_config(config);
158 let checkpoint_builder = MockCheckpointBuilder::new(config.genesis.checkpoint());
159
160 let genesis = &config.genesis;
161 let epoch_state = EpochState::new(genesis.iota_system_object());
162
163 Self {
164 deny_config: TransactionDenyConfig::default(),
165 verifier_signing_config: VerifierSigningConfig::default(),
166 inner: RwLock::new(SimulacrumInner {
167 rng,
168 keystore,
169 genesis: genesis.clone(),
170 store,
171 checkpoint_builder,
172 epoch_state,
173 data_ingestion_path: None,
174 }),
175 }
176 }
177
178 pub fn execute_transaction(
192 &self,
193 transaction: 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, gas_coin::GasCoin, transaction::TransactionAPI,
973 };
974 use rand::{SeedableRng, rngs::StdRng};
975
976 use super::*;
977
978 #[test]
979 fn deterministic_genesis() {
980 let rng = StdRng::from_seed([9; 32]);
981 let chain1 = Simulacrum::new_with_rng(rng);
982 let genesis_checkpoint_digest1 = chain1
983 .with_store(|store| *store.get_checkpoint_by_sequence_number(0).unwrap().digest());
984
985 let rng = StdRng::from_seed([9; 32]);
986 let chain2 = Simulacrum::new_with_rng(rng);
987 let genesis_checkpoint_digest2 = chain2
988 .with_store(|store| *store.get_checkpoint_by_sequence_number(0).unwrap().digest());
989
990 assert_eq!(genesis_checkpoint_digest1, genesis_checkpoint_digest2);
991
992 let rng = StdRng::from_seed([0; 32]);
994 let chain3 = Simulacrum::new_with_rng(rng);
995
996 let committee1 = chain1.with_store(|store| store.get_committee_by_epoch(0).cloned());
997 let committee3 = chain3.with_store(|store| store.get_committee_by_epoch(0).cloned());
998 assert_ne!(committee1, committee3);
999 }
1000
1001 #[test]
1002 fn simple() {
1003 let steps = 10;
1004 let sim = Simulacrum::new();
1005
1006 let start_time_ms = sim.with_store(|store| {
1007 let clock = store.get_clock();
1008 println!("clock: {clock:#?}");
1009 clock.timestamp_ms()
1010 });
1011
1012 for _ in 0..steps {
1013 sim.advance_clock(Duration::from_millis(1));
1014 sim.create_checkpoint();
1015 sim.with_store(|store| {
1016 let clock = store.get_clock();
1017 println!("clock: {clock:#?}");
1018 });
1019 }
1020 let end_time_ms = sim.with_store(|store| store.get_clock().timestamp_ms());
1021 assert_eq!(end_time_ms - start_time_ms, steps);
1022 sim.with_store(|store| {
1023 dbg!(store.get_highest_checkpoint());
1024 });
1025 }
1026
1027 #[test]
1028 fn advance_epoch_creates_deny_rules_object() {
1029 let _guard =
1030 iota_protocol_config::ProtocolConfig::apply_overrides_for_testing(|_, mut config| {
1031 config.set_deny_rule_governance_for_testing(true);
1032 config.set_deny_rule_governance_on_chain_for_testing(true);
1033 config.set_deny_rule_update_max_entries_per_tx_for_testing(1000);
1034 config.set_deny_rule_removal_grace_round_floor_for_testing(0);
1035 config
1036 });
1037 let sim = Simulacrum::new();
1038
1039 assert!(sim.with_store(|store| {
1040 store
1041 .get_object(&iota_types::IOTA_TRANSACTION_DENY_RULES_OBJECT_ID)
1042 .is_none()
1043 }));
1044
1045 sim.advance_epoch(true);
1046
1047 let object = sim
1048 .with_store(|store| {
1049 store
1050 .get_object(&iota_types::IOTA_TRANSACTION_DENY_RULES_OBJECT_ID)
1051 .cloned()
1052 })
1053 .expect("the TransactionDenyRules object must exist after the create");
1054 assert!(object.owner().is_shared());
1055 }
1056
1057 #[test]
1060 fn walked_object_state_matches_applied_deltas() {
1061 use std::collections::BTreeSet;
1062
1063 use iota_sdk_types::TransactionDenyRulesUpdate;
1064 use iota_types::transaction_deny_rules::{
1065 get_transaction_deny_rules, get_transaction_deny_rules_obj_initial_shared_version,
1066 };
1067
1068 let _guard =
1069 iota_protocol_config::ProtocolConfig::apply_overrides_for_testing(|_, mut config| {
1070 config.set_deny_rule_governance_for_testing(true);
1071 config.set_deny_rule_governance_on_chain_for_testing(true);
1072 config.set_deny_rule_update_max_entries_per_tx_for_testing(1000);
1073 config.set_deny_rule_removal_grace_round_floor_for_testing(0);
1074 config
1075 });
1076 let sim = Simulacrum::new();
1077 sim.advance_epoch(true);
1078
1079 let initial_shared_version = sim
1080 .with_store(|store| get_transaction_deny_rules_obj_initial_shared_version(store))
1081 .unwrap()
1082 .expect("object must exist after the create");
1083
1084 let denied_address = Address::new([0xAA; 32]);
1085 let removed_address = Address::new([0xBB; 32]);
1086 let denied_package = ObjectId::new([0x2B; 32]);
1087 let update = |round, added_addresses: BTreeSet<Address>, removed_addresses| {
1088 VerifiedTransaction::new_transaction_deny_rules_update(TransactionDenyRulesUpdate {
1089 epoch: 1,
1090 round,
1091 added_addresses,
1092 removed_addresses,
1093 added_objects: BTreeSet::new(),
1094 removed_objects: BTreeSet::new(),
1095 added_packages: [denied_package].into(),
1096 removed_packages: BTreeSet::new(),
1097 package_publish_disabled: false,
1098 package_upgrade_disabled: false,
1099 shared_object_disabled: true,
1100 user_transaction_disabled: false,
1101 receiving_objects_disabled: false,
1102 move_authenticator_disabled: false,
1103 deny_rules_obj_initial_shared_version: initial_shared_version,
1104 })
1105 };
1106
1107 let (_, error) = sim
1110 .execute_transaction(
1111 update(0, [denied_address, removed_address].into(), BTreeSet::new()).into(),
1112 )
1113 .unwrap();
1114 assert!(error.is_none());
1115 let (_, error) = sim
1116 .execute_transaction(update(1, BTreeSet::new(), [removed_address].into()).into())
1117 .unwrap();
1118 assert!(error.is_none());
1119
1120 let walked = sim
1121 .with_store(|store| get_transaction_deny_rules(store))
1122 .unwrap()
1123 .expect("object must exist");
1124 assert_eq!(walked.denied_addresses, [denied_address].into());
1125 assert!(walked.denied_objects.is_empty());
1126 assert_eq!(walked.denied_packages, [denied_package].into());
1127 assert!(walked.shared_object_disabled);
1128 assert!(!walked.user_transaction_disabled);
1129 }
1130
1131 #[test]
1135 fn deny_rule_update_executes_at_the_chunk_ceiling() {
1136 use std::collections::BTreeSet;
1137
1138 use iota_sdk_types::TransactionDenyRulesUpdate;
1139 use iota_types::transaction_deny_rules::{
1140 get_transaction_deny_rules, get_transaction_deny_rules_obj_initial_shared_version,
1141 };
1142
1143 const CEILING: u64 = 2048;
1144
1145 let _guard =
1146 iota_protocol_config::ProtocolConfig::apply_overrides_for_testing(|_, mut config| {
1147 config.set_deny_rule_governance_for_testing(true);
1148 config.set_deny_rule_governance_on_chain_for_testing(true);
1149 config.set_deny_rule_update_max_entries_per_tx_for_testing(CEILING);
1150 config.set_deny_rule_removal_grace_round_floor_for_testing(0);
1151 config
1152 });
1153 let sim = Simulacrum::new();
1154 sim.advance_epoch(true);
1155
1156 let initial_shared_version = sim
1157 .with_store(|store| get_transaction_deny_rules_obj_initial_shared_version(store))
1158 .unwrap()
1159 .expect("object must exist after the create");
1160
1161 let addresses: BTreeSet<Address> = (0..CEILING)
1162 .map(|i| {
1163 let mut bytes = [0u8; 32];
1164 bytes[..8].copy_from_slice(&i.to_be_bytes());
1165 Address::new(bytes)
1166 })
1167 .collect();
1168 let update = |round, added_addresses, removed_addresses| {
1169 VerifiedTransaction::new_transaction_deny_rules_update(TransactionDenyRulesUpdate {
1170 epoch: 1,
1171 round,
1172 added_addresses,
1173 removed_addresses,
1174 added_objects: BTreeSet::new(),
1175 removed_objects: BTreeSet::new(),
1176 added_packages: BTreeSet::new(),
1177 removed_packages: BTreeSet::new(),
1178 package_publish_disabled: false,
1179 package_upgrade_disabled: false,
1180 shared_object_disabled: false,
1181 user_transaction_disabled: false,
1182 receiving_objects_disabled: false,
1183 move_authenticator_disabled: false,
1184 deny_rules_obj_initial_shared_version: initial_shared_version,
1185 })
1186 };
1187
1188 let (_, error) = sim
1189 .execute_transaction(update(0, addresses.clone(), BTreeSet::new()).into())
1190 .unwrap();
1191 assert!(error.is_none());
1192 let walked = sim
1193 .with_store(|store| get_transaction_deny_rules(store))
1194 .unwrap()
1195 .expect("object must exist");
1196 assert_eq!(walked.denied_addresses.len(), CEILING as usize);
1197
1198 let (_, error) = sim
1199 .execute_transaction(update(1, BTreeSet::new(), addresses).into())
1200 .unwrap();
1201 assert!(error.is_none());
1202 let walked = sim
1203 .with_store(|store| get_transaction_deny_rules(store))
1204 .unwrap()
1205 .expect("object must exist");
1206 assert!(walked.denied_addresses.is_empty());
1207 }
1208
1209 #[test]
1210 fn simple_epoch() {
1211 let steps = 10;
1212 let sim = Simulacrum::new();
1213
1214 let start_epoch = sim.with_store(|store| store.get_highest_checkpoint().unwrap().epoch);
1215 for i in 0..steps {
1216 sim.advance_epoch(false);
1217 sim.advance_clock(Duration::from_millis(1));
1218 sim.create_checkpoint();
1219 println!("{i}");
1220 }
1221 let end_epoch = sim.with_store(|store| store.get_highest_checkpoint().unwrap().epoch);
1222 assert_eq!(end_epoch - start_epoch, steps);
1223 sim.with_store(|store| {
1224 dbg!(store.get_highest_checkpoint());
1225 });
1226 }
1227
1228 #[test]
1229 fn transfer() {
1230 let sim = Simulacrum::new();
1231 let recipient = Address::random();
1232 let (tx, transfer_amount) = sim.transfer_txn(recipient);
1233
1234 let gas_id = tx.data().transaction().gas_data().objects[0].object_id;
1235 let effects = sim.execute_transaction(tx).unwrap().0;
1236 let gas_summary = effects.gas_cost_summary();
1237 let gas_paid = gas_summary.net_gas_usage();
1238
1239 sim.with_store(|store| {
1240 assert_eq!(
1241 (transfer_amount as i64 - gas_paid) as u64,
1242 store::SimulatorStore::get_object(store, &gas_id)
1243 .and_then(|object| GasCoin::try_from(&object).ok())
1244 .unwrap()
1245 .value()
1246 );
1247
1248 assert_eq!(
1249 transfer_amount,
1250 store
1251 .owned_objects(recipient)
1252 .next()
1253 .and_then(|object| GasCoin::try_from(object).ok())
1254 .unwrap()
1255 .value()
1256 );
1257 });
1258
1259 let checkpoint = sim.create_checkpoint();
1260
1261 assert_eq!(&checkpoint.epoch_rolling_gas_cost_summary, gas_summary);
1262 assert_eq!(checkpoint.network_total_transactions, 2); }
1264}