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