1mod epoch_state;
16pub mod store;
17
18use std::{num::NonZeroUsize, path::PathBuf, sync::Arc};
19
20use anyhow::{Result, anyhow};
21use fastcrypto::traits::Signer;
22use iota_config::{
23 genesis, transaction_deny_config::TransactionDenyConfig,
24 verifier_signing_config::VerifierSigningConfig,
25};
26use iota_protocol_config::ProtocolVersion;
27use iota_storage::blob::{Blob, BlobEncoding};
28use iota_swarm_config::{
29 genesis_config::AccountConfig, network_config::NetworkConfig,
30 network_config_builder::ConfigBuilder,
31};
32use iota_types::{
33 base_types::{AuthorityName, IotaAddress, ObjectID, VersionNumber},
34 committee::Committee,
35 crypto::AuthoritySignature,
36 digests::ConsensusCommitDigest,
37 effects::TransactionEffects,
38 error::ExecutionError,
39 gas_coin::{GasCoin, NANOS_PER_IOTA},
40 inner_temporary_store::InnerTemporaryStore,
41 iota_system_state::epoch_start_iota_system_state::EpochStartSystemState,
42 messages_checkpoint::{
43 CheckpointContents, CheckpointSequenceNumber, EndOfEpochData, VerifiedCheckpoint,
44 },
45 mock_checkpoint_builder::{MockCheckpointBuilder, ValidatorKeypairProvider},
46 object::Object,
47 programmable_transaction_builder::ProgrammableTransactionBuilder,
48 signature::VerifyParams,
49 storage::{ObjectStore, ReadStore, RestStateReader},
50 transaction::{
51 EndOfEpochTransactionKind, GasData, Transaction, TransactionData, TransactionKind,
52 VerifiedTransaction,
53 },
54};
55use rand::rngs::OsRng;
56
57pub use self::store::{SimulatorStore, in_mem_store::InMemoryStore};
58use self::{epoch_state::EpochState, store::in_mem_store::KeyStore};
59
60pub struct Simulacrum<R = OsRng, Store: SimulatorStore = InMemoryStore> {
70 rng: R,
71 keystore: KeyStore,
72 #[expect(unused)]
73 genesis: genesis::Genesis,
74 store: Store,
75 checkpoint_builder: MockCheckpointBuilder,
76
77 epoch_state: EpochState,
79
80 deny_config: TransactionDenyConfig,
82 data_ingestion_path: Option<PathBuf>,
83 verifier_signing_config: VerifierSigningConfig,
84}
85
86impl Simulacrum {
87 #[expect(clippy::new_without_default)]
90 pub fn new() -> Self {
91 Self::new_with_rng(OsRng)
92 }
93}
94
95impl<R> Simulacrum<R>
96where
97 R: rand::RngCore + rand::CryptoRng,
98{
99 pub fn new_with_rng(mut rng: R) -> Self {
114 let config = ConfigBuilder::new_with_temp_dir()
115 .rng(&mut rng)
116 .with_chain_start_timestamp_ms(1)
117 .deterministic_committee_size(NonZeroUsize::new(1).unwrap())
118 .build();
119 Self::new_with_network_config_in_mem(&config, rng)
120 }
121
122 pub fn new_with_protocol_version_and_accounts(
123 mut rng: R,
124 chain_start_timestamp_ms: u64,
125 protocol_version: ProtocolVersion,
126 account_configs: Vec<AccountConfig>,
127 ) -> Self {
128 let config = ConfigBuilder::new_with_temp_dir()
129 .rng(&mut rng)
130 .with_chain_start_timestamp_ms(chain_start_timestamp_ms)
131 .deterministic_committee_size(NonZeroUsize::new(1).unwrap())
132 .with_protocol_version(protocol_version)
133 .with_accounts(account_configs)
134 .build();
135 Self::new_with_network_config_in_mem(&config, rng)
136 }
137
138 fn new_with_network_config_in_mem(config: &NetworkConfig, rng: R) -> Self {
139 let store = InMemoryStore::new(&config.genesis);
140 Self::new_with_network_config_store(config, rng, store)
141 }
142}
143
144impl<R, S: store::SimulatorStore> Simulacrum<R, S> {
145 pub fn new_with_network_config_store(config: &NetworkConfig, rng: R, store: S) -> Self {
146 let keystore = KeyStore::from_network_config(config);
147 let checkpoint_builder = MockCheckpointBuilder::new(config.genesis.checkpoint());
148
149 let genesis = &config.genesis;
150 let epoch_state = EpochState::new(genesis.iota_system_object());
151
152 Self {
153 rng,
154 keystore,
155 genesis: genesis.clone(),
156 store,
157 checkpoint_builder,
158 epoch_state,
159 deny_config: TransactionDenyConfig::default(),
160 verifier_signing_config: VerifierSigningConfig::default(),
161 data_ingestion_path: None,
162 }
163 }
164
165 pub fn execute_transaction(
179 &mut self,
180 transaction: Transaction,
181 ) -> anyhow::Result<(TransactionEffects, Option<ExecutionError>)> {
182 let transaction = transaction
183 .try_into_verified_for_testing(self.epoch_state.epoch(), &VerifyParams::default())?;
184
185 let (inner_temporary_store, _, effects, execution_error_opt) =
186 self.epoch_state.execute_transaction(
187 &self.store,
188 &self.deny_config,
189 &self.verifier_signing_config,
190 &transaction,
191 )?;
192
193 let InnerTemporaryStore {
194 written, events, ..
195 } = inner_temporary_store;
196
197 self.store.insert_executed_transaction(
198 transaction.clone(),
199 effects.clone(),
200 events,
201 written,
202 );
203
204 self.checkpoint_builder
206 .push_transaction(transaction, effects.clone());
207 Ok((effects, execution_error_opt.err()))
208 }
209
210 pub fn create_checkpoint(&mut self) -> VerifiedCheckpoint {
213 let committee = CommitteeWithKeys::new(&self.keystore, self.epoch_state.committee());
214 let (checkpoint, contents, _) = self
215 .checkpoint_builder
216 .build(&committee, self.store.get_clock().timestamp_ms());
217 self.store.insert_checkpoint(checkpoint.clone());
218 self.store.insert_checkpoint_contents(contents.clone());
219 self.process_data_ingestion(checkpoint.clone(), contents)
220 .unwrap();
221 checkpoint
222 }
223
224 pub fn advance_clock(&mut self, duration: std::time::Duration) -> TransactionEffects {
229 let epoch = self.epoch_state.epoch();
230 let round = self.epoch_state.next_consensus_round();
231 let timestamp_ms = self.store.get_clock().timestamp_ms() + duration.as_millis() as u64;
232
233 let consensus_commit_prologue_transaction =
234 VerifiedTransaction::new_consensus_commit_prologue_v1(
235 epoch,
236 round,
237 timestamp_ms,
238 ConsensusCommitDigest::default(),
239 Vec::new(),
240 );
241
242 self.execute_transaction(consensus_commit_prologue_transaction.into())
243 .expect("advancing the clock cannot fail")
244 .0
245 }
246
247 pub fn advance_epoch(&mut self) {
257 let next_epoch = self.epoch_state.epoch() + 1;
258 let next_epoch_protocol_version = self.epoch_state.protocol_version();
259 let gas_cost_summary = self.checkpoint_builder.epoch_rolling_gas_cost_summary();
260 let epoch_start_timestamp_ms = self.store.get_clock().timestamp_ms();
261 let next_epoch_system_package_bytes = vec![];
262
263 let kinds = vec![EndOfEpochTransactionKind::new_change_epoch_v3(
264 next_epoch,
265 next_epoch_protocol_version,
266 gas_cost_summary.storage_cost,
267 gas_cost_summary.computation_cost,
268 gas_cost_summary.computation_cost_burned,
269 gas_cost_summary.storage_rebate,
270 gas_cost_summary.non_refundable_storage_fee,
271 epoch_start_timestamp_ms,
272 next_epoch_system_package_bytes,
273 vec![],
274 )];
275
276 let tx = VerifiedTransaction::new_end_of_epoch_transaction(kinds);
277 self.execute_transaction(tx.into())
278 .expect("advancing the epoch cannot fail");
279
280 let new_epoch_state = EpochState::new(self.store.get_system_state());
281 let end_of_epoch_data = EndOfEpochData {
282 next_epoch_committee: new_epoch_state.committee().voting_rights.clone(),
283 next_epoch_protocol_version,
284 epoch_commitments: vec![],
285 epoch_supply_change: 0,
287 };
288 let committee = CommitteeWithKeys::new(&self.keystore, self.epoch_state.committee());
289 let (checkpoint, contents, _) = self.checkpoint_builder.build_end_of_epoch(
290 &committee,
291 self.store.get_clock().timestamp_ms(),
292 next_epoch,
293 end_of_epoch_data,
294 );
295
296 self.store.insert_checkpoint(checkpoint.clone());
297 self.store.insert_checkpoint_contents(contents.clone());
298 self.process_data_ingestion(checkpoint, contents).unwrap();
299 self.epoch_state = new_epoch_state;
300 }
301
302 pub fn store(&self) -> &dyn SimulatorStore {
303 &self.store
304 }
305
306 pub fn keystore(&self) -> &KeyStore {
307 &self.keystore
308 }
309
310 pub fn epoch_start_state(&self) -> &EpochStartSystemState {
311 self.epoch_state.epoch_start_state()
312 }
313
314 pub fn rng(&mut self) -> &mut R {
321 &mut self.rng
322 }
323
324 pub fn reference_gas_price(&self) -> u64 {
326 self.epoch_state.reference_gas_price()
327 }
328
329 pub fn request_gas(&mut self, address: IotaAddress, amount: u64) -> Result<TransactionEffects> {
345 let (sender, key) = self.keystore().accounts().next().unwrap();
349 let object = self
350 .store()
351 .owned_objects(*sender)
352 .find(|object| {
353 object.is_gas_coin() && object.get_coin_value_unsafe() > amount + NANOS_PER_IOTA
354 })
355 .ok_or_else(|| {
356 anyhow!("unable to find a coin with enough to satisfy request for {amount} Nanos")
357 })?;
358
359 let gas_data = iota_types::transaction::GasData {
360 payment: vec![object.compute_object_reference()],
361 owner: *sender,
362 price: self.reference_gas_price(),
363 budget: NANOS_PER_IOTA,
364 };
365
366 let pt = {
367 let mut builder =
368 iota_types::programmable_transaction_builder::ProgrammableTransactionBuilder::new();
369 builder.transfer_iota(address, Some(amount));
370 builder.finish()
371 };
372
373 let kind = iota_types::transaction::TransactionKind::ProgrammableTransaction(pt);
374 let tx_data =
375 iota_types::transaction::TransactionData::new_with_gas_data(kind, *sender, gas_data);
376 let tx = Transaction::from_data_and_signer(tx_data, vec![key]);
377
378 self.execute_transaction(tx).map(|x| x.0)
379 }
380
381 pub fn set_data_ingestion_path(&mut self, data_ingestion_path: PathBuf) {
382 self.data_ingestion_path = Some(data_ingestion_path);
383 let checkpoint = self.store.get_checkpoint_by_sequence_number(0).unwrap();
384 let contents = self
385 .store
386 .get_checkpoint_contents(&checkpoint.content_digest);
387 self.process_data_ingestion(checkpoint, contents.unwrap())
388 .unwrap();
389 }
390
391 pub fn override_next_checkpoint_number(&mut self, number: CheckpointSequenceNumber) {
397 let committee = CommitteeWithKeys::new(&self.keystore, self.epoch_state.committee());
398 self.checkpoint_builder
399 .override_next_checkpoint_number(number, &committee);
400 }
401
402 fn process_data_ingestion(
403 &self,
404 checkpoint: VerifiedCheckpoint,
405 checkpoint_contents: CheckpointContents,
406 ) -> anyhow::Result<()> {
407 if let Some(path) = &self.data_ingestion_path {
408 let file_name = format!("{}.chk", checkpoint.sequence_number);
409 let checkpoint_data = self.try_get_checkpoint_data(checkpoint, checkpoint_contents)?;
410 std::fs::create_dir_all(path)?;
411 let blob = Blob::encode(&checkpoint_data, BlobEncoding::Bcs)?;
412 std::fs::write(path.join(file_name), blob.to_bytes())?;
413 }
414 Ok(())
415 }
416}
417
418pub struct CommitteeWithKeys<'a> {
419 keystore: &'a KeyStore,
420 committee: &'a Committee,
421}
422
423impl<'a> CommitteeWithKeys<'a> {
424 fn new(keystore: &'a KeyStore, committee: &'a Committee) -> Self {
425 Self {
426 keystore,
427 committee,
428 }
429 }
430
431 pub fn keystore(&self) -> &KeyStore {
432 self.keystore
433 }
434}
435
436impl ValidatorKeypairProvider for CommitteeWithKeys<'_> {
437 fn get_validator_key(&self, name: &AuthorityName) -> &dyn Signer<AuthoritySignature> {
438 self.keystore.validator(name).unwrap()
439 }
440
441 fn get_committee(&self) -> &Committee {
442 self.committee
443 }
444}
445
446impl<T, V: store::SimulatorStore> ObjectStore for Simulacrum<T, V> {
447 fn try_get_object(
448 &self,
449 object_id: &ObjectID,
450 ) -> Result<Option<Object>, iota_types::storage::error::Error> {
451 Ok(store::SimulatorStore::get_object(&self.store, object_id))
452 }
453
454 fn try_get_object_by_key(
455 &self,
456 object_id: &ObjectID,
457 version: VersionNumber,
458 ) -> Result<Option<Object>, iota_types::storage::error::Error> {
459 self.store.try_get_object_by_key(object_id, version)
460 }
461}
462
463impl<T, V: store::SimulatorStore> ReadStore for Simulacrum<T, V> {
464 fn try_get_committee(
465 &self,
466 _epoch: iota_types::committee::EpochId,
467 ) -> iota_types::storage::error::Result<Option<std::sync::Arc<Committee>>> {
468 todo!()
469 }
470
471 fn try_get_latest_checkpoint(&self) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
472 Ok(self.store().get_highest_checkpoint().unwrap())
473 }
474
475 fn try_get_highest_verified_checkpoint(
476 &self,
477 ) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
478 todo!()
479 }
480
481 fn try_get_highest_synced_checkpoint(
482 &self,
483 ) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
484 todo!()
485 }
486
487 fn try_get_lowest_available_checkpoint(
488 &self,
489 ) -> iota_types::storage::error::Result<iota_types::messages_checkpoint::CheckpointSequenceNumber>
490 {
491 Ok(0)
494 }
495
496 fn try_get_checkpoint_by_digest(
497 &self,
498 digest: &iota_types::messages_checkpoint::CheckpointDigest,
499 ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
500 Ok(self.store().get_checkpoint_by_digest(digest))
501 }
502
503 fn try_get_checkpoint_by_sequence_number(
504 &self,
505 sequence_number: iota_types::messages_checkpoint::CheckpointSequenceNumber,
506 ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
507 Ok(self
508 .store()
509 .get_checkpoint_by_sequence_number(sequence_number))
510 }
511
512 fn try_get_checkpoint_contents_by_digest(
513 &self,
514 digest: &iota_types::messages_checkpoint::CheckpointContentsDigest,
515 ) -> iota_types::storage::error::Result<
516 Option<iota_types::messages_checkpoint::CheckpointContents>,
517 > {
518 Ok(self.store().get_checkpoint_contents(digest))
519 }
520
521 fn try_get_checkpoint_contents_by_sequence_number(
522 &self,
523 _sequence_number: iota_types::messages_checkpoint::CheckpointSequenceNumber,
524 ) -> iota_types::storage::error::Result<
525 Option<iota_types::messages_checkpoint::CheckpointContents>,
526 > {
527 todo!()
528 }
529
530 fn try_get_transaction(
531 &self,
532 tx_digest: &iota_types::digests::TransactionDigest,
533 ) -> iota_types::storage::error::Result<Option<Arc<VerifiedTransaction>>> {
534 Ok(self.store().get_transaction(tx_digest).map(Arc::new))
535 }
536
537 fn try_get_transaction_effects(
538 &self,
539 tx_digest: &iota_types::digests::TransactionDigest,
540 ) -> iota_types::storage::error::Result<Option<TransactionEffects>> {
541 Ok(self.store().get_transaction_effects(tx_digest))
542 }
543
544 fn try_get_events(
545 &self,
546 event_digest: &iota_types::digests::TransactionEventsDigest,
547 ) -> iota_types::storage::error::Result<Option<iota_types::effects::TransactionEvents>> {
548 Ok(self.store().get_transaction_events(event_digest))
549 }
550
551 fn try_get_full_checkpoint_contents_by_sequence_number(
552 &self,
553 _sequence_number: iota_types::messages_checkpoint::CheckpointSequenceNumber,
554 ) -> iota_types::storage::error::Result<
555 Option<iota_types::messages_checkpoint::FullCheckpointContents>,
556 > {
557 todo!()
558 }
559
560 fn try_get_full_checkpoint_contents(
561 &self,
562 _digest: &iota_types::messages_checkpoint::CheckpointContentsDigest,
563 ) -> iota_types::storage::error::Result<
564 Option<iota_types::messages_checkpoint::FullCheckpointContents>,
565 > {
566 todo!()
567 }
568}
569
570impl<T: Send + Sync, V: store::SimulatorStore + Send + Sync> RestStateReader for Simulacrum<T, V> {
571 fn get_lowest_available_checkpoint_objects(
572 &self,
573 ) -> iota_types::storage::error::Result<CheckpointSequenceNumber> {
574 Ok(0)
575 }
576
577 fn get_chain_identifier(
578 &self,
579 ) -> iota_types::storage::error::Result<iota_types::digests::ChainIdentifier> {
580 Ok(self
581 .store()
582 .get_checkpoint_by_sequence_number(0)
583 .unwrap()
584 .digest()
585 .to_owned()
586 .into())
587 }
588
589 fn get_epoch_last_checkpoint(
590 &self,
591 _epoch_id: iota_types::committee::EpochId,
592 ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
593 todo!()
594 }
595
596 fn indexes(&self) -> Option<&dyn iota_types::storage::RestIndexes> {
597 None
598 }
599}
600
601impl Simulacrum {
602 pub fn transfer_txn(&mut self, recipient: IotaAddress) -> (Transaction, u64) {
609 let (sender, key) = self.keystore().accounts().next().unwrap();
610 let sender = *sender;
611
612 let object = self
613 .store()
614 .owned_objects(sender)
615 .find(|object| object.is_gas_coin())
616 .unwrap();
617 let gas_coin = GasCoin::try_from(&object).unwrap();
618 let transfer_amount = gas_coin.value() / 2;
619
620 let pt = {
621 let mut builder = ProgrammableTransactionBuilder::new();
622 builder.transfer_iota(recipient, Some(transfer_amount));
623 builder.finish()
624 };
625
626 let kind = TransactionKind::ProgrammableTransaction(pt);
627 let gas_data = GasData {
628 payment: vec![object.compute_object_reference()],
629 owner: sender,
630 price: self.reference_gas_price(),
631 budget: 1_000_000_000,
632 };
633 let tx_data = TransactionData::new_with_gas_data(kind, sender, gas_data);
634 let tx = Transaction::from_data_and_signer(tx_data, vec![key]);
635 (tx, transfer_amount)
636 }
637}
638
639#[cfg(test)]
640mod tests {
641 use std::time::Duration;
642
643 use iota_types::{
644 base_types::IotaAddress, effects::TransactionEffectsAPI, gas_coin::GasCoin,
645 transaction::TransactionDataAPI,
646 };
647 use rand::{SeedableRng, rngs::StdRng};
648
649 use super::*;
650
651 #[test]
652 fn deterministic_genesis() {
653 let rng = StdRng::from_seed([9; 32]);
654 let chain1 = Simulacrum::new_with_rng(rng);
655 let genesis_checkpoint_digest1 = *chain1
656 .store()
657 .get_checkpoint_by_sequence_number(0)
658 .unwrap()
659 .digest();
660
661 let rng = StdRng::from_seed([9; 32]);
662 let chain2 = Simulacrum::new_with_rng(rng);
663 let genesis_checkpoint_digest2 = *chain2
664 .store()
665 .get_checkpoint_by_sequence_number(0)
666 .unwrap()
667 .digest();
668
669 assert_eq!(genesis_checkpoint_digest1, genesis_checkpoint_digest2);
670
671 let rng = StdRng::from_seed([0; 32]);
673 let chain3 = Simulacrum::new_with_rng(rng);
674
675 assert_ne!(
676 chain1.store().get_committee_by_epoch(0),
677 chain3.store().get_committee_by_epoch(0),
678 );
679 }
680
681 #[test]
682 fn simple() {
683 let steps = 10;
684 let mut chain = Simulacrum::new();
685
686 let clock = chain.store().get_clock();
687 let start_time_ms = clock.timestamp_ms();
688 println!("clock: {clock:#?}");
689 for _ in 0..steps {
690 chain.advance_clock(Duration::from_millis(1));
691 chain.create_checkpoint();
692 let clock = chain.store().get_clock();
693 println!("clock: {clock:#?}");
694 }
695 let end_time_ms = chain.store().get_clock().timestamp_ms();
696 assert_eq!(end_time_ms - start_time_ms, steps);
697 dbg!(chain.store().get_highest_checkpoint());
698 }
699
700 #[test]
701 fn simple_epoch() {
702 let steps = 10;
703 let mut chain = Simulacrum::new();
704
705 let start_epoch = chain.store.get_highest_checkpoint().unwrap().epoch;
706 for i in 0..steps {
707 chain.advance_epoch();
708 chain.advance_clock(Duration::from_millis(1));
709 chain.create_checkpoint();
710 println!("{i}");
711 }
712 let end_epoch = chain.store.get_highest_checkpoint().unwrap().epoch;
713 assert_eq!(end_epoch - start_epoch, steps);
714 dbg!(chain.store().get_highest_checkpoint());
715 }
716
717 #[test]
718 fn transfer() {
719 let mut sim = Simulacrum::new();
720 let recipient = IotaAddress::random_for_testing_only();
721 let (tx, transfer_amount) = sim.transfer_txn(recipient);
722
723 let gas_id = tx.data().transaction_data().gas_data().payment[0].0;
724 let effects = sim.execute_transaction(tx).unwrap().0;
725 let gas_summary = effects.gas_cost_summary();
726 let gas_paid = gas_summary.net_gas_usage();
727
728 assert_eq!(
729 (transfer_amount as i64 - gas_paid) as u64,
730 store::SimulatorStore::get_object(sim.store(), &gas_id)
731 .and_then(|object| GasCoin::try_from(&object).ok())
732 .unwrap()
733 .value()
734 );
735
736 assert_eq!(
737 transfer_amount,
738 sim.store()
739 .owned_objects(recipient)
740 .next()
741 .and_then(|object| GasCoin::try_from(&object).ok())
742 .unwrap()
743 .value()
744 );
745
746 let checkpoint = sim.create_checkpoint();
747
748 assert_eq!(&checkpoint.epoch_rolling_gas_cost_summary, gas_summary);
749 assert_eq!(checkpoint.network_total_transactions, 2); }
751}