Skip to main content

iota_transactional_test_runner/
lib.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5//! This module contains the transactional test runner instantiation for the
6//! IOTA adapter
7
8pub mod args;
9pub mod offchain_state;
10pub mod programmable_transaction_test_parser;
11mod simulator_persisted_store;
12pub mod test_adapter;
13
14use std::{path::Path, sync::Arc};
15
16use iota_core::authority::{
17    AuthorityState, authority_per_epoch_store::TxLockGuard,
18    authority_test_utils::send_and_confirm_transaction_with_execution_error,
19    shared_object_version_manager::AssignedVersions,
20};
21use iota_json_rpc::authority_state::StateRead;
22use iota_json_rpc_types::EventFilter;
23use iota_sdk_types::{
24    Address, CheckpointContents, CheckpointContentsDigest, CheckpointDigest, Event, ObjectId,
25    Transaction, TransactionDigest, TransactionEffects, TransactionEvents,
26};
27use iota_storage::key_value_store::TransactionKeyValueStore;
28use iota_types::{
29    base_types::VersionNumber,
30    committee::EpochId,
31    error::{ExecutionError, IotaError, IotaResult},
32    executable_transaction::{ExecutableTransaction, VerifiedExecutableTransaction},
33    iota_system_state::{
34        IotaSystemStateTrait, epoch_start_iota_system_state::EpochStartSystemStateTrait,
35        iota_system_state_summary::IotaSystemStateSummary,
36    },
37    messages_checkpoint::VerifiedCheckpoint,
38    object::Object,
39    storage::{ObjectStore, ReadStore},
40    transaction::{InputObjects, SenderSignedTransactionAPI, TransactionEnvelope},
41    transaction_executor::{SimulateTransactionResult, VmChecks},
42};
43pub use move_transactional_test_runner::framework::{
44    create_adapter, run_tasks_with_adapter, run_test_impl,
45};
46use rand::rngs::StdRng;
47use simulacrum::{Simulacrum, SimulatorStore};
48use simulator_persisted_store::PersistedStore;
49use test_adapter::{IotaTestAdapter, PRE_COMPILED};
50
51#[cfg_attr(not(msim), tokio::main)]
52#[cfg_attr(msim, msim::main)]
53pub async fn run_test(path: &Path) -> Result<(), Box<dyn std::error::Error>> {
54    let (_guard, _filter_handle) = telemetry_subscribers::TelemetryConfig::new()
55        .with_env()
56        .init();
57    run_test_impl::<IotaTestAdapter>(path, Some(std::sync::Arc::new(PRE_COMPILED.clone()))).await?;
58    Ok(())
59}
60
61pub struct ValidatorWithFullnode {
62    pub validator: Arc<AuthorityState>,
63    pub fullnode: Arc<AuthorityState>,
64    pub kv_store: Arc<TransactionKeyValueStore>,
65}
66
67/// TODO: better name?
68#[async_trait::async_trait]
69pub trait TransactionalAdapter: Send + Sync + ReadStore {
70    async fn execute_txn(
71        &mut self,
72        transaction: TransactionEnvelope,
73    ) -> anyhow::Result<(TransactionEffects, Option<ExecutionError>)>;
74
75    async fn read_input_objects(
76        &self,
77        transaction: TransactionEnvelope,
78        assigned_versions: AssignedVersions,
79    ) -> IotaResult<InputObjects>;
80
81    fn prepare_txn(
82        &self,
83        transaction: TransactionEnvelope,
84        input_objects: InputObjects,
85    ) -> anyhow::Result<(TransactionEffects, Option<ExecutionError>)>;
86
87    async fn create_checkpoint(&mut self) -> anyhow::Result<VerifiedCheckpoint>;
88
89    async fn advance_clock(
90        &mut self,
91        duration: std::time::Duration,
92    ) -> anyhow::Result<TransactionEffects>;
93
94    /// Returns the effects of the end-of-epoch transaction when the executor
95    /// exposes them (the simulator does, the validator setup does not).
96    async fn advance_epoch(
97        &mut self,
98        create_deny_rules_object: bool,
99    ) -> anyhow::Result<Option<TransactionEffects>>;
100
101    async fn request_gas(
102        &mut self,
103        address: Address,
104        amount: u64,
105    ) -> anyhow::Result<TransactionEffects>;
106
107    async fn simulate_transaction(
108        &self,
109        transaction: Transaction,
110        checks: VmChecks,
111    ) -> IotaResult<SimulateTransactionResult>;
112
113    async fn query_tx_events_asc(
114        &self,
115        tx_digest: &TransactionDigest,
116        limit: usize,
117    ) -> IotaResult<Vec<Event>>;
118
119    async fn get_active_validator_addresses(&self) -> IotaResult<Vec<Address>>;
120}
121
122#[async_trait::async_trait]
123impl TransactionalAdapter for ValidatorWithFullnode {
124    async fn execute_txn(
125        &mut self,
126        transaction: TransactionEnvelope,
127    ) -> anyhow::Result<(TransactionEffects, Option<ExecutionError>)> {
128        let with_shared = transaction.contains_shared_object();
129        let (_, effects, execution_error) = send_and_confirm_transaction_with_execution_error(
130            &self.validator,
131            Some(&self.fullnode),
132            transaction,
133            with_shared,
134            false,
135        )
136        .await?;
137        Ok((effects.into_data(), execution_error))
138    }
139
140    async fn read_input_objects(
141        &self,
142        transaction: TransactionEnvelope,
143        assigned_versions: AssignedVersions,
144    ) -> IotaResult<InputObjects> {
145        let tx = VerifiedExecutableTransaction::new_unchecked(
146            ExecutableTransaction::new_from_data_and_sig(
147                transaction.data().clone(),
148                iota_types::executable_transaction::CertificateProof::Checkpoint(0, 0),
149            ),
150        );
151
152        let epoch_store = self.validator.load_epoch_store_one_call_per_task().clone();
153        self.validator
154            .read_objects_for_execution(
155                &TxLockGuard::guard_for_tests(),
156                &tx,
157                assigned_versions,
158                &epoch_store,
159            )
160            .map(|(tx_input_objects, _)| tx_input_objects)
161    }
162
163    fn prepare_txn(
164        &self,
165        transaction: TransactionEnvelope,
166        input_objects: InputObjects,
167    ) -> anyhow::Result<(TransactionEffects, Option<ExecutionError>)> {
168        let tx = VerifiedExecutableTransaction::new_unchecked(
169            ExecutableTransaction::new_from_data_and_sig(
170                transaction.data().clone(),
171                iota_types::executable_transaction::CertificateProof::Checkpoint(0, 0),
172            ),
173        );
174
175        let epoch_store = self.validator.load_epoch_store_one_call_per_task().clone();
176        let (_, effects, error) =
177            self.validator
178                .prepare_transaction_for_benchmark(&tx, input_objects, &epoch_store)?;
179        Ok((effects, error))
180    }
181
182    async fn simulate_transaction(
183        &self,
184        transaction: Transaction,
185        checks: VmChecks,
186    ) -> IotaResult<SimulateTransactionResult> {
187        self.fullnode.simulate_transaction(transaction, checks)
188    }
189
190    async fn query_tx_events_asc(
191        &self,
192        tx_digest: &TransactionDigest,
193        limit: usize,
194    ) -> IotaResult<Vec<Event>> {
195        Ok(self
196            .validator
197            .query_events(
198                &self.kv_store,
199                EventFilter::Transaction(*tx_digest),
200                None,
201                limit,
202                false,
203            )
204            .await
205            .unwrap_or_default()
206            .into_iter()
207            .map(|iota_event| iota_event.into())
208            .collect())
209    }
210
211    async fn create_checkpoint(&mut self) -> anyhow::Result<VerifiedCheckpoint> {
212        unimplemented!("create_checkpoint not supported")
213    }
214
215    async fn advance_clock(
216        &mut self,
217        _duration: std::time::Duration,
218    ) -> anyhow::Result<TransactionEffects> {
219        unimplemented!("advance_clock not supported")
220    }
221
222    async fn advance_epoch(
223        &mut self,
224        create_deny_rules_object: bool,
225    ) -> anyhow::Result<Option<TransactionEffects>> {
226        anyhow::ensure!(
227            !create_deny_rules_object,
228            "--create-deny-rules-object is only supported in simulator mode"
229        );
230        self.validator.reconfigure_for_testing().await;
231        self.fullnode.reconfigure_for_testing().await;
232        Ok(None)
233    }
234
235    async fn request_gas(
236        &mut self,
237        _address: Address,
238        _amount: u64,
239    ) -> anyhow::Result<TransactionEffects> {
240        unimplemented!("request_gas not supported")
241    }
242
243    async fn get_active_validator_addresses(&self) -> IotaResult<Vec<Address>> {
244        let system_state_summary = self
245            .fullnode
246            .get_system_state()
247            .map_err(|e| {
248                IotaError::IotaSystemStateRead(format!(
249                    "Failed to get system state from fullnode: {e}"
250                ))
251            })?
252            .into_iota_system_state_summary();
253        let active_validators = match system_state_summary {
254            IotaSystemStateSummary::V1(inner) => inner.active_validators,
255            IotaSystemStateSummary::V2(inner) => inner.active_validators,
256            _ => unimplemented!(
257                "a new IotaSystemStateSummary enum variant was added and needs to be handled"
258            ),
259        };
260
261        Ok(active_validators
262            .iter()
263            .map(|x| x.iota_address)
264            .collect::<Vec<_>>())
265    }
266}
267
268impl ReadStore for ValidatorWithFullnode {
269    fn try_get_committee(
270        &self,
271        _epoch: EpochId,
272    ) -> iota_types::storage::error::Result<Option<Arc<iota_types::committee::Committee>>> {
273        todo!()
274    }
275
276    fn try_get_latest_epoch_id(&self) -> iota_types::storage::error::Result<EpochId> {
277        Ok(self.validator.epoch_store_for_testing().epoch())
278    }
279
280    fn try_get_latest_checkpoint(&self) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
281        let sequence_number = self
282            .validator
283            .get_latest_checkpoint_sequence_number()
284            .unwrap();
285        self.try_get_checkpoint_by_sequence_number(sequence_number)
286            .map(|c| c.unwrap())
287    }
288
289    fn try_get_highest_verified_checkpoint(
290        &self,
291    ) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
292        todo!()
293    }
294
295    fn try_get_highest_synced_checkpoint(
296        &self,
297    ) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
298        todo!()
299    }
300
301    fn try_get_lowest_available_checkpoint(
302        &self,
303    ) -> iota_types::storage::error::Result<iota_types::messages_checkpoint::CheckpointSequenceNumber>
304    {
305        todo!()
306    }
307
308    fn try_get_checkpoint_by_digest(
309        &self,
310        _digest: &CheckpointDigest,
311    ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
312        todo!()
313    }
314
315    fn try_get_checkpoint_by_sequence_number(
316        &self,
317        sequence_number: iota_types::messages_checkpoint::CheckpointSequenceNumber,
318    ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
319        self.validator
320            .get_checkpoint_store()
321            .get_checkpoint_by_sequence_number(sequence_number)
322            .map_err(iota_types::storage::error::Error::custom)
323    }
324
325    fn try_get_checkpoint_contents_by_digest(
326        &self,
327        digest: &CheckpointContentsDigest,
328    ) -> iota_types::storage::error::Result<Option<CheckpointContents>> {
329        self.validator
330            .get_checkpoint_store()
331            .get_checkpoint_contents(digest)
332            .map_err(iota_types::storage::error::Error::custom)
333    }
334
335    fn try_get_checkpoint_contents_by_sequence_number(
336        &self,
337        _sequence_number: iota_types::messages_checkpoint::CheckpointSequenceNumber,
338    ) -> iota_types::storage::error::Result<Option<CheckpointContents>> {
339        todo!()
340    }
341
342    fn try_get_transaction(
343        &self,
344        tx_digest: &TransactionDigest,
345    ) -> iota_types::storage::error::Result<Option<Arc<iota_types::transaction::VerifiedTransaction>>>
346    {
347        self.validator
348            .get_transaction_cache_reader()
349            .try_get_transaction_block(tx_digest)
350            .map_err(iota_types::storage::error::Error::custom)
351    }
352
353    fn try_get_transaction_effects(
354        &self,
355        tx_digest: &TransactionDigest,
356    ) -> iota_types::storage::error::Result<Option<TransactionEffects>> {
357        self.validator
358            .get_transaction_cache_reader()
359            .try_get_executed_effects(tx_digest)
360            .map_err(iota_types::storage::error::Error::custom)
361    }
362
363    fn try_get_events(
364        &self,
365        digest: &TransactionDigest,
366    ) -> iota_types::storage::error::Result<Option<TransactionEvents>> {
367        self.validator
368            .get_transaction_cache_reader()
369            .try_get_events(digest)
370            .map_err(iota_types::storage::error::Error::custom)
371    }
372
373    fn try_get_full_checkpoint_contents_by_sequence_number(
374        &self,
375        _sequence_number: iota_types::messages_checkpoint::CheckpointSequenceNumber,
376    ) -> iota_types::storage::error::Result<
377        Option<iota_types::messages_checkpoint::FullCheckpointContents>,
378    > {
379        todo!()
380    }
381
382    fn try_get_full_checkpoint_contents(
383        &self,
384        _digest: &CheckpointContentsDigest,
385    ) -> iota_types::storage::error::Result<
386        Option<iota_types::messages_checkpoint::FullCheckpointContents>,
387    > {
388        todo!()
389    }
390}
391
392impl ObjectStore for ValidatorWithFullnode {
393    fn try_get_object(
394        &self,
395        object_id: &ObjectId,
396    ) -> Result<Option<Object>, iota_types::storage::error::Error> {
397        self.validator.get_object_store().try_get_object(object_id)
398    }
399
400    fn try_get_object_by_key(
401        &self,
402        object_id: &ObjectId,
403        version: VersionNumber,
404    ) -> Result<Option<Object>, iota_types::storage::error::Error> {
405        self.validator
406            .get_object_store()
407            .try_get_object_by_key(object_id, version)
408    }
409}
410
411#[async_trait::async_trait]
412impl TransactionalAdapter for Simulacrum<StdRng, PersistedStore> {
413    async fn execute_txn(
414        &mut self,
415        transaction: TransactionEnvelope,
416    ) -> anyhow::Result<(TransactionEffects, Option<ExecutionError>)> {
417        Ok(self.execute_transaction(transaction)?)
418    }
419
420    async fn read_input_objects(
421        &self,
422        _transaction: TransactionEnvelope,
423        _assigned_versions: AssignedVersions,
424    ) -> IotaResult<InputObjects> {
425        unimplemented!("read_input_objects not supported in simulator mode")
426    }
427
428    fn prepare_txn(
429        &self,
430        _transaction: TransactionEnvelope,
431        _input_objects: InputObjects,
432    ) -> anyhow::Result<(TransactionEffects, Option<ExecutionError>)> {
433        unimplemented!("prepare_txn not supported in simulator mode")
434    }
435
436    async fn simulate_transaction(
437        &self,
438        transaction: Transaction,
439        checks: VmChecks,
440    ) -> IotaResult<SimulateTransactionResult> {
441        Simulacrum::simulate_transaction(self, transaction, checks)
442    }
443
444    async fn query_tx_events_asc(
445        &self,
446        tx_digest: &TransactionDigest,
447        _limit: usize,
448    ) -> IotaResult<Vec<Event>> {
449        match self.try_get_events(tx_digest)? {
450            Some(events) => Ok(events.0),
451            None => Ok(Vec::new()),
452        }
453    }
454
455    async fn create_checkpoint(&mut self) -> anyhow::Result<VerifiedCheckpoint> {
456        Ok(Simulacrum::create_checkpoint(self))
457    }
458
459    async fn advance_clock(
460        &mut self,
461        duration: std::time::Duration,
462    ) -> anyhow::Result<TransactionEffects> {
463        Ok(Simulacrum::advance_clock(self, duration))
464    }
465
466    async fn advance_epoch(
467        &mut self,
468        create_deny_rules_object: bool,
469    ) -> anyhow::Result<Option<TransactionEffects>> {
470        Ok(Some(Simulacrum::advance_epoch(
471            self,
472            create_deny_rules_object,
473        )))
474    }
475
476    async fn request_gas(
477        &mut self,
478        address: Address,
479        amount: u64,
480    ) -> anyhow::Result<TransactionEffects> {
481        Simulacrum::request_gas(self, address, amount)
482    }
483
484    async fn get_active_validator_addresses(&self) -> IotaResult<Vec<Address>> {
485        // TODO: this is a hack to get the validator addresses. Currently using start
486        // state       but we should have a better way to get this information
487        // after reconfig
488        Ok(self.epoch_start_state().get_validator_addresses())
489    }
490}