Skip to main content

iota_types/
execution.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::{
6    collections::{BTreeMap, BTreeSet, HashSet},
7    time::Duration,
8};
9
10use iota_sdk_types::{
11    Argument, Event, ObjectData, ObjectDigest, ObjectId, ObjectReference, Owner, TransactionDigest,
12    TypeTag, Version,
13};
14use once_cell::sync::Lazy;
15use serde::{Deserialize, Serialize};
16
17use crate::{
18    object::{MoveStructExt, Object},
19    storage::BackingPackageStore,
20};
21
22/// A type containing all of the information needed to work with a deleted
23/// shared object in execution and when committing the execution effects of the
24/// transaction. This holds:
25/// 0. The object ID of the deleted shared object.
26/// 1. The version of the shared object.
27/// 2. Whether the object appeared as mutable (or owned) in the transaction, or
28///    as a read-only shared object.
29/// 3. The transaction digest of the previous transaction that used this shared
30///    object mutably or took it by value.
31pub type DeletedSharedObjectInfo = (ObjectId, Version, bool, TransactionDigest);
32
33/// A sequence of information about deleted shared objects in the transaction's
34/// inputs.
35pub type DeletedSharedObjects = Vec<DeletedSharedObjectInfo>;
36
37#[derive(Clone, Debug, PartialEq, Eq)]
38pub enum SharedInput {
39    Existing(ObjectReference),
40    Deleted(DeletedSharedObjectInfo),
41    Cancelled((ObjectId, Version)),
42}
43
44#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
45pub struct DynamicallyLoadedObjectMetadata {
46    pub version: Version,
47    pub digest: ObjectDigest,
48    pub owner: Owner,
49    pub storage_rebate: u64,
50    pub previous_transaction: TransactionDigest,
51}
52
53/// View of the store necessary to produce the layouts of types.
54pub trait TypeLayoutStore: BackingPackageStore {}
55impl<T> TypeLayoutStore for T where T: BackingPackageStore {}
56
57#[derive(Debug)]
58pub enum ExecutionResults {
59    V1(ExecutionResultsV1),
60}
61
62/// Used by iota-execution v1 and above, to capture the execution results from
63/// Move. The results represent the primitive information that can then be used
64/// to construct both transaction effect V1.
65#[derive(Debug, Default)]
66pub struct ExecutionResultsV1 {
67    /// All objects written regardless of whether they were mutated, created, or
68    /// unwrapped.
69    pub written_objects: BTreeMap<ObjectId, Object>,
70    /// All objects that existed prior to this transaction, and are modified in
71    /// this transaction. This includes any type of modification, including
72    /// mutated, wrapped and deleted objects.
73    pub modified_objects: BTreeSet<ObjectId>,
74    /// All object IDs created in this transaction.
75    pub created_object_ids: BTreeSet<ObjectId>,
76    /// All object IDs deleted in this transaction.
77    /// No object ID should be in both created_object_ids and
78    /// deleted_object_ids.
79    pub deleted_object_ids: BTreeSet<ObjectId>,
80    /// All Move events emitted in this transaction.
81    pub user_events: Vec<Event>,
82}
83
84pub type ExecutionResult = (
85    // mutable_reference_outputs
86    Vec<(Argument, Vec<u8>, TypeTag)>,
87    // return_values
88    Vec<(Vec<u8>, TypeTag)>,
89);
90
91impl ExecutionResultsV1 {
92    pub fn drop_writes(&mut self) {
93        self.written_objects.clear();
94        self.modified_objects.clear();
95        self.created_object_ids.clear();
96        self.deleted_object_ids.clear();
97        self.user_events.clear();
98    }
99
100    pub fn merge_results(&mut self, new_results: Self) {
101        self.written_objects.extend(new_results.written_objects);
102        self.modified_objects.extend(new_results.modified_objects);
103        self.created_object_ids
104            .extend(new_results.created_object_ids);
105        self.deleted_object_ids
106            .extend(new_results.deleted_object_ids);
107        self.user_events.extend(new_results.user_events);
108    }
109
110    pub fn update_version_and_previous_tx(
111        &mut self,
112        lamport_version: Version,
113        prev_tx: TransactionDigest,
114        input_objects: &BTreeMap<ObjectId, Object>,
115    ) {
116        for (id, obj) in self.written_objects.iter_mut() {
117            // TODO: We can now get rid of the following logic by passing in lamport version
118            // into the execution layer, and create new objects using the lamport version
119            // directly.
120
121            // Update the version for the written object.
122            match &mut obj.data {
123                ObjectData::Struct(obj) => {
124                    // Move objects all get the transaction's lamport timestamp
125                    obj.increment_version_to(lamport_version);
126                }
127
128                ObjectData::Package(pkg) => {
129                    // Modified packages get their version incremented (this is a special case that
130                    // only applies to system packages).  All other packages can only be created,
131                    // and they are left alone.
132                    if self.modified_objects.contains(id) {
133                        debug_assert!(id.is_system_package());
134                        pkg.increment_version()
135                            .expect("package version should never overflow");
136                    }
137                }
138
139                _ => unimplemented!(
140                    "a new ObjectData enum variant was added and needs to be handled"
141                ),
142            }
143
144            // Record the version that the shared object was created at in its owner field.
145            // Note, this only works because shared objects must be created as
146            // shared (not created as owned in one transaction and later
147            // converted to shared in another).
148            if let Owner::Shared(initial_shared_version) = &mut obj.owner {
149                if self.created_object_ids.contains(id) {
150                    assert_eq!(
151                        *initial_shared_version,
152                        Version::default(),
153                        "Initial version should be blank before this point for {id}",
154                    );
155                    *initial_shared_version = lamport_version;
156                }
157
158                // Update initial_shared_version for reshared objects
159                if let Some(previous_initial_shared_version) = input_objects
160                    .get(id)
161                    .and_then(|obj| obj.owner.as_opt_shared())
162                {
163                    debug_assert!(!self.created_object_ids.contains(id));
164                    debug_assert!(!self.deleted_object_ids.contains(id));
165                    debug_assert!(
166                        *initial_shared_version == Version::default()
167                            || *initial_shared_version == *previous_initial_shared_version
168                    );
169
170                    *initial_shared_version = *previous_initial_shared_version;
171                }
172            }
173
174            obj.previous_transaction = prev_tx;
175        }
176    }
177}
178
179pub enum ExecutionTiming {
180    Success(Duration),
181    Abort(Duration),
182}
183pub type ResultWithTimings<R, E> = Result<(R, Vec<ExecutionTiming>), (E, Vec<ExecutionTiming>)>;
184
185/// If a transaction digest shows up in this list, when executing such
186/// transaction, we will always return `ExecutionError::CertificateDenied`
187/// without executing it (but still do gas smashing). Because this list is not
188/// gated by protocol version, there are a few important criteria for adding a
189/// digest to this list:
190/// 1. The certificate must be causing all validators to either panic or hang
191///    forever deterministically.
192/// 2. If we ever ship a fix to make it no longer panic or hang when executing
193///    such transaction, we must make sure the transaction is already in this
194///    list. Otherwise nodes running the newer version without these
195///    transactions in the list will generate forked result.
196///
197/// Below is a scenario of when we need to use this list:
198/// 1. We detect that a specific transaction is causing all validators to either
199///    panic or hang forever deterministically.
200/// 2. We push a CertificateDenyConfig to deny such transaction to all
201///    validators asap.
202/// 3. To make sure that all fullnodes are able to sync to the latest version,
203///    we need to add the transaction digest to this list as well asap, and ship
204///    this binary to all fullnodes, so that they can sync past this
205///    transaction.
206/// 4. We then can start fixing the issue, and ship the fix to all nodes.
207/// 5. Unfortunately, we can't remove the transaction digest from this list,
208///    because if we do so, any future node that sync from genesis will fork on
209///    this transaction. We may be able to remove it once we have stable
210///    snapshots and the binary has a minimum supported protocol version past
211///    the epoch.
212pub fn get_denied_certificates() -> &'static HashSet<TransactionDigest> {
213    static DENIED_CERTIFICATES: Lazy<HashSet<TransactionDigest>> = Lazy::new(|| HashSet::from([]));
214    Lazy::force(&DENIED_CERTIFICATES)
215}
216
217pub fn is_certificate_denied(
218    transaction_digest: &TransactionDigest,
219    certificate_deny_set: &HashSet<TransactionDigest>,
220) -> bool {
221    certificate_deny_set.contains(transaction_digest)
222        || get_denied_certificates().contains(transaction_digest)
223}