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