iota_types/transaction_executor.rs
1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::{collections::BTreeMap, time::Duration};
6
7use iota_sdk_types::{
8 GasPayment, ObjectId, Transaction, TransactionDigest, TransactionEffects, TransactionEvents,
9};
10
11use crate::{
12 error::{ExecutionError, IotaError},
13 execution::ExecutionResult,
14 messages_checkpoint::CheckpointSequenceNumber,
15 object::Object,
16 quorum_driver_types::{
17 ExecuteTransactionRequestV1, ExecuteTransactionResponseV1, QuorumDriverError,
18 },
19};
20
21/// Trait to define the interface for how the REST service interacts with a
22/// QuorumDriver or a simulated transaction executor.
23#[async_trait::async_trait]
24pub trait TransactionExecutor: Send + Sync {
25 async fn execute_transaction(
26 &self,
27 request: ExecuteTransactionRequestV1,
28 skip_certification: bool,
29 client_addr: Option<std::net::SocketAddr>,
30 ) -> Result<ExecuteTransactionResponseV1, QuorumDriverError>;
31
32 fn simulate_transaction(
33 &self,
34 transaction: Transaction,
35 checks: VmChecks,
36 ) -> Result<SimulateTransactionResult, IotaError>;
37
38 /// Wait for the given transactions to be included in a checkpoint.
39 ///
40 /// Returns a mapping from transaction digest to
41 /// `(checkpoint_sequence_number, checkpoint_timestamp_ms)`.
42 /// On timeout, returns partial results for any transactions that were
43 /// already checkpointed.
44 async fn wait_for_checkpoint_inclusion(
45 &self,
46 digests: &[TransactionDigest],
47 timeout: Duration,
48 ) -> Result<BTreeMap<TransactionDigest, (CheckpointSequenceNumber, u64)>, IotaError>;
49
50 /// Read authoritative effects, events, and input/output objects for a
51 /// locally-executed transaction from the cache. Used by callers that
52 /// have already waited for checkpoint inclusion and want to discard any
53 /// uncertified single-validator copies.
54 ///
55 /// Returns `Ok(None)` if the tx is not in the cache, or if the executor
56 /// does not maintain a local cache (e.g. simulacrum).
57 fn read_transaction_from_cache(
58 &self,
59 digest: &TransactionDigest,
60 include_events: bool,
61 include_input_objects: bool,
62 include_output_objects: bool,
63 ) -> Result<Option<CachedTransactionData>, IotaError> {
64 // Default: no cache — safe fallback for executors like simulacrum.
65 let _ = (
66 digest,
67 include_events,
68 include_input_objects,
69 include_output_objects,
70 );
71 Ok(None)
72 }
73}
74
75/// Authoritative per-transaction data read from a local cache.
76pub struct CachedTransactionData {
77 pub effects: TransactionEffects,
78 pub events: Option<TransactionEvents>,
79 pub input_objects: Option<Vec<Object>>,
80 pub output_objects: Option<Vec<Object>>,
81}
82
83pub struct SimulateTransactionResult {
84 pub effects: TransactionEffects,
85 pub events: Option<TransactionEvents>,
86 /// Every object the transaction ran with as input — including immutable
87 /// and read-only shared inputs, the packages it calls, and the gas coins
88 /// (the mock one included) — plus the runtime-loaded objects (e.g. dynamic
89 /// fields) it modified, at their pre-state versions, keyed by id.
90 pub input_objects: BTreeMap<ObjectId, Object>,
91 pub output_objects: BTreeMap<ObjectId, Object>,
92 /// The return values and mutable-reference outputs of every command, under
93 /// either [`VmChecks`] — both run through the executor's dev-inspect entry
94 /// point, which collects them regardless of which checks are in force.
95 pub execution_result: Result<Vec<ExecutionResult>, ExecutionError>,
96 pub mock_gas_id: Option<ObjectId>,
97 pub suggested_gas_price: Option<u64>,
98 /// The gas the simulation ran with, once whatever the transaction left
99 /// unset was filled in. Callers reporting the transaction back should
100 /// use this rather than re-deriving it, which would read a possibly
101 /// different epoch.
102 pub gas_data: GasPayment,
103}
104
105/// Which Move VM checks a simulation runs with.
106///
107/// This is the only thing that separates the two ways a transaction can be
108/// simulated, so it is what callers pick between: a dry run wants
109/// [`VmChecks::Enabled`], a dev inspect wants [`VmChecks::Disabled`].
110#[derive(Default, Debug, Copy, Clone)]
111pub enum VmChecks {
112 /// Run the transaction as it would run on chain: the same input and gas
113 /// checks a validator applies, and metering against the transaction's own
114 /// budget.
115 #[default]
116 Enabled,
117 /// Relax the rules around entry functions and argument values, so that any
118 /// Move function can be called and any value built from its bytes. Input
119 /// checks are reduced to the ones execution cannot proceed without.
120 Disabled,
121}
122
123impl VmChecks {
124 pub fn disabled(self) -> bool {
125 matches!(self, Self::Disabled)
126 }
127
128 pub fn enabled(self) -> bool {
129 matches!(self, Self::Enabled)
130 }
131}