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    shared_object_version_manager::AssignedVersions,
20};
21use iota_json_rpc::authority_state::StateRead;
22use iota_sdk_types::{
23    Address, CheckpointContents, CheckpointContentsDigest, CheckpointDigest, Event, ObjectId,
24    Transaction, TransactionDigest, TransactionEffects, TransactionEvents,
25};
26use iota_storage::key_value_store::TransactionKeyValueStore;
27use iota_types::{
28    base_types::VersionNumber,
29    committee::EpochId,
30    error::{ExecutionError, IotaError, IotaResult},
31    executable_transaction::{ExecutableTransaction, VerifiedExecutableTransaction},
32    iota_system_state::{
33        IotaSystemStateTrait, epoch_start_iota_system_state::EpochStartSystemStateTrait,
34        iota_system_state_summary::IotaSystemStateSummary,
35    },
36    messages_checkpoint::VerifiedCheckpoint,
37    object::Object,
38    storage::{ObjectStore, ReadStore},
39    transaction::{InputObjects, SenderSignedTransactionAPI, TransactionEnvelope},
40    transaction_executor::{SimulateTransactionResult, VmChecks},
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: TransactionEnvelope,
72    ) -> anyhow::Result<(TransactionEffects, Option<ExecutionError>)>;
73
74    async fn read_input_objects(
75        &self,
76        transaction: TransactionEnvelope,
77        assigned_versions: AssignedVersions,
78    ) -> IotaResult<InputObjects>;
79
80    fn prepare_txn(
81        &self,
82        transaction: TransactionEnvelope,
83        input_objects: InputObjects,
84    ) -> anyhow::Result<(TransactionEffects, Option<ExecutionError>)>;
85
86    async fn create_checkpoint(&mut self) -> anyhow::Result<VerifiedCheckpoint>;
87
88    async fn advance_clock(
89        &mut self,
90        duration: std::time::Duration,
91    ) -> anyhow::Result<TransactionEffects>;
92
93    /// Returns the effects of the end-of-epoch transaction when the executor
94    /// exposes them (the simulator does, the validator setup does not).
95    async fn advance_epoch(
96        &mut self,
97        create_deny_rules_object: bool,
98    ) -> anyhow::Result<Option<TransactionEffects>>;
99
100    async fn request_gas(
101        &mut self,
102        address: Address,
103        amount: u64,
104    ) -> anyhow::Result<TransactionEffects>;
105
106    async fn simulate_transaction(
107        &self,
108        transaction: Transaction,
109        checks: VmChecks,
110    ) -> IotaResult<SimulateTransactionResult>;
111
112    async fn query_tx_events_asc(
113        &self,
114        tx_digest: &TransactionDigest,
115        limit: usize,
116    ) -> IotaResult<Vec<Event>>;
117
118    async fn get_active_validator_addresses(&self) -> IotaResult<Vec<Address>>;
119}
120
121#[async_trait::async_trait]
122impl TransactionalAdapter for ValidatorWithFullnode {
123    async fn execute_txn(
124        &mut self,
125        transaction: TransactionEnvelope,
126    ) -> anyhow::Result<(TransactionEffects, Option<ExecutionError>)> {
127        let with_shared = transaction.contains_shared_object();
128        let (_, effects, execution_error) = send_and_confirm_transaction_with_execution_error(
129            &self.validator,
130            Some(&self.fullnode),
131            transaction,
132            with_shared,
133            false,
134        )
135        .await?;
136        Ok((effects.into_data(), execution_error))
137    }
138
139    async fn read_input_objects(
140        &self,
141        transaction: TransactionEnvelope,
142        assigned_versions: AssignedVersions,
143    ) -> IotaResult<InputObjects> {
144        let tx = VerifiedExecutableTransaction::new_unchecked(
145            ExecutableTransaction::new_from_data_and_sig(
146                transaction.data().clone(),
147                iota_types::executable_transaction::CertificateProof::Checkpoint(0, 0),
148            ),
149        );
150
151        let epoch_store = self.validator.load_epoch_store_one_call_per_task().clone();
152        self.validator
153            .read_objects_for_execution(
154                &TxLockGuard::guard_for_tests(),
155                &tx,
156                assigned_versions,
157                &epoch_store,
158            )
159            .map(|(tx_input_objects, _)| tx_input_objects)
160    }
161
162    fn prepare_txn(
163        &self,
164        transaction: TransactionEnvelope,
165        input_objects: InputObjects,
166    ) -> anyhow::Result<(TransactionEffects, Option<ExecutionError>)> {
167        let tx = VerifiedExecutableTransaction::new_unchecked(
168            ExecutableTransaction::new_from_data_and_sig(
169                transaction.data().clone(),
170                iota_types::executable_transaction::CertificateProof::Checkpoint(0, 0),
171            ),
172        );
173
174        let epoch_store = self.validator.load_epoch_store_one_call_per_task().clone();
175        let (_, effects, error) =
176            self.validator
177                .prepare_transaction_for_benchmark(&tx, input_objects, &epoch_store)?;
178        Ok((effects, error))
179    }
180
181    async fn simulate_transaction(
182        &self,
183        transaction: Transaction,
184        checks: VmChecks,
185    ) -> IotaResult<SimulateTransactionResult> {
186        self.fullnode.simulate_transaction(transaction, checks)
187    }
188
189    async fn query_tx_events_asc(
190        &self,
191        tx_digest: &TransactionDigest,
192        limit: usize,
193    ) -> IotaResult<Vec<Event>> {
194        // Read from the store rather than the JSON-RPC index, which indexes a
195        // transaction only once its checkpoint executes, and these tests
196        // execute no checkpoints.
197        let events = self
198            .try_get_events(tx_digest)?
199            .map_or_else(Vec::new, |e| e.0);
200        Ok(events.into_iter().take(limit).collect())
201    }
202
203    async fn create_checkpoint(&mut self) -> anyhow::Result<VerifiedCheckpoint> {
204        unimplemented!("create_checkpoint not supported")
205    }
206
207    async fn advance_clock(
208        &mut self,
209        _duration: std::time::Duration,
210    ) -> anyhow::Result<TransactionEffects> {
211        unimplemented!("advance_clock not supported")
212    }
213
214    async fn advance_epoch(
215        &mut self,
216        create_deny_rules_object: bool,
217    ) -> anyhow::Result<Option<TransactionEffects>> {
218        anyhow::ensure!(
219            !create_deny_rules_object,
220            "--create-deny-rules-object is only supported in simulator mode"
221        );
222        self.validator.reconfigure_for_testing().await;
223        self.fullnode.reconfigure_for_testing().await;
224        Ok(None)
225    }
226
227    async fn request_gas(
228        &mut self,
229        _address: Address,
230        _amount: u64,
231    ) -> anyhow::Result<TransactionEffects> {
232        unimplemented!("request_gas not supported")
233    }
234
235    async fn get_active_validator_addresses(&self) -> IotaResult<Vec<Address>> {
236        let system_state_summary = self
237            .fullnode
238            .get_system_state()
239            .map_err(|e| {
240                IotaError::IotaSystemStateRead(format!(
241                    "Failed to get system state from fullnode: {e}"
242                ))
243            })?
244            .into_iota_system_state_summary();
245        let active_validators = match system_state_summary {
246            IotaSystemStateSummary::V1(inner) => inner.active_validators,
247            IotaSystemStateSummary::V2(inner) => inner.active_validators,
248            _ => unimplemented!(
249                "a new IotaSystemStateSummary enum variant was added and needs to be handled"
250            ),
251        };
252
253        Ok(active_validators
254            .iter()
255            .map(|x| x.iota_address)
256            .collect::<Vec<_>>())
257    }
258}
259
260impl ReadStore for ValidatorWithFullnode {
261    fn try_get_committee(
262        &self,
263        _epoch: EpochId,
264    ) -> iota_types::storage::error::Result<Option<Arc<iota_types::committee::Committee>>> {
265        todo!()
266    }
267
268    fn try_get_latest_epoch_id(&self) -> iota_types::storage::error::Result<EpochId> {
269        Ok(self.validator.epoch_store_for_testing().epoch())
270    }
271
272    fn try_get_latest_checkpoint(&self) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
273        let sequence_number = self
274            .validator
275            .get_latest_checkpoint_sequence_number()
276            .unwrap();
277        self.try_get_checkpoint_by_sequence_number(sequence_number)
278            .map(|c| c.unwrap())
279    }
280
281    fn try_get_highest_verified_checkpoint(
282        &self,
283    ) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
284        todo!()
285    }
286
287    fn try_get_highest_synced_checkpoint(
288        &self,
289    ) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
290        todo!()
291    }
292
293    fn try_get_lowest_available_checkpoint(
294        &self,
295    ) -> iota_types::storage::error::Result<iota_types::messages_checkpoint::CheckpointSequenceNumber>
296    {
297        todo!()
298    }
299
300    fn try_get_checkpoint_by_digest(
301        &self,
302        _digest: &CheckpointDigest,
303    ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
304        todo!()
305    }
306
307    fn try_get_checkpoint_by_sequence_number(
308        &self,
309        sequence_number: iota_types::messages_checkpoint::CheckpointSequenceNumber,
310    ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
311        self.validator
312            .get_checkpoint_store()
313            .get_checkpoint_by_sequence_number(sequence_number)
314            .map_err(iota_types::storage::error::Error::custom)
315    }
316
317    fn try_get_checkpoint_contents_by_digest(
318        &self,
319        digest: &CheckpointContentsDigest,
320    ) -> iota_types::storage::error::Result<Option<CheckpointContents>> {
321        self.validator
322            .get_checkpoint_store()
323            .get_checkpoint_contents(digest)
324            .map_err(iota_types::storage::error::Error::custom)
325    }
326
327    fn try_get_checkpoint_contents_by_sequence_number(
328        &self,
329        _sequence_number: iota_types::messages_checkpoint::CheckpointSequenceNumber,
330    ) -> iota_types::storage::error::Result<Option<CheckpointContents>> {
331        todo!()
332    }
333
334    fn try_get_transaction(
335        &self,
336        tx_digest: &TransactionDigest,
337    ) -> iota_types::storage::error::Result<Option<Arc<iota_types::transaction::VerifiedTransaction>>>
338    {
339        self.validator
340            .get_transaction_cache_reader()
341            .try_get_transaction_block(tx_digest)
342            .map_err(iota_types::storage::error::Error::custom)
343    }
344
345    fn try_get_transaction_effects(
346        &self,
347        tx_digest: &TransactionDigest,
348    ) -> iota_types::storage::error::Result<Option<TransactionEffects>> {
349        self.validator
350            .get_transaction_cache_reader()
351            .try_get_executed_effects(tx_digest)
352            .map_err(iota_types::storage::error::Error::custom)
353    }
354
355    fn try_get_events(
356        &self,
357        digest: &TransactionDigest,
358    ) -> iota_types::storage::error::Result<Option<TransactionEvents>> {
359        self.validator
360            .get_transaction_cache_reader()
361            .try_get_events(digest)
362            .map_err(iota_types::storage::error::Error::custom)
363    }
364
365    fn try_get_full_checkpoint_contents_by_sequence_number(
366        &self,
367        _sequence_number: iota_types::messages_checkpoint::CheckpointSequenceNumber,
368    ) -> iota_types::storage::error::Result<
369        Option<iota_types::messages_checkpoint::FullCheckpointContents>,
370    > {
371        todo!()
372    }
373
374    fn try_get_full_checkpoint_contents(
375        &self,
376        _digest: &CheckpointContentsDigest,
377    ) -> iota_types::storage::error::Result<
378        Option<iota_types::messages_checkpoint::FullCheckpointContents>,
379    > {
380        todo!()
381    }
382}
383
384impl ObjectStore for ValidatorWithFullnode {
385    fn try_get_object(
386        &self,
387        object_id: &ObjectId,
388    ) -> Result<Option<Object>, iota_types::storage::error::Error> {
389        self.validator.get_object_store().try_get_object(object_id)
390    }
391
392    fn try_get_object_by_key(
393        &self,
394        object_id: &ObjectId,
395        version: VersionNumber,
396    ) -> Result<Option<Object>, iota_types::storage::error::Error> {
397        self.validator
398            .get_object_store()
399            .try_get_object_by_key(object_id, version)
400    }
401}
402
403#[async_trait::async_trait]
404impl TransactionalAdapter for Simulacrum<StdRng, PersistedStore> {
405    async fn execute_txn(
406        &mut self,
407        transaction: TransactionEnvelope,
408    ) -> anyhow::Result<(TransactionEffects, Option<ExecutionError>)> {
409        Ok(self.execute_transaction(transaction)?)
410    }
411
412    async fn read_input_objects(
413        &self,
414        _transaction: TransactionEnvelope,
415        _assigned_versions: AssignedVersions,
416    ) -> IotaResult<InputObjects> {
417        unimplemented!("read_input_objects not supported in simulator mode")
418    }
419
420    fn prepare_txn(
421        &self,
422        _transaction: TransactionEnvelope,
423        _input_objects: InputObjects,
424    ) -> anyhow::Result<(TransactionEffects, Option<ExecutionError>)> {
425        unimplemented!("prepare_txn not supported in simulator mode")
426    }
427
428    async fn simulate_transaction(
429        &self,
430        transaction: Transaction,
431        checks: VmChecks,
432    ) -> IotaResult<SimulateTransactionResult> {
433        Simulacrum::simulate_transaction(self, transaction, checks)
434    }
435
436    async fn query_tx_events_asc(
437        &self,
438        tx_digest: &TransactionDigest,
439        _limit: usize,
440    ) -> IotaResult<Vec<Event>> {
441        match self.try_get_events(tx_digest)? {
442            Some(events) => Ok(events.0),
443            None => Ok(Vec::new()),
444        }
445    }
446
447    async fn create_checkpoint(&mut self) -> anyhow::Result<VerifiedCheckpoint> {
448        Ok(Simulacrum::create_checkpoint(self))
449    }
450
451    async fn advance_clock(
452        &mut self,
453        duration: std::time::Duration,
454    ) -> anyhow::Result<TransactionEffects> {
455        Ok(Simulacrum::advance_clock(self, duration))
456    }
457
458    async fn advance_epoch(
459        &mut self,
460        create_deny_rules_object: bool,
461    ) -> anyhow::Result<Option<TransactionEffects>> {
462        Ok(Some(Simulacrum::advance_epoch(
463            self,
464            create_deny_rules_object,
465        )))
466    }
467
468    async fn request_gas(
469        &mut self,
470        address: Address,
471        amount: u64,
472    ) -> anyhow::Result<TransactionEffects> {
473        Simulacrum::request_gas(self, address, amount)
474    }
475
476    async fn get_active_validator_addresses(&self) -> IotaResult<Vec<Address>> {
477        // TODO: this is a hack to get the validator addresses. Currently using start
478        // state       but we should have a better way to get this information
479        // after reconfig
480        Ok(self.epoch_start_state().get_validator_addresses())
481    }
482}