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