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, CheckpointContentsDigest, CheckpointDigest, ConsensusCommitDigest,
36 EndOfEpochTransactionKind, GasPayment, ObjectId, StructTag, SystemPackage, Transaction,
37 TransactionDigest, TransactionEffects, TransactionEvents, TransactionKind,
38 checkpoint::{CheckpointContents, EndOfEpochData},
39};
40use iota_storage::blob::{Blob, BlobEncoding};
41use iota_swarm_config::{
42 genesis_config::AccountConfig, network_config::NetworkConfig,
43 network_config_builder::ConfigBuilder,
44};
45use iota_types::{
46 base_types::{AuthorityName, EpochId, VersionNumber},
47 committee::Committee,
48 crypto::AuthoritySignature,
49 error::ExecutionError,
50 gas_coin::{GasCoin, NANOS_PER_IOTA},
51 inner_temporary_store::InnerTemporaryStore,
52 iota_system_state::{
53 IotaSystemState, IotaSystemStateTrait, epoch_start_iota_system_state::EpochStartSystemState,
54 },
55 messages_checkpoint::{CheckpointContentsExt, CheckpointSequenceNumber, VerifiedCheckpoint},
56 mock_checkpoint_builder::{MockCheckpointBuilder, ValidatorKeypairProvider},
57 object::Object,
58 programmable_transaction_builder::ProgrammableTransactionBuilder,
59 signature::VerifyParams,
60 storage::{EpochInfoV2, ObjectStore, ReadStore, TransactionInfo},
61 transaction::{TransactionAPI, TransactionEnvelope, VerifiedTransaction},
62};
63use rand::rngs::OsRng;
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 = OsRng, 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(OsRng)
104 }
105}
106
107impl<R> Simulacrum<R>
108where
109 R: rand::RngCore + 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_accounts(account_configs)
146 .build();
147 Self::new_with_network_config_in_mem(&config, rng)
148 }
149
150 fn new_with_network_config_in_mem(config: &NetworkConfig, rng: R) -> Self {
151 let store = InMemoryStore::new(&config.genesis);
152 Self::new_with_network_config_store(config, rng, store)
153 }
154}
155
156impl<R, S: store::SimulatorStore> Simulacrum<R, S> {
157 pub fn new_with_network_config_store(config: &NetworkConfig, rng: R, store: S) -> Self {
158 let keystore = KeyStore::from_network_config(config);
159 let checkpoint_builder = MockCheckpointBuilder::new(config.genesis.checkpoint());
160
161 let genesis = &config.genesis;
162 let epoch_state = EpochState::new(genesis.iota_system_object());
163
164 Self {
165 deny_config: TransactionDenyConfig::default(),
166 verifier_signing_config: VerifierSigningConfig::default(),
167 inner: RwLock::new(SimulacrumInner {
168 rng,
169 keystore,
170 genesis: genesis.clone(),
171 store,
172 checkpoint_builder,
173 epoch_state,
174 data_ingestion_path: None,
175 }),
176 }
177 }
178
179 pub fn execute_transaction(
193 &self,
194 transaction: TransactionEnvelope,
195 ) -> anyhow::Result<(TransactionEffects, Option<ExecutionError>)> {
196 let mut inner = self.inner.write().unwrap();
197 let transaction = transaction.try_into_verified_for_testing(&VerifyParams::default())?;
198
199 let (inner_temporary_store, _, effects, execution_error_opt) =
200 inner.epoch_state.execute_transaction(
201 &inner.store,
202 &self.deny_config,
203 &self.verifier_signing_config,
204 &transaction,
205 )?;
206
207 let InnerTemporaryStore {
208 written, events, ..
209 } = inner_temporary_store;
210
211 inner.store.insert_executed_transaction(
212 transaction.clone(),
213 effects.clone(),
214 events,
215 written,
216 );
217
218 inner
220 .checkpoint_builder
221 .push_transaction(transaction, effects.clone());
222 Ok((effects, execution_error_opt.err()))
223 }
224
225 pub fn simulate_transaction(
228 &self,
229 transaction: Transaction,
230 checks: iota_types::transaction_executor::VmChecks,
231 ) -> iota_types::error::IotaResult<iota_types::transaction_executor::SimulateTransactionResult>
232 {
233 let inner = self.inner.read().unwrap();
234 inner.epoch_state.simulate_transaction(
235 &inner.store,
236 &self.deny_config,
237 &self.verifier_signing_config,
238 transaction,
239 checks,
240 )
241 }
242
243 pub fn create_checkpoint(&self) -> VerifiedCheckpoint {
246 let (checkpoint, contents) = {
247 let mut inner = self.inner.write().unwrap();
248 let committee = CommitteeWithKeys::new(&inner.keystore, inner.epoch_state.committee());
249 let timestamp_ms = inner.store.get_clock().timestamp_ms();
250 let (checkpoint, contents, _) =
251 inner.checkpoint_builder.build(&committee, timestamp_ms);
252 inner.store.insert_checkpoint(checkpoint.clone());
253 inner.store.insert_checkpoint_contents(contents.clone());
254 (checkpoint, contents)
255 };
256 self.process_data_ingestion(checkpoint.clone(), contents)
258 .unwrap();
259 checkpoint
260 }
261
262 pub fn advance_clock(&self, duration: std::time::Duration) -> TransactionEffects {
267 let mut inner = self.inner.write().unwrap();
268 let epoch = inner.epoch_state.epoch();
269 let round = inner.epoch_state.next_consensus_round();
270 let timestamp_ms = inner.store.get_clock().timestamp_ms() + duration.as_millis() as u64;
271 drop(inner);
272
273 let consensus_commit_prologue_transaction =
274 VerifiedTransaction::new_consensus_commit_prologue_v1(
275 epoch,
276 round,
277 timestamp_ms,
278 ConsensusCommitDigest::default(),
279 Vec::new(),
280 );
281
282 self.execute_transaction(consensus_commit_prologue_transaction.into())
283 .expect("advancing the clock cannot fail")
284 .0
285 }
286
287 pub fn advance_epoch(&self) {
301 let inner = self.inner.read().unwrap();
302 let current_epoch = inner.epoch_state.epoch();
303 let next_epoch = current_epoch + 1;
304 let next_epoch_protocol_version = inner.epoch_state.protocol_version();
305 let gas_cost_summary = inner
306 .checkpoint_builder
307 .epoch_rolling_gas_cost_summary()
308 .clone();
309 let epoch_start_timestamp_ms = inner.store.get_clock().timestamp_ms();
310 let pass_validator_scores = inner
311 .epoch_state
312 .protocol_config()
313 .pass_validator_scores_to_advance_epoch();
314 let adjust_rewards_by_score = inner
315 .epoch_state
316 .protocol_config()
317 .adjust_rewards_by_score();
318 let committee_size = inner.epoch_state.committee().num_members();
319 drop(inner);
320
321 const MAX_SCORE: u64 = u16::MAX as u64 + 1;
325 let scores = vec![MAX_SCORE; committee_size];
326
327 let next_epoch_system_package_bytes: Vec<SystemPackage> = vec![];
328 let kinds = vec![if pass_validator_scores {
331 EndOfEpochTransactionKind::new_change_epoch_v4(
332 next_epoch,
333 next_epoch_protocol_version.as_u64(),
334 gas_cost_summary.storage_cost,
335 gas_cost_summary.computation_cost,
336 gas_cost_summary.computation_cost_burned,
337 gas_cost_summary.storage_rebate,
338 gas_cost_summary.non_refundable_storage_fee,
339 epoch_start_timestamp_ms,
340 next_epoch_system_package_bytes,
341 vec![],
342 scores,
343 adjust_rewards_by_score,
344 )
345 } else {
346 EndOfEpochTransactionKind::new_change_epoch_v3(
347 next_epoch,
348 next_epoch_protocol_version.as_u64(),
349 gas_cost_summary.storage_cost,
350 gas_cost_summary.computation_cost,
351 gas_cost_summary.computation_cost_burned,
352 gas_cost_summary.storage_rebate,
353 gas_cost_summary.non_refundable_storage_fee,
354 epoch_start_timestamp_ms,
355 next_epoch_system_package_bytes,
356 vec![],
357 )
358 }];
359
360 let tx = VerifiedTransaction::new_end_of_epoch_transaction(kinds);
361 let (effects, execution_error) = self
362 .execute_transaction(tx.into())
363 .expect("advancing the epoch cannot fail");
364 assert!(
365 execution_error.is_none(),
366 "the end-of-epoch transaction failed: {execution_error:?}, {effects:?}"
367 );
368
369 let (checkpoint, contents, new_epoch_state) = {
370 let mut inner = self.inner.write().unwrap();
371 let new_epoch_state = EpochState::new(inner.store.get_system_state());
372 let end_of_epoch_data = EndOfEpochData {
373 next_epoch_committee: new_epoch_state.committee().committee_members(),
374 next_epoch_protocol_version: next_epoch_protocol_version.as_u64(),
375 epoch_commitments: vec![],
376 epoch_supply_change: 0,
378 };
379 let (checkpoint, contents, _) = {
380 let committee =
381 CommitteeWithKeys::new(&inner.keystore, inner.epoch_state.committee());
382 let timestamp_ms = inner.store.get_clock().timestamp_ms();
383 inner.checkpoint_builder.build_end_of_epoch(
384 &committee,
385 timestamp_ms,
386 next_epoch,
387 end_of_epoch_data,
388 )
389 };
390
391 inner.store.insert_checkpoint(checkpoint.clone());
392 inner.store.insert_checkpoint_contents(contents.clone());
393 inner
394 .store
395 .update_last_checkpoint_of_epoch(current_epoch, checkpoint.sequence_number());
396 (checkpoint, contents, new_epoch_state)
397 };
398
399 self.process_data_ingestion(checkpoint, contents).unwrap();
401
402 let mut inner = self.inner.write().unwrap();
404 inner.epoch_state = new_epoch_state;
405 }
406
407 pub fn with_store<F, T>(&self, f: F) -> T
412 where
413 F: FnOnce(&S) -> T,
414 {
415 let inner = self.inner.read().unwrap();
416 f(&inner.store)
417 }
418
419 pub fn with_keystore<F, T>(&self, f: F) -> T
424 where
425 F: FnOnce(&KeyStore) -> T,
426 {
427 let inner = self.inner.read().unwrap();
428 f(&inner.keystore)
429 }
430
431 pub fn epoch_start_state(&self) -> EpochStartSystemState {
432 let inner = self.inner.read().unwrap();
433 inner.epoch_state.epoch_start_state()
434 }
435
436 pub fn with_rng<F, T>(&self, f: F) -> T
443 where
444 F: FnOnce(&mut R) -> T,
445 {
446 let mut inner = self.inner.write().unwrap();
447 f(&mut inner.rng)
448 }
449
450 pub fn reference_gas_price(&self) -> u64 {
452 self.inner.read().unwrap().epoch_state.reference_gas_price()
453 }
454
455 pub fn request_gas(&self, address: Address, amount: u64) -> Result<TransactionEffects> {
472 let (sender, key) = self.with_keystore(|keystore| -> Result<(Address, _)> {
476 let (s, k) = keystore
477 .accounts()
478 .next()
479 .ok_or_else(|| anyhow!("no accounts available in keystore"))?;
480 Ok((*s, k.clone()))
481 })?;
482
483 let object = self
484 .with_store(|store| {
485 store.owned_objects(sender).find(|object| {
486 object.is_gas_coin()
487 && object.get_coin_value_unchecked() > amount + NANOS_PER_IOTA
488 })
489 })
490 .ok_or_else(|| {
491 anyhow!("unable to find a coin with enough to satisfy request for {amount} Nanos")
492 })?;
493
494 let gas_data = GasPayment {
495 objects: vec![object.object_ref()],
496 owner: sender,
497 price: self.reference_gas_price(),
498 budget: NANOS_PER_IOTA,
499 };
500
501 let pt = {
502 let mut builder =
503 iota_types::programmable_transaction_builder::ProgrammableTransactionBuilder::new();
504 builder.transfer_iota(address, Some(amount));
505 builder.finish()
506 };
507
508 let kind = TransactionKind::Programmable(pt);
509 let tx = iota_sdk_types::Transaction::new_with_gas_data(kind, sender, gas_data);
510 let tx = TransactionEnvelope::from_data_and_signer(tx, vec![&key]);
511
512 self.execute_transaction(tx).map(|x| x.0)
513 }
514
515 pub fn set_data_ingestion_path(&self, data_ingestion_path: PathBuf) {
516 let checkpoint = {
517 let mut inner = self.inner.write().unwrap();
518 inner.data_ingestion_path = Some(data_ingestion_path);
519 let checkpoint = inner.store.get_checkpoint_by_sequence_number(0).unwrap();
520 let contents = inner
521 .store
522 .get_checkpoint_contents_by_digest(&checkpoint.contents_digest);
523 (checkpoint, contents)
524 };
525 if let (checkpoint, Some(contents)) = checkpoint {
527 self.process_data_ingestion(checkpoint, contents).unwrap();
528 }
529 }
530
531 pub fn override_next_checkpoint_number(&self, number: CheckpointSequenceNumber) {
537 let mut inner = self.inner.write().unwrap();
538 let committee = CommitteeWithKeys::new(&inner.keystore, inner.epoch_state.committee());
539 inner
540 .checkpoint_builder
541 .override_next_checkpoint_number(number, &committee);
542 }
543
544 fn process_data_ingestion(
547 &self,
548 checkpoint: VerifiedCheckpoint,
549 checkpoint_contents: CheckpointContents,
550 ) -> anyhow::Result<()> {
551 let path = self.inner.read().unwrap().data_ingestion_path.clone();
552 if let Some(data_path) = path {
553 let file_name = format!("{}.chk", checkpoint.sequence_number);
554 let checkpoint_data = self.try_get_checkpoint_data(checkpoint, checkpoint_contents)?;
555 std::fs::create_dir_all(&data_path)?;
556 let blob = Blob::encode(&checkpoint_data, BlobEncoding::Bcs)?;
557 std::fs::write(data_path.join(file_name), blob.to_bytes())?;
558 }
559 Ok(())
560 }
561}
562
563pub struct CommitteeWithKeys {
564 keystore: KeyStore,
565 committee: Committee,
566}
567
568impl CommitteeWithKeys {
569 fn new(keystore: &KeyStore, committee: &Committee) -> Self {
570 Self {
571 keystore: keystore.clone(),
572 committee: committee.clone(),
573 }
574 }
575
576 pub fn keystore(&self) -> &KeyStore {
577 &self.keystore
578 }
579}
580
581impl ValidatorKeypairProvider for CommitteeWithKeys {
582 fn get_validator_key(&self, name: &AuthorityName) -> &dyn Signer<AuthoritySignature> {
583 self.keystore.validator(name).unwrap()
584 }
585
586 fn get_committee(&self) -> &Committee {
587 &self.committee
588 }
589}
590
591impl<T, V: store::SimulatorStore> ObjectStore for Simulacrum<T, V> {
592 fn try_get_object(
593 &self,
594 object_id: &ObjectId,
595 ) -> Result<Option<Object>, iota_types::storage::error::Error> {
596 self.with_store(|store| store.try_get_object(object_id))
597 }
598
599 fn try_get_object_by_key(
600 &self,
601 object_id: &ObjectId,
602 version: VersionNumber,
603 ) -> Result<Option<Object>, iota_types::storage::error::Error> {
604 self.with_store(|store| store.try_get_object_by_key(object_id, version))
605 }
606}
607
608impl<T, V: store::SimulatorStore> ReadStore for Simulacrum<T, V> {
609 fn try_get_committee(
610 &self,
611 epoch: iota_types::committee::EpochId,
612 ) -> iota_types::storage::error::Result<Option<std::sync::Arc<Committee>>> {
613 self.with_store(|store| store.try_get_committee(epoch))
614 }
615
616 fn try_get_latest_checkpoint(&self) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
617 Ok(self.with_store(|store| store.get_highest_checkpoint().unwrap()))
618 }
619
620 fn try_get_latest_epoch_id(&self) -> iota_types::storage::error::Result<EpochId> {
621 Ok(self.inner.read().unwrap().epoch_state.epoch())
622 }
623
624 fn try_get_highest_verified_checkpoint(
625 &self,
626 ) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
627 Ok(self.with_store(|store| store.get_highest_checkpoint().unwrap()))
628 }
629
630 fn try_get_highest_synced_checkpoint(
631 &self,
632 ) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
633 Ok(self.with_store(|store| store.get_highest_checkpoint().unwrap()))
634 }
635
636 fn try_get_lowest_available_checkpoint(
637 &self,
638 ) -> iota_types::storage::error::Result<iota_types::messages_checkpoint::CheckpointSequenceNumber>
639 {
640 Ok(0)
643 }
644
645 fn try_get_checkpoint_by_digest(
646 &self,
647 digest: &CheckpointDigest,
648 ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
649 Ok(self.with_store(|store| store.get_checkpoint_by_digest(digest)))
650 }
651
652 fn try_get_checkpoint_by_sequence_number(
653 &self,
654 sequence_number: iota_types::messages_checkpoint::CheckpointSequenceNumber,
655 ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
656 Ok(self.with_store(|store| store.get_checkpoint_by_sequence_number(sequence_number)))
657 }
658
659 fn try_get_checkpoint_contents_by_digest(
660 &self,
661 digest: &CheckpointContentsDigest,
662 ) -> iota_types::storage::error::Result<Option<CheckpointContents>> {
663 Ok(self.with_store(|store| store.get_checkpoint_contents_by_digest(digest)))
664 }
665
666 fn try_get_checkpoint_contents_by_sequence_number(
667 &self,
668 sequence_number: iota_types::messages_checkpoint::CheckpointSequenceNumber,
669 ) -> iota_types::storage::error::Result<Option<CheckpointContents>> {
670 Ok(self.with_store(|store| {
671 store
672 .get_checkpoint_by_sequence_number(sequence_number)
673 .and_then(|checkpoint| {
674 store.get_checkpoint_contents_by_digest(&checkpoint.contents_digest)
675 })
676 }))
677 }
678
679 fn try_get_transaction(
680 &self,
681 tx_digest: &TransactionDigest,
682 ) -> iota_types::storage::error::Result<Option<Arc<VerifiedTransaction>>> {
683 Ok(self.with_store(|store| store.get_transaction(tx_digest)))
684 }
685
686 fn try_get_transaction_effects(
687 &self,
688 tx_digest: &TransactionDigest,
689 ) -> iota_types::storage::error::Result<Option<TransactionEffects>> {
690 Ok(self.with_store(|store| store.get_transaction_effects(tx_digest)))
691 }
692
693 fn try_get_events(
694 &self,
695 digest: &TransactionDigest,
696 ) -> iota_types::storage::error::Result<Option<TransactionEvents>> {
697 Ok(self.with_store(|store| store.get_events(digest)))
698 }
699
700 fn try_get_full_checkpoint_contents_by_sequence_number(
701 &self,
702 sequence_number: iota_types::messages_checkpoint::CheckpointSequenceNumber,
703 ) -> iota_types::storage::error::Result<
704 Option<iota_types::messages_checkpoint::FullCheckpointContents>,
705 > {
706 self.with_store(|store| {
707 store
708 .try_get_checkpoint_by_sequence_number(sequence_number)?
709 .and_then(|chk| store.get_checkpoint_contents_by_digest(&chk.contents_digest))
710 .map_or(Ok(None), |contents| {
711 iota_types::messages_checkpoint::FullCheckpointContents::try_from_checkpoint_contents(
712 store,
713 contents,
714 )
715 })
716 })
717 }
718
719 fn try_get_full_checkpoint_contents(
720 &self,
721 digest: &CheckpointContentsDigest,
722 ) -> iota_types::storage::error::Result<
723 Option<iota_types::messages_checkpoint::FullCheckpointContents>,
724 > {
725 self.with_store(|store| {
726 store.get_checkpoint_contents_by_digest(digest)
727 .map_or(Ok(None), |contents| {
728 iota_types::messages_checkpoint::FullCheckpointContents::try_from_checkpoint_contents(
729 store,
730 contents,
731 )
732 })
733 })
734 }
735}
736
737impl<T: Send + Sync, V: store::SimulatorStore + Send + Sync> GrpcStateReader for Simulacrum<T, V> {
738 fn get_lowest_available_checkpoint_objects(
739 &self,
740 ) -> iota_types::storage::error::Result<CheckpointSequenceNumber> {
741 Ok(0)
742 }
743
744 fn get_chain_identifier(
745 &self,
746 ) -> iota_types::storage::error::Result<iota_types::digests::ChainIdentifier> {
747 Ok(self
748 .with_store(|store| store.get_checkpoint_by_sequence_number(0))
749 .expect("lowest available checkpoint should exist")
750 .digest()
751 .to_owned()
752 .into())
753 }
754
755 fn get_epoch_last_checkpoint(
756 &self,
757 epoch_id: iota_types::committee::EpochId,
758 ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
759 Ok(self.with_store(|store| {
760 store
761 .get_last_checkpoint_of_epoch(epoch_id)
762 .and_then(|seq| store.get_checkpoint_by_sequence_number(seq))
763 }))
764 }
765
766 fn get_epoch_info(
767 &self,
768 epoch: iota_types::committee::EpochId,
769 ) -> iota_types::storage::error::Result<Option<EpochInfoV2>> {
770 Ok(self.with_store(|store| {
771 let start_checkpoint_seq = if epoch != 0 {
772 store
773 .get_last_checkpoint_of_epoch(epoch - 1)
774 .map(|seq| Some(seq + 1))
775 .unwrap_or(None)?
776 } else {
777 0
778 };
779
780 let start_checkpoint = store.get_checkpoint_by_sequence_number(start_checkpoint_seq)?;
781
782 let system_state = self.get_system_state_for_epoch(epoch)?;
783
784 Some(EpochInfoV2 {
785 epoch,
786 start_checkpoint: start_checkpoint_seq,
787 start_timestamp_ms: start_checkpoint.data().timestamp_ms,
788 system_state,
789 epoch_close_proof: None,
792 })
793 }))
794 }
795
796 fn grpc_indexes(&self) -> Option<&dyn iota_node_storage::GrpcIndexes> {
797 Some(self)
798 }
799
800 fn get_struct_layout(
801 &self,
802 _: &StructTag,
803 ) -> iota_types::storage::error::Result<Option<move_core_types::annotated_value::MoveTypeLayout>>
804 {
805 Ok(None)
806 }
807}
808
809impl<T: Send + Sync, V: store::SimulatorStore + Send + Sync> Simulacrum<T, V> {
810 fn get_system_state_for_epoch(&self, epoch: u64) -> Option<IotaSystemState> {
811 self.with_store(|store| {
812 if let Some(historical_state) = store.get_system_state_by_epoch(epoch) {
813 return Some(historical_state.clone());
814 }
815 let current_system_state = store.get_system_state();
816 if epoch == current_system_state.epoch() {
817 return Some(current_system_state);
818 }
819 None
820 })
821 }
822}
823
824impl<T: Send + Sync, V: store::SimulatorStore + Send + Sync> GrpcIndexes for Simulacrum<T, V> {
825 fn get_transaction_info(
826 &self,
827 digest: &TransactionDigest,
828 ) -> iota_types::storage::error::Result<Option<TransactionInfo>> {
829 Ok(self.with_store(|store| {
830 let highest_seq = store
831 .get_highest_checkpoint()
832 .map(|cp| cp.sequence_number())?;
833
834 for seq in (0..=highest_seq).rev() {
835 if let Some(checkpoint) = store.get_checkpoint_by_sequence_number(seq) {
836 if let Some(contents) =
837 store.get_checkpoint_contents_by_digest(&checkpoint.contents_digest)
838 {
839 if contents
840 .iter()
841 .any(|exec_digests| exec_digests.transaction == *digest)
842 {
843 return Some(TransactionInfo {
847 checkpoint: checkpoint.sequence_number(),
848 object_types: HashMap::new(),
849 });
850 }
851 }
852 }
853 }
854 None
855 }))
856 }
857
858 fn account_owned_objects_info_iter(
859 &self,
860 _owner: Address,
861 _cursor: Option<&iota_types::storage::OwnedObjectCursor>,
862 _object_type: Option<StructTag>,
863 ) -> iota_types::storage::error::Result<
864 Box<dyn Iterator<Item = iota_types::storage::OwnedObjectIteratorItem> + '_>,
865 > {
866 Ok(Box::new(std::iter::empty()))
867 }
868
869 fn dynamic_field_iter(
870 &self,
871 _parent: iota_sdk_types::ObjectId,
872 _cursor: Option<iota_sdk_types::ObjectId>,
873 ) -> iota_types::storage::error::Result<
874 Box<
875 dyn Iterator<
876 Item = Result<
877 iota_types::storage::DynamicFieldKey,
878 typed_store_error::TypedStoreError,
879 >,
880 > + '_,
881 >,
882 > {
883 Ok(Box::new(std::iter::empty()))
884 }
885
886 fn get_coin_info(
887 &self,
888 _coin_type: &StructTag,
889 ) -> iota_types::storage::error::Result<Option<iota_types::storage::CoinInfo>> {
890 Ok(None)
891 }
892
893 fn package_versions_iter(
894 &self,
895 _original_package_id: iota_sdk_types::ObjectId,
896 _cursor: Option<u64>,
897 ) -> iota_types::storage::error::Result<
898 Box<dyn Iterator<Item = iota_types::storage::PackageVersionIteratorItem> + '_>,
899 > {
900 Ok(Box::new(std::iter::empty()))
901 }
902}
903
904impl Simulacrum {
905 pub fn transfer_txn(&self, recipient: Address) -> (TransactionEnvelope, u64) {
912 let (sender, key) = self.with_keystore(|keystore| {
913 let (s, k) = keystore.accounts().next().unwrap();
914 (*s, k.clone())
915 });
916
917 let (object, gas_coin_value) = self.with_store(|store| {
918 let object = store
919 .owned_objects(sender)
920 .find(|object| object.is_gas_coin())
921 .unwrap();
922 let gas_coin = GasCoin::try_from(object).unwrap();
923 (object.clone(), gas_coin.value())
924 });
925 let transfer_amount = gas_coin_value / 2;
926
927 let pt = {
928 let mut builder = ProgrammableTransactionBuilder::new();
929 builder.transfer_iota(recipient, Some(transfer_amount));
930 builder.finish()
931 };
932
933 let kind = TransactionKind::Programmable(pt);
934 let gas_data = GasPayment {
935 objects: vec![object.object_ref()],
936 owner: sender,
937 price: self.reference_gas_price(),
938 budget: 1_000_000_000,
939 };
940 let tx = Transaction::new_with_gas_data(kind, sender, gas_data);
941 let tx = TransactionEnvelope::from_data_and_signer(tx, vec![&key]);
942 (tx, transfer_amount)
943 }
944}
945
946#[cfg(test)]
947mod tests {
948 use std::time::Duration;
949
950 use iota_types::{
951 effects::TransactionEffectsAPI, gas_coin::GasCoin, transaction::TransactionAPI,
952 };
953 use rand::{SeedableRng, rngs::StdRng};
954
955 use super::*;
956
957 #[test]
958 fn deterministic_genesis() {
959 let rng = StdRng::from_seed([9; 32]);
960 let chain1 = Simulacrum::new_with_rng(rng);
961 let genesis_checkpoint_digest1 = chain1
962 .with_store(|store| *store.get_checkpoint_by_sequence_number(0).unwrap().digest());
963
964 let rng = StdRng::from_seed([9; 32]);
965 let chain2 = Simulacrum::new_with_rng(rng);
966 let genesis_checkpoint_digest2 = chain2
967 .with_store(|store| *store.get_checkpoint_by_sequence_number(0).unwrap().digest());
968
969 assert_eq!(genesis_checkpoint_digest1, genesis_checkpoint_digest2);
970
971 let rng = StdRng::from_seed([0; 32]);
973 let chain3 = Simulacrum::new_with_rng(rng);
974
975 let committee1 = chain1.with_store(|store| store.get_committee_by_epoch(0).cloned());
976 let committee3 = chain3.with_store(|store| store.get_committee_by_epoch(0).cloned());
977 assert_ne!(committee1, committee3);
978 }
979
980 #[test]
981 fn simple() {
982 let steps = 10;
983 let sim = Simulacrum::new();
984
985 let start_time_ms = sim.with_store(|store| {
986 let clock = store.get_clock();
987 println!("clock: {clock:#?}");
988 clock.timestamp_ms()
989 });
990
991 for _ in 0..steps {
992 sim.advance_clock(Duration::from_millis(1));
993 sim.create_checkpoint();
994 sim.with_store(|store| {
995 let clock = store.get_clock();
996 println!("clock: {clock:#?}");
997 });
998 }
999 let end_time_ms = sim.with_store(|store| store.get_clock().timestamp_ms());
1000 assert_eq!(end_time_ms - start_time_ms, steps);
1001 sim.with_store(|store| {
1002 dbg!(store.get_highest_checkpoint());
1003 });
1004 }
1005
1006 #[test]
1007 fn simple_epoch() {
1008 let steps = 10;
1009 let sim = Simulacrum::new();
1010
1011 let start_epoch = sim.with_store(|store| store.get_highest_checkpoint().unwrap().epoch);
1012 for i in 0..steps {
1013 sim.advance_epoch();
1014 sim.advance_clock(Duration::from_millis(1));
1015 sim.create_checkpoint();
1016 println!("{i}");
1017 }
1018 let end_epoch = sim.with_store(|store| store.get_highest_checkpoint().unwrap().epoch);
1019 assert_eq!(end_epoch - start_epoch, steps);
1020 sim.with_store(|store| {
1021 dbg!(store.get_highest_checkpoint());
1022 });
1023 }
1024
1025 #[test]
1026 fn transfer() {
1027 let sim = Simulacrum::new();
1028 let recipient = Address::random();
1029 let (tx, transfer_amount) = sim.transfer_txn(recipient);
1030
1031 let gas_id = tx.data().transaction().gas_data().objects[0].object_id;
1032 let effects = sim.execute_transaction(tx).unwrap().0;
1033 let gas_summary = effects.gas_cost_summary();
1034 let gas_paid = gas_summary.net_gas_usage();
1035
1036 sim.with_store(|store| {
1037 assert_eq!(
1038 (transfer_amount as i64 - gas_paid) as u64,
1039 store::SimulatorStore::get_object(store, &gas_id)
1040 .and_then(|object| GasCoin::try_from(&object).ok())
1041 .unwrap()
1042 .value()
1043 );
1044
1045 assert_eq!(
1046 transfer_amount,
1047 store
1048 .owned_objects(recipient)
1049 .next()
1050 .and_then(|object| GasCoin::try_from(object).ok())
1051 .unwrap()
1052 .value()
1053 );
1054 });
1055
1056 let checkpoint = sim.create_checkpoint();
1057
1058 assert_eq!(&checkpoint.epoch_rolling_gas_cost_summary, gas_summary);
1059 assert_eq!(checkpoint.network_total_transactions, 2); }
1061}