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