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            .await
184            .map(|result| result.0)
185    }
186
187    async fn dev_inspect_transaction_block(
188        &self,
189        sender: IotaAddress,
190        transaction_kind: TransactionKind,
191        gas_price: Option<u64>,
192    ) -> IotaResult<DevInspectResults> {
193        self.fullnode
194            .dev_inspect_transaction_block(
195                sender,
196                transaction_kind,
197                gas_price,
198                None,
199                None,
200                None,
201                None,
202                None,
203            )
204            .await
205    }
206
207    async fn query_tx_events_asc(
208        &self,
209        tx_digest: &TransactionDigest,
210        limit: usize,
211    ) -> IotaResult<Vec<Event>> {
212        Ok(self
213            .validator
214            .query_events(
215                &self.kv_store,
216                EventFilter::Transaction(*tx_digest),
217                None,
218                limit,
219                false,
220            )
221            .await
222            .unwrap_or_default()
223            .into_iter()
224            .map(|iota_event| iota_event.into())
225            .collect())
226    }
227
228    async fn create_checkpoint(&mut self) -> anyhow::Result<VerifiedCheckpoint> {
229        unimplemented!("create_checkpoint not supported")
230    }
231
232    async fn advance_clock(
233        &mut self,
234        _duration: std::time::Duration,
235    ) -> anyhow::Result<TransactionEffects> {
236        unimplemented!("advance_clock not supported")
237    }
238
239    async fn advance_epoch(&mut self) -> anyhow::Result<()> {
240        self.validator.reconfigure_for_testing().await;
241        self.fullnode.reconfigure_for_testing().await;
242        Ok(())
243    }
244
245    async fn request_gas(
246        &mut self,
247        _address: IotaAddress,
248        _amount: u64,
249    ) -> anyhow::Result<TransactionEffects> {
250        unimplemented!("request_gas not supported")
251    }
252
253    async fn get_active_validator_addresses(&self) -> IotaResult<Vec<IotaAddress>> {
254        let system_state_summary = self
255            .fullnode
256            .get_system_state()
257            .map_err(|e| {
258                IotaError::IotaSystemStateRead(format!(
259                    "Failed to get system state from fullnode: {}",
260                    e
261                ))
262            })?
263            .into_iota_system_state_summary();
264        let active_validators = match system_state_summary {
265            IotaSystemStateSummary::V1(inner) => inner.active_validators,
266            IotaSystemStateSummary::V2(inner) => inner.active_validators,
267            _ => unimplemented!(),
268        };
269
270        Ok(active_validators
271            .iter()
272            .map(|x| x.iota_address)
273            .collect::<Vec<_>>())
274    }
275}
276
277impl ReadStore for ValidatorWithFullnode {
278    fn get_committee(
279        &self,
280        _epoch: EpochId,
281    ) -> iota_types::storage::error::Result<Option<Arc<iota_types::committee::Committee>>> {
282        todo!()
283    }
284
285    fn get_latest_epoch_id(&self) -> iota_types::storage::error::Result<EpochId> {
286        Ok(self.validator.epoch_store_for_testing().epoch())
287    }
288
289    fn get_latest_checkpoint(&self) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
290        let sequence_number = self
291            .validator
292            .get_latest_checkpoint_sequence_number()
293            .unwrap();
294        self.get_checkpoint_by_sequence_number(sequence_number)
295            .map(|c| c.unwrap())
296    }
297
298    fn get_highest_verified_checkpoint(
299        &self,
300    ) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
301        todo!()
302    }
303
304    fn get_highest_synced_checkpoint(
305        &self,
306    ) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
307        todo!()
308    }
309
310    fn get_lowest_available_checkpoint(
311        &self,
312    ) -> iota_types::storage::error::Result<iota_types::messages_checkpoint::CheckpointSequenceNumber>
313    {
314        todo!()
315    }
316
317    fn get_checkpoint_by_digest(
318        &self,
319        _digest: &iota_types::messages_checkpoint::CheckpointDigest,
320    ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
321        todo!()
322    }
323
324    fn get_checkpoint_by_sequence_number(
325        &self,
326        sequence_number: iota_types::messages_checkpoint::CheckpointSequenceNumber,
327    ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
328        self.validator
329            .get_checkpoint_store()
330            .get_checkpoint_by_sequence_number(sequence_number)
331            .map_err(iota_types::storage::error::Error::custom)
332    }
333
334    fn get_checkpoint_contents_by_digest(
335        &self,
336        digest: &CheckpointContentsDigest,
337    ) -> iota_types::storage::error::Result<
338        Option<iota_types::messages_checkpoint::CheckpointContents>,
339    > {
340        self.validator
341            .get_checkpoint_store()
342            .get_checkpoint_contents(digest)
343            .map_err(iota_types::storage::error::Error::custom)
344    }
345
346    fn get_checkpoint_contents_by_sequence_number(
347        &self,
348        _sequence_number: iota_types::messages_checkpoint::CheckpointSequenceNumber,
349    ) -> iota_types::storage::error::Result<
350        Option<iota_types::messages_checkpoint::CheckpointContents>,
351    > {
352        todo!()
353    }
354
355    fn get_transaction(
356        &self,
357        tx_digest: &TransactionDigest,
358    ) -> iota_types::storage::error::Result<Option<Arc<iota_types::transaction::VerifiedTransaction>>>
359    {
360        self.validator
361            .get_transaction_cache_reader()
362            .get_transaction_block(tx_digest)
363            .map_err(iota_types::storage::error::Error::custom)
364    }
365
366    fn get_transaction_effects(
367        &self,
368        tx_digest: &TransactionDigest,
369    ) -> iota_types::storage::error::Result<Option<TransactionEffects>> {
370        self.validator
371            .get_transaction_cache_reader()
372            .get_executed_effects(tx_digest)
373            .map_err(iota_types::storage::error::Error::custom)
374    }
375
376    fn get_events(
377        &self,
378        event_digest: &TransactionEventsDigest,
379    ) -> iota_types::storage::error::Result<Option<TransactionEvents>> {
380        self.validator
381            .get_transaction_cache_reader()
382            .get_events(event_digest)
383            .map_err(iota_types::storage::error::Error::custom)
384    }
385
386    fn get_full_checkpoint_contents_by_sequence_number(
387        &self,
388        _sequence_number: iota_types::messages_checkpoint::CheckpointSequenceNumber,
389    ) -> iota_types::storage::error::Result<
390        Option<iota_types::messages_checkpoint::FullCheckpointContents>,
391    > {
392        todo!()
393    }
394
395    fn get_full_checkpoint_contents(
396        &self,
397        _digest: &CheckpointContentsDigest,
398    ) -> iota_types::storage::error::Result<
399        Option<iota_types::messages_checkpoint::FullCheckpointContents>,
400    > {
401        todo!()
402    }
403}
404
405impl ObjectStore for ValidatorWithFullnode {
406    fn get_object(
407        &self,
408        object_id: &ObjectID,
409    ) -> Result<Option<Object>, iota_types::storage::error::Error> {
410        self.validator.get_object_store().get_object(object_id)
411    }
412
413    fn get_object_by_key(
414        &self,
415        object_id: &ObjectID,
416        version: VersionNumber,
417    ) -> Result<Option<Object>, iota_types::storage::error::Error> {
418        self.validator
419            .get_object_store()
420            .get_object_by_key(object_id, version)
421    }
422}
423
424#[async_trait::async_trait]
425impl TransactionalAdapter for Simulacrum<StdRng, PersistedStore> {
426    async fn execute_txn(
427        &mut self,
428        transaction: Transaction,
429    ) -> anyhow::Result<(TransactionEffects, Option<ExecutionError>)> {
430        Ok(self.execute_transaction(transaction)?)
431    }
432
433    async fn read_input_objects(&self, _transaction: Transaction) -> IotaResult<InputObjects> {
434        unimplemented!("read_input_objects not supported in simulator mode")
435    }
436
437    fn prepare_txn(
438        &self,
439        _transaction: Transaction,
440        _input_objects: InputObjects,
441    ) -> anyhow::Result<(TransactionEffects, Option<ExecutionError>)> {
442        unimplemented!("prepare_txn not supported in simulator mode")
443    }
444
445    async fn dev_inspect_transaction_block(
446        &self,
447        _sender: IotaAddress,
448        _transaction_kind: TransactionKind,
449        _gas_price: Option<u64>,
450    ) -> IotaResult<DevInspectResults> {
451        unimplemented!("dev_inspect_transaction_block not supported in simulator mode")
452    }
453
454    async fn dry_run_transaction_block(
455        &self,
456        _transaction_block: TransactionData,
457        _transaction_digest: TransactionDigest,
458    ) -> IotaResult<DryRunTransactionBlockResponse> {
459        unimplemented!("dry_run_transaction_block not supported in simulator mode")
460    }
461
462    async fn query_tx_events_asc(
463        &self,
464        tx_digest: &TransactionDigest,
465        _limit: usize,
466    ) -> IotaResult<Vec<Event>> {
467        Ok(self
468            .store()
469            .get_transaction_events_by_tx_digest(tx_digest)
470            .map(|x| x.data)
471            .unwrap_or_default())
472    }
473
474    async fn create_checkpoint(&mut self) -> anyhow::Result<VerifiedCheckpoint> {
475        Ok(self.create_checkpoint())
476    }
477
478    async fn advance_clock(
479        &mut self,
480        duration: std::time::Duration,
481    ) -> anyhow::Result<TransactionEffects> {
482        Ok(self.advance_clock(duration))
483    }
484
485    async fn advance_epoch(&mut self) -> anyhow::Result<()> {
486        self.advance_epoch();
487        Ok(())
488    }
489
490    async fn request_gas(
491        &mut self,
492        address: IotaAddress,
493        amount: u64,
494    ) -> anyhow::Result<TransactionEffects> {
495        self.request_gas(address, amount)
496    }
497
498    async fn get_active_validator_addresses(&self) -> IotaResult<Vec<IotaAddress>> {
499        // TODO: this is a hack to get the validator addresses. Currently using start
500        // state       but we should have a better way to get this information
501        // after reconfig
502        Ok(self.epoch_start_state().get_validator_addresses())
503    }
504}