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};
20use iota_json_rpc::authority_state::StateRead;
21use iota_json_rpc_types::{DevInspectResults, DryRunTransactionBlockResponse, EventFilter};
22use iota_sdk_types::{
23    Address, CheckpointContentsDigest, CheckpointDigest, Event, ObjectId, TransactionDigest,
24    TransactionKind,
25};
26use iota_storage::key_value_store::TransactionKeyValueStore;
27use iota_types::{
28    base_types::VersionNumber,
29    committee::EpochId,
30    effects::{TransactionEffects, TransactionEvents},
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, Transaction, TransactionData},
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: Address,
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: Address,
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<Address>>;
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.contains_shared_object();
126        let (_, effects, execution_error) = send_and_confirm_transaction_with_execution_error(
127            &self.validator,
128            Some(&self.fullnode),
129            transaction,
130            with_shared,
131            false,
132        )
133        .await?;
134        Ok((effects.into_data(), execution_error))
135    }
136
137    async fn read_input_objects(&self, transaction: Transaction) -> IotaResult<InputObjects> {
138        let tx = VerifiedExecutableTransaction::new_unchecked(
139            ExecutableTransaction::new_from_data_and_sig(
140                transaction.data().clone(),
141                iota_types::executable_transaction::CertificateProof::Checkpoint(0, 0),
142            ),
143        );
144
145        let epoch_store = self.validator.load_epoch_store_one_call_per_task().clone();
146        self.validator
147            .read_objects_for_execution(&TxLockGuard::guard_for_tests(), &tx, &epoch_store)
148            .map(|(tx_input_objects, _)| tx_input_objects)
149    }
150
151    fn prepare_txn(
152        &self,
153        transaction: Transaction,
154        input_objects: InputObjects,
155    ) -> anyhow::Result<(TransactionEffects, Option<ExecutionError>)> {
156        let tx = VerifiedExecutableTransaction::new_unchecked(
157            ExecutableTransaction::new_from_data_and_sig(
158                transaction.data().clone(),
159                iota_types::executable_transaction::CertificateProof::Checkpoint(0, 0),
160            ),
161        );
162
163        let epoch_store = self.validator.load_epoch_store_one_call_per_task().clone();
164        let (_, effects, error) =
165            self.validator
166                .prepare_transaction_for_benchmark(&tx, input_objects, &epoch_store)?;
167        Ok((effects, error))
168    }
169
170    async fn dry_run_transaction_block(
171        &self,
172        transaction_block: TransactionData,
173        transaction_digest: TransactionDigest,
174    ) -> IotaResult<DryRunTransactionBlockResponse> {
175        self.fullnode
176            .dry_exec_transaction(transaction_block, transaction_digest)
177            .map(|result| result.0)
178    }
179
180    async fn dev_inspect_transaction_block(
181        &self,
182        sender: Address,
183        transaction_kind: TransactionKind,
184        gas_price: Option<u64>,
185    ) -> IotaResult<DevInspectResults> {
186        self.fullnode
187            .dev_inspect_transaction_block(
188                sender,
189                transaction_kind,
190                gas_price,
191                None,
192                None,
193                None,
194                None,
195                None,
196            )
197            .await
198    }
199
200    async fn query_tx_events_asc(
201        &self,
202        tx_digest: &TransactionDigest,
203        limit: usize,
204    ) -> IotaResult<Vec<Event>> {
205        Ok(self
206            .validator
207            .query_events(
208                &self.kv_store,
209                EventFilter::Transaction(*tx_digest),
210                None,
211                limit,
212                false,
213            )
214            .await
215            .unwrap_or_default()
216            .into_iter()
217            .map(|iota_event| iota_event.into())
218            .collect())
219    }
220
221    async fn create_checkpoint(&mut self) -> anyhow::Result<VerifiedCheckpoint> {
222        unimplemented!("create_checkpoint not supported")
223    }
224
225    async fn advance_clock(
226        &mut self,
227        _duration: std::time::Duration,
228    ) -> anyhow::Result<TransactionEffects> {
229        unimplemented!("advance_clock not supported")
230    }
231
232    async fn advance_epoch(&mut self) -> anyhow::Result<()> {
233        self.validator.reconfigure_for_testing().await;
234        self.fullnode.reconfigure_for_testing().await;
235        Ok(())
236    }
237
238    async fn request_gas(
239        &mut self,
240        _address: Address,
241        _amount: u64,
242    ) -> anyhow::Result<TransactionEffects> {
243        unimplemented!("request_gas not supported")
244    }
245
246    async fn get_active_validator_addresses(&self) -> IotaResult<Vec<Address>> {
247        let system_state_summary = self
248            .fullnode
249            .get_system_state()
250            .map_err(|e| {
251                IotaError::IotaSystemStateRead(format!(
252                    "Failed to get system state from fullnode: {e}"
253                ))
254            })?
255            .into_iota_system_state_summary();
256        let active_validators = match system_state_summary {
257            IotaSystemStateSummary::V1(inner) => inner.active_validators,
258            IotaSystemStateSummary::V2(inner) => inner.active_validators,
259            _ => unimplemented!(
260                "a new IotaSystemStateSummary enum variant was added and needs to be handled"
261            ),
262        };
263
264        Ok(active_validators
265            .iter()
266            .map(|x| x.iota_address)
267            .collect::<Vec<_>>())
268    }
269}
270
271impl ReadStore for ValidatorWithFullnode {
272    fn try_get_committee(
273        &self,
274        _epoch: EpochId,
275    ) -> iota_types::storage::error::Result<Option<Arc<iota_types::committee::Committee>>> {
276        todo!()
277    }
278
279    fn try_get_latest_epoch_id(&self) -> iota_types::storage::error::Result<EpochId> {
280        Ok(self.validator.epoch_store_for_testing().epoch())
281    }
282
283    fn try_get_latest_checkpoint(&self) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
284        let sequence_number = self
285            .validator
286            .get_latest_checkpoint_sequence_number()
287            .unwrap();
288        self.try_get_checkpoint_by_sequence_number(sequence_number)
289            .map(|c| c.unwrap())
290    }
291
292    fn try_get_highest_verified_checkpoint(
293        &self,
294    ) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
295        todo!()
296    }
297
298    fn try_get_highest_synced_checkpoint(
299        &self,
300    ) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
301        todo!()
302    }
303
304    fn try_get_lowest_available_checkpoint(
305        &self,
306    ) -> iota_types::storage::error::Result<iota_types::messages_checkpoint::CheckpointSequenceNumber>
307    {
308        todo!()
309    }
310
311    fn try_get_checkpoint_by_digest(
312        &self,
313        _digest: &CheckpointDigest,
314    ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
315        todo!()
316    }
317
318    fn try_get_checkpoint_by_sequence_number(
319        &self,
320        sequence_number: iota_types::messages_checkpoint::CheckpointSequenceNumber,
321    ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
322        self.validator
323            .get_checkpoint_store()
324            .get_checkpoint_by_sequence_number(sequence_number)
325            .map_err(iota_types::storage::error::Error::custom)
326    }
327
328    fn try_get_checkpoint_contents_by_digest(
329        &self,
330        digest: &CheckpointContentsDigest,
331    ) -> iota_types::storage::error::Result<
332        Option<iota_types::messages_checkpoint::CheckpointContents>,
333    > {
334        self.validator
335            .get_checkpoint_store()
336            .get_checkpoint_contents(digest)
337            .map_err(iota_types::storage::error::Error::custom)
338    }
339
340    fn try_get_checkpoint_contents_by_sequence_number(
341        &self,
342        _sequence_number: iota_types::messages_checkpoint::CheckpointSequenceNumber,
343    ) -> iota_types::storage::error::Result<
344        Option<iota_types::messages_checkpoint::CheckpointContents>,
345    > {
346        todo!()
347    }
348
349    fn try_get_transaction(
350        &self,
351        tx_digest: &TransactionDigest,
352    ) -> iota_types::storage::error::Result<Option<Arc<iota_types::transaction::VerifiedTransaction>>>
353    {
354        self.validator
355            .get_transaction_cache_reader()
356            .try_get_transaction_block(tx_digest)
357            .map_err(iota_types::storage::error::Error::custom)
358    }
359
360    fn try_get_transaction_effects(
361        &self,
362        tx_digest: &TransactionDigest,
363    ) -> iota_types::storage::error::Result<Option<TransactionEffects>> {
364        self.validator
365            .get_transaction_cache_reader()
366            .try_get_executed_effects(tx_digest)
367            .map_err(iota_types::storage::error::Error::custom)
368    }
369
370    fn try_get_events(
371        &self,
372        digest: &TransactionDigest,
373    ) -> iota_types::storage::error::Result<Option<TransactionEvents>> {
374        self.validator
375            .get_transaction_cache_reader()
376            .try_get_events(digest)
377            .map_err(iota_types::storage::error::Error::custom)
378    }
379
380    fn try_get_full_checkpoint_contents_by_sequence_number(
381        &self,
382        _sequence_number: iota_types::messages_checkpoint::CheckpointSequenceNumber,
383    ) -> iota_types::storage::error::Result<
384        Option<iota_types::messages_checkpoint::FullCheckpointContents>,
385    > {
386        todo!()
387    }
388
389    fn try_get_full_checkpoint_contents(
390        &self,
391        _digest: &CheckpointContentsDigest,
392    ) -> iota_types::storage::error::Result<
393        Option<iota_types::messages_checkpoint::FullCheckpointContents>,
394    > {
395        todo!()
396    }
397}
398
399impl ObjectStore for ValidatorWithFullnode {
400    fn try_get_object(
401        &self,
402        object_id: &ObjectId,
403    ) -> Result<Option<Object>, iota_types::storage::error::Error> {
404        self.validator.get_object_store().try_get_object(object_id)
405    }
406
407    fn try_get_object_by_key(
408        &self,
409        object_id: &ObjectId,
410        version: VersionNumber,
411    ) -> Result<Option<Object>, iota_types::storage::error::Error> {
412        self.validator
413            .get_object_store()
414            .try_get_object_by_key(object_id, version)
415    }
416}
417
418#[async_trait::async_trait]
419impl TransactionalAdapter for Simulacrum<StdRng, PersistedStore> {
420    async fn execute_txn(
421        &mut self,
422        transaction: Transaction,
423    ) -> anyhow::Result<(TransactionEffects, Option<ExecutionError>)> {
424        Ok(self.execute_transaction(transaction)?)
425    }
426
427    async fn read_input_objects(&self, _transaction: Transaction) -> IotaResult<InputObjects> {
428        unimplemented!("read_input_objects not supported in simulator mode")
429    }
430
431    fn prepare_txn(
432        &self,
433        _transaction: Transaction,
434        _input_objects: InputObjects,
435    ) -> anyhow::Result<(TransactionEffects, Option<ExecutionError>)> {
436        unimplemented!("prepare_txn not supported in simulator mode")
437    }
438
439    async fn dev_inspect_transaction_block(
440        &self,
441        _sender: Address,
442        _transaction_kind: TransactionKind,
443        _gas_price: Option<u64>,
444    ) -> IotaResult<DevInspectResults> {
445        unimplemented!("dev_inspect_transaction_block not supported in simulator mode")
446    }
447
448    async fn dry_run_transaction_block(
449        &self,
450        _transaction_block: TransactionData,
451        _transaction_digest: TransactionDigest,
452    ) -> IotaResult<DryRunTransactionBlockResponse> {
453        unimplemented!("dry_run_transaction_block not supported in simulator mode")
454    }
455
456    async fn query_tx_events_asc(
457        &self,
458        tx_digest: &TransactionDigest,
459        _limit: usize,
460    ) -> IotaResult<Vec<Event>> {
461        match self.try_get_events(tx_digest)? {
462            Some(events) => Ok(events.0),
463            None => Ok(Vec::new()),
464        }
465    }
466
467    async fn create_checkpoint(&mut self) -> anyhow::Result<VerifiedCheckpoint> {
468        Ok(Simulacrum::create_checkpoint(self))
469    }
470
471    async fn advance_clock(
472        &mut self,
473        duration: std::time::Duration,
474    ) -> anyhow::Result<TransactionEffects> {
475        Ok(Simulacrum::advance_clock(self, duration))
476    }
477
478    async fn advance_epoch(&mut self) -> anyhow::Result<()> {
479        Simulacrum::advance_epoch(self);
480        Ok(())
481    }
482
483    async fn request_gas(
484        &mut self,
485        address: Address,
486        amount: u64,
487    ) -> anyhow::Result<TransactionEffects> {
488        Simulacrum::request_gas(self, address, amount)
489    }
490
491    async fn get_active_validator_addresses(&self) -> IotaResult<Vec<Address>> {
492        // TODO: this is a hack to get the validator addresses. Currently using start
493        // state       but we should have a better way to get this information
494        // after reconfig
495        Ok(self.epoch_start_state().get_validator_addresses())
496    }
497}