sui_types/
execution.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::{
5    MoveTypeTagTrait, SUI_SYSTEM_ADDRESS,
6    accumulator_event::AccumulatorEvent,
7    base_types::{ObjectID, ObjectRef, SequenceNumber},
8    digests::{ObjectDigest, TransactionDigest},
9    error::{ExecutionError, SuiError},
10    event::Event,
11    is_system_package,
12    object::{Data, Object, Owner},
13    storage::{BackingPackageStore, ObjectChange},
14    sui_system_state::SUI_SYSTEM_STATE_INNER_MODULE_NAME,
15    transaction::{Argument, Command, SharedObjectMutability},
16    type_input::TypeInput,
17};
18use move_core_types::{
19    ident_str,
20    identifier::IdentStr,
21    language_storage::{StructTag, TypeTag},
22};
23use serde::{Deserialize, Serialize};
24use std::collections::{BTreeMap, BTreeSet};
25use std::time::Duration;
26
27const EXECUTION_TIME_OBSERVATION_CHUNK_KEY_STRUCT: &IdentStr =
28    ident_str!("ExecutionTimeObservationChunkKey");
29
30/// A type containing all of the information needed to work in execution with an object whose
31/// consensus stream is ended, and when committing the execution effects of the transaction.
32/// This holds:
33/// 0. The object ID.
34/// 1. The version.
35/// 2. Whether the object appeared as mutable (or owned) in the transaction, or as read-only.
36/// 3. The transaction digest of the previous transaction that used this object mutably or
37///    took it by value.
38pub type ConsensusStreamEndedInfo = (
39    ObjectID,
40    SequenceNumber,
41    SharedObjectMutability,
42    TransactionDigest,
43);
44
45/// A sequence of information about removed consensus objects in the transaction's inputs.
46pub type ConsensusStreamEndedObjects = Vec<ConsensusStreamEndedInfo>;
47
48#[derive(Clone, Debug, PartialEq, Eq)]
49pub enum SharedInput {
50    Existing(ObjectRef),
51    ConsensusStreamEnded(ConsensusStreamEndedInfo),
52    Cancelled((ObjectID, SequenceNumber)),
53}
54
55#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
56pub struct DynamicallyLoadedObjectMetadata {
57    pub version: SequenceNumber,
58    pub digest: ObjectDigest,
59    pub owner: Owner,
60    pub storage_rebate: u64,
61    pub previous_transaction: TransactionDigest,
62}
63
64/// View of the store necessary to produce the layouts of types.
65pub trait TypeLayoutStore: BackingPackageStore {}
66impl<T> TypeLayoutStore for T where T: BackingPackageStore {}
67
68#[derive(Debug)]
69pub enum ExecutionResults {
70    V1(ExecutionResultsV1),
71    V2(ExecutionResultsV2),
72}
73
74#[derive(Debug)]
75pub struct ExecutionResultsV1 {
76    pub object_changes: BTreeMap<ObjectID, ObjectChange>,
77    pub user_events: Vec<Event>,
78}
79
80/// Used by sui-execution v1 and above, to capture the execution results from Move.
81/// The results represent the primitive information that can then be used to construct
82/// both transaction effects V1 and V2.
83#[derive(Debug, Default)]
84pub struct ExecutionResultsV2 {
85    /// All objects written regardless of whether they were mutated, created, or unwrapped.
86    pub written_objects: BTreeMap<ObjectID, Object>,
87    /// All objects that existed prior to this transaction, and are modified in this transaction.
88    /// This includes any type of modification, including mutated, wrapped and deleted objects.
89    pub modified_objects: BTreeSet<ObjectID>,
90    /// All object IDs created in this transaction.
91    pub created_object_ids: BTreeSet<ObjectID>,
92    /// All object IDs deleted in this transaction.
93    /// No object ID should be in both created_object_ids and deleted_object_ids.
94    pub deleted_object_ids: BTreeSet<ObjectID>,
95    /// All Move events emitted in this transaction.
96    pub user_events: Vec<Event>,
97    /// All accumulator events emitted in this transaction.
98    pub accumulator_events: Vec<AccumulatorEvent>,
99
100    /// Used to track SUI conservation in settlement transactions. Settlement transactions
101    /// gather up withdraws and deposits from other transactions, and record them to accumulator
102    /// fields. The settlement transaction records the total amount of SUI being disbursed here,
103    /// so that we can verify that the amount stored in the fields at the end of the transaction
104    /// is correct.
105    pub settlement_input_sui: u64,
106    pub settlement_output_sui: u64,
107}
108
109pub type ExecutionResult = (
110    /*  mutable_reference_outputs */ Vec<(Argument, Vec<u8>, TypeTag)>,
111    /*  return_values */ Vec<(Vec<u8>, TypeTag)>,
112);
113
114impl ExecutionResultsV2 {
115    pub fn drop_writes(&mut self) {
116        self.written_objects.clear();
117        self.modified_objects.clear();
118        self.created_object_ids.clear();
119        self.deleted_object_ids.clear();
120        self.user_events.clear();
121        self.accumulator_events.clear();
122    }
123
124    /// If `consistent_merge` is true, the deletes and writes in `new_results` will update the
125    /// results any existing writes and deletes in `self` respectively. If false, it is assumed
126    /// that deletes and writes are disjoint.
127    /// If `invariant_checks` is true, the function will check for disjointness between deleted
128    /// and created/written objects.
129    pub fn merge_results(
130        &mut self,
131        new_results: Self,
132        consistent_merge: bool,
133        invariant_checks: bool,
134    ) -> Result<(), ExecutionError> {
135        if consistent_merge {
136            // An object written before the merge (e.g., gas coin written by smash_gas) may be
137            // deleted by the new results (e.g., send_funds destroying the gas coin during PTB
138            // execution). Remove such stale entries.
139            for id in &new_results.deleted_object_ids {
140                self.written_objects.remove(id);
141                // additional hardening
142                self.created_object_ids.remove(id);
143            }
144            // While not possible currently, we should ensure that any object previously marked as
145            // deleted is now marked only as written
146            for id in new_results.written_objects.keys() {
147                self.deleted_object_ids.remove(id);
148            }
149        }
150
151        self.written_objects.extend(new_results.written_objects);
152        self.modified_objects.extend(new_results.modified_objects);
153        self.created_object_ids
154            .extend(new_results.created_object_ids);
155        self.deleted_object_ids
156            .extend(new_results.deleted_object_ids);
157
158        if invariant_checks {
159            // debug assert that deleted is disjoint with created and written
160            assert_invariant!(
161                self.deleted_object_ids
162                    .is_disjoint(&self.created_object_ids),
163                "Deleted object IDs should be disjoint with created object IDs"
164            );
165            assert_invariant!(
166                self.written_objects
167                    .keys()
168                    .all(|id| !self.deleted_object_ids.contains(id)),
169                "Deleted object IDs should be disjoint with written object IDs"
170            );
171        }
172        self.user_events.extend(new_results.user_events);
173        self.accumulator_events
174            .extend(new_results.accumulator_events);
175        self.settlement_input_sui += new_results.settlement_input_sui;
176        self.settlement_output_sui += new_results.settlement_output_sui;
177        Ok(())
178    }
179
180    pub fn update_version_and_previous_tx(
181        &mut self,
182        lamport_version: SequenceNumber,
183        prev_tx: TransactionDigest,
184        input_objects: &BTreeMap<ObjectID, Object>,
185        reshare_at_initial_version: bool,
186    ) {
187        for (id, obj) in self.written_objects.iter_mut() {
188            // TODO: We can now get rid of the following logic by passing in lamport version
189            // into the execution layer, and create new objects using the lamport version directly.
190
191            // Update the version for the written object.
192            match &mut obj.data {
193                Data::Move(obj) => {
194                    // Move objects all get the transaction's lamport timestamp
195                    obj.increment_version_to(lamport_version);
196                }
197
198                Data::Package(pkg) => {
199                    // Modified packages get their version incremented (this is a special case that
200                    // only applies to system packages).  All other packages can only be created,
201                    // and they are left alone.
202                    if self.modified_objects.contains(id) {
203                        debug_assert!(is_system_package(*id));
204                        pkg.increment_version();
205                    }
206                }
207            }
208
209            // Record the version that the shared object was created at in its owner field.  Note,
210            // this only works because shared objects must be created as shared (not created as
211            // owned in one transaction and later converted to shared in another).
212            if let Owner::Shared {
213                initial_shared_version,
214            } = &mut obj.owner
215            {
216                if self.created_object_ids.contains(id) {
217                    assert_eq!(
218                        *initial_shared_version,
219                        SequenceNumber::new(),
220                        "Initial version should be blank before this point for {id:?}",
221                    );
222                    *initial_shared_version = lamport_version;
223                }
224
225                // Update initial_shared_version for reshared objects
226                if reshare_at_initial_version
227                    && let Some(Owner::Shared {
228                        initial_shared_version: previous_initial_shared_version,
229                    }) = input_objects.get(id).map(|obj| &obj.owner)
230                {
231                    debug_assert!(!self.created_object_ids.contains(id));
232                    debug_assert!(!self.deleted_object_ids.contains(id));
233                    debug_assert!(
234                        *initial_shared_version == SequenceNumber::new()
235                            || *initial_shared_version == *previous_initial_shared_version
236                    );
237
238                    *initial_shared_version = *previous_initial_shared_version;
239                }
240            }
241
242            // Record start version for ConsensusAddressOwner objects.
243            if let Owner::ConsensusAddressOwner {
244                start_version,
245                owner,
246            } = &mut obj.owner
247            {
248                debug_assert!(!self.deleted_object_ids.contains(id));
249
250                if let Some(Owner::ConsensusAddressOwner {
251                    start_version: previous_start_version,
252                    owner: previous_owner,
253                }) = input_objects.get(id).map(|obj| &obj.owner)
254                {
255                    if owner == previous_owner {
256                        // Assign existing start_version in case a ConsensusAddressOwner object was
257                        // transferred to the same owner.
258                        *start_version = *previous_start_version;
259                    } else {
260                        // If owner changes, we need to begin a new stream.
261                        *start_version = lamport_version;
262                    }
263                } else {
264                    // ConsensusAddressOwner object was created, transferred from another Owner
265                    // type, or unwrapped, so we begin a new stream.
266                    *start_version = lamport_version;
267                }
268            }
269
270            obj.previous_transaction = prev_tx;
271        }
272    }
273}
274
275#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Clone, Serialize, Deserialize)]
276pub enum ExecutionTimeObservationKey {
277    // Contains all the fields from `ProgrammableMoveCall` besides `arguments`.
278    MoveEntryPoint {
279        /// The package containing the module and function.
280        package: ObjectID,
281        /// The specific module in the package containing the function.
282        module: String,
283        /// The function to be called.
284        function: String,
285        /// The type arguments to the function.
286        /// NOTE: This field is currently not populated.
287        type_arguments: Vec<TypeInput>,
288    },
289    TransferObjects,
290    SplitCoins,
291    MergeCoins,
292    Publish, // special case: should not be used; we only use hard-coded estimate for this
293    MakeMoveVec,
294    Upgrade,
295}
296
297impl std::fmt::Display for ExecutionTimeObservationKey {
298    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
299        match self {
300            ExecutionTimeObservationKey::MoveEntryPoint {
301                module, function, ..
302            } => {
303                write!(f, "{}:{}", module, function)
304            }
305            ExecutionTimeObservationKey::TransferObjects => write!(f, "TransferObjects"),
306            ExecutionTimeObservationKey::SplitCoins => write!(f, "SplitCoins"),
307            ExecutionTimeObservationKey::MergeCoins => write!(f, "MergeCoins"),
308            ExecutionTimeObservationKey::Publish => write!(f, "Publish"),
309            ExecutionTimeObservationKey::MakeMoveVec => write!(f, "MakeMoveVec"),
310            ExecutionTimeObservationKey::Upgrade => write!(f, "Upgrade"),
311        }
312    }
313}
314
315impl ExecutionTimeObservationKey {
316    pub fn is_move_call(&self) -> bool {
317        matches!(self, ExecutionTimeObservationKey::MoveEntryPoint { .. })
318    }
319
320    pub fn from_command(command: &Command) -> Self {
321        match command {
322            Command::MoveCall(call) => ExecutionTimeObservationKey::MoveEntryPoint {
323                package: call.package,
324                module: call.module.clone(),
325                function: call.function.clone(),
326                type_arguments: vec![],
327            },
328            Command::TransferObjects(_, _) => ExecutionTimeObservationKey::TransferObjects,
329            Command::SplitCoins(_, _) => ExecutionTimeObservationKey::SplitCoins,
330            Command::MergeCoins(_, _) => ExecutionTimeObservationKey::MergeCoins,
331            Command::Publish(_, _) => ExecutionTimeObservationKey::Publish,
332            Command::MakeMoveVec(_, _) => ExecutionTimeObservationKey::MakeMoveVec,
333            Command::Upgrade(_, _, _, _) => ExecutionTimeObservationKey::Upgrade,
334        }
335    }
336
337    pub fn default_duration(&self) -> Duration {
338        match self {
339            ExecutionTimeObservationKey::MoveEntryPoint { .. } => Duration::from_millis(1),
340            ExecutionTimeObservationKey::TransferObjects => Duration::from_millis(1),
341            ExecutionTimeObservationKey::SplitCoins => Duration::from_millis(1),
342            ExecutionTimeObservationKey::MergeCoins => Duration::from_millis(1),
343            ExecutionTimeObservationKey::Publish => Duration::from_millis(3),
344            ExecutionTimeObservationKey::MakeMoveVec => Duration::from_millis(1),
345            ExecutionTimeObservationKey::Upgrade => Duration::from_millis(3),
346        }
347    }
348}
349
350#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Clone, Serialize, Deserialize)]
351pub struct ExecutionTimeObservationChunkKey {
352    pub chunk_index: u64,
353}
354
355impl MoveTypeTagTrait for ExecutionTimeObservationChunkKey {
356    fn get_type_tag() -> TypeTag {
357        TypeTag::Struct(Box::new(StructTag {
358            address: SUI_SYSTEM_ADDRESS,
359            module: SUI_SYSTEM_STATE_INNER_MODULE_NAME.to_owned(),
360            name: EXECUTION_TIME_OBSERVATION_CHUNK_KEY_STRUCT.to_owned(),
361            type_params: vec![],
362        }))
363    }
364}
365
366#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
367pub enum ExecutionTiming {
368    Success(Duration),
369    Abort(Duration),
370}
371
372impl ExecutionTiming {
373    pub fn is_abort(&self) -> bool {
374        matches!(self, ExecutionTiming::Abort(_))
375    }
376
377    pub fn duration(&self) -> Duration {
378        match self {
379            ExecutionTiming::Success(duration) => *duration,
380            ExecutionTiming::Abort(duration) => *duration,
381        }
382    }
383}
384
385pub type ResultWithTimings<R, E> = Result<(R, Vec<ExecutionTiming>), (E, Vec<ExecutionTiming>)>;
386
387/// Captures the output of executing a transaction in the execution driver.
388#[derive(Debug)]
389pub enum ExecutionOutput<T> {
390    /// The expected typical path - transaction executed successfully.
391    Success(T),
392    /// Validator has halted at epoch end or epoch mismatch. This is a valid state that should
393    /// be handled gracefully.
394    EpochEnded,
395    /// Execution failed with an error. This should never happen - we use fatal! when encountered.
396    Fatal(SuiError),
397    /// Execution should be retried later due to unsatisfied constraints such as insufficient object
398    /// balance withdrawals that require waiting for the balance to reach a deterministic amount.
399    /// When this happens, the transaction is auto-rescheduled from AuthorityState.
400    RetryLater,
401}
402
403impl<T> ExecutionOutput<T> {
404    /// Unwraps the ExecutionOutput, panicking if it's not Success.
405    /// This is primarily for test code.
406    pub fn unwrap(self) -> T {
407        match self {
408            ExecutionOutput::Success(value) => value,
409            ExecutionOutput::EpochEnded => {
410                panic!("called `ExecutionOutput::unwrap()` on `EpochEnded`")
411            }
412            ExecutionOutput::Fatal(e) => {
413                panic!("called `ExecutionOutput::unwrap()` on `Fatal`: {e}")
414            }
415            ExecutionOutput::RetryLater => {
416                panic!("called `ExecutionOutput::unwrap()` on `RetryLater`")
417            }
418        }
419    }
420
421    /// Expect the execution output to be an error (i.e. not Success).
422    pub fn unwrap_err<S>(self) -> ExecutionOutput<S> {
423        match self {
424            Self::Success(_) => {
425                panic!("called `ExecutionOutput::unwrap_err()` on `Success`")
426            }
427            Self::EpochEnded => ExecutionOutput::EpochEnded,
428            Self::Fatal(e) => ExecutionOutput::Fatal(e),
429            Self::RetryLater => ExecutionOutput::RetryLater,
430        }
431    }
432}