sui_adapter_v1/
temporary_store.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::gas_charger::GasCharger;
5use parking_lot::RwLock;
6use std::collections::{BTreeMap, BTreeSet, HashSet};
7use sui_protocol_config::ProtocolConfig;
8use sui_types::base_types::VersionDigest;
9use sui_types::committee::EpochId;
10use sui_types::digests::ObjectDigest;
11use sui_types::effects::{TransactionEffects, TransactionEvents};
12use sui_types::execution::{
13    DynamicallyLoadedObjectMetadata, ExecutionResults, ExecutionResultsV2, SharedInput,
14};
15use sui_types::execution_status::ExecutionStatus;
16use sui_types::inner_temporary_store::InnerTemporaryStore;
17use sui_types::layout_resolver::LayoutResolver;
18use sui_types::storage::{BackingStore, DenyListResult, PackageObject};
19use sui_types::sui_system_state::{get_sui_system_state_wrapper, AdvanceEpochParams};
20use sui_types::{
21    base_types::{ObjectID, ObjectRef, SequenceNumber, SuiAddress, TransactionDigest},
22    effects::EffectsObjectChange,
23    error::{ExecutionError, SuiResult},
24    gas::GasCostSummary,
25    object::Object,
26    object::Owner,
27    storage::{BackingPackageStore, ChildObjectResolver, ParentSync, Storage},
28    transaction::InputObjects,
29    TypeTag,
30};
31use sui_types::{is_system_package, SUI_SYSTEM_STATE_OBJECT_ID};
32
33pub struct TemporaryStore<'backing> {
34    // The backing store for retrieving Move packages onchain.
35    // When executing a Move call, the dependent packages are not going to be
36    // in the input objects. They will be fetched from the backing store.
37    // Also used for fetching the backing parent_sync to get the last known version for wrapped
38    // objects
39    store: &'backing dyn BackingStore,
40    tx_digest: TransactionDigest,
41    input_objects: BTreeMap<ObjectID, Object>,
42    deleted_consensus_objects: BTreeMap<ObjectID, SequenceNumber>,
43    /// The version to assign to all objects written by the transaction using this store.
44    lamport_timestamp: SequenceNumber,
45    mutable_input_refs: BTreeMap<ObjectID, (VersionDigest, Owner)>, // Inputs that are mutable
46    execution_results: ExecutionResultsV2,
47    /// Objects that were loaded during execution (dynamic fields + received objects).
48    loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
49    protocol_config: ProtocolConfig,
50
51    /// Every package that was loaded from DB store during execution.
52    /// These packages were not previously loaded into the temporary store.
53    runtime_packages_loaded_from_db: RwLock<BTreeMap<ObjectID, PackageObject>>,
54
55    /// The set of objects that we may receive during execution. Not guaranteed to receive all, or
56    /// any of the objects referenced in this set.
57    receiving_objects: Vec<ObjectRef>,
58}
59
60impl<'backing> TemporaryStore<'backing> {
61    /// Creates a new store associated with an authority store, and populates it with
62    /// initial objects.
63    pub fn new(
64        store: &'backing dyn BackingStore,
65        input_objects: InputObjects,
66        receiving_objects: Vec<ObjectRef>,
67        tx_digest: TransactionDigest,
68        protocol_config: &ProtocolConfig,
69    ) -> Self {
70        let mutable_input_refs = input_objects.exclusive_mutable_inputs();
71        let lamport_timestamp = input_objects.lamport_timestamp(&receiving_objects);
72        let deleted_consensus_objects = input_objects.consensus_stream_ended_objects();
73        let objects = input_objects.into_object_map();
74        Self {
75            store,
76            tx_digest,
77            input_objects: objects,
78            deleted_consensus_objects,
79            lamport_timestamp,
80            mutable_input_refs,
81            execution_results: ExecutionResultsV2::default(),
82            protocol_config: protocol_config.clone(),
83            loaded_runtime_objects: BTreeMap::new(),
84            runtime_packages_loaded_from_db: RwLock::new(BTreeMap::new()),
85            receiving_objects,
86        }
87    }
88
89    // Helpers to access private fields
90    pub fn objects(&self) -> &BTreeMap<ObjectID, Object> {
91        &self.input_objects
92    }
93
94    pub fn update_object_version_and_prev_tx(&mut self) {
95        self.execution_results.update_version_and_previous_tx(
96            self.lamport_timestamp,
97            self.tx_digest,
98            &self.input_objects,
99            false,
100        );
101
102        #[cfg(debug_assertions)]
103        {
104            self.check_invariants();
105        }
106    }
107
108    /// Break up the structure and return its internal stores (objects, active_inputs, written, deleted)
109    pub fn into_inner(self) -> InnerTemporaryStore {
110        let results = self.execution_results;
111        InnerTemporaryStore {
112            input_objects: self.input_objects,
113            stream_ended_consensus_objects: self.deleted_consensus_objects,
114            mutable_inputs: self.mutable_input_refs,
115            written: results.written_objects,
116            events: TransactionEvents {
117                data: results.user_events,
118            },
119            // no accumulator events for v1
120            accumulator_events: vec![],
121            loaded_runtime_objects: self.loaded_runtime_objects,
122            runtime_packages_loaded_from_db: self.runtime_packages_loaded_from_db.into_inner(),
123            lamport_version: self.lamport_timestamp,
124            binary_config: self.protocol_config.binary_config(None),
125        }
126    }
127
128    /// For every object from active_inputs (i.e. all mutable objects), if they are not
129    /// mutated during the transaction execution, force mutating them by incrementing the
130    /// sequence number. This is required to achieve safety.
131    pub(crate) fn ensure_active_inputs_mutated(&mut self) {
132        let mut to_be_updated = vec![];
133        for id in self.mutable_input_refs.keys() {
134            if !self.execution_results.modified_objects.contains(id) {
135                // We cannot update here but have to push to `to_be_updated` and update later
136                // because the for loop is holding a reference to `self`, and calling
137                // `self.write_object` requires a mutable reference to `self`.
138                to_be_updated.push(self.input_objects[id].clone());
139            }
140        }
141        for object in to_be_updated {
142            // The object must be mutated as it was present in the input objects
143            self.mutate_input_object(object.clone());
144        }
145    }
146
147    fn get_object_changes(&self) -> BTreeMap<ObjectID, EffectsObjectChange> {
148        let results = &self.execution_results;
149        let all_ids = results
150            .created_object_ids
151            .iter()
152            .chain(&results.deleted_object_ids)
153            .chain(&results.modified_objects)
154            .chain(results.written_objects.keys())
155            .collect::<BTreeSet<_>>();
156        all_ids
157            .into_iter()
158            .map(|id| {
159                (
160                    *id,
161                    EffectsObjectChange::new(
162                        self.get_object_modified_at(id)
163                            .map(|metadata| ((metadata.version, metadata.digest), metadata.owner)),
164                        results.written_objects.get(id),
165                        results.created_object_ids.contains(id),
166                        results.deleted_object_ids.contains(id),
167                    ),
168                )
169            })
170            .collect()
171    }
172
173    pub fn into_effects(
174        mut self,
175        shared_object_refs: Vec<SharedInput>,
176        transaction_digest: &TransactionDigest,
177        mut transaction_dependencies: BTreeSet<TransactionDigest>,
178        gas_cost_summary: GasCostSummary,
179        status: ExecutionStatus,
180        gas_charger: &mut GasCharger,
181        epoch: EpochId,
182    ) -> (InnerTemporaryStore, TransactionEffects) {
183        self.update_object_version_and_prev_tx();
184
185        // Regardless of execution status (including aborts), we insert the previous transaction
186        // for any successfully received objects during the transaction.
187        for (id, expected_version, expected_digest) in &self.receiving_objects {
188            // If the receiving object is in the loaded runtime objects, then that means that it
189            // was actually successfully loaded (so existed, and there was authenticated mutable
190            // access to it). So we insert the previous transaction as a dependency.
191            if let Some(obj_meta) = self.loaded_runtime_objects.get(id) {
192                // Check that the expected version, digest, and owner match the loaded version,
193                // digest, and owner. If they don't then don't register a dependency.
194                // This is because this could be "spoofed" by loading a dynamic object field.
195                let loaded_via_receive = obj_meta.version == *expected_version
196                    && obj_meta.digest == *expected_digest
197                    && obj_meta.owner.is_address_owned();
198                if loaded_via_receive {
199                    transaction_dependencies.insert(obj_meta.previous_transaction);
200                }
201            }
202        }
203
204        if self.protocol_config.enable_effects_v2() {
205            self.into_effects_v2(
206                shared_object_refs,
207                transaction_digest,
208                transaction_dependencies,
209                gas_cost_summary,
210                status,
211                gas_charger,
212                epoch,
213            )
214        } else {
215            let shared_object_refs = shared_object_refs
216                .into_iter()
217                .map(|shared_input| match shared_input {
218                    SharedInput::Existing(oref) => oref,
219                    SharedInput::ConsensusStreamEnded(_) => {
220                        unreachable!("Shared object deletion not supported in effects v1")
221                    }
222                    SharedInput::Cancelled(_) => {
223                        unreachable!("Per object congestion control not supported in effects v1.")
224                    }
225                })
226                .collect();
227            self.into_effects_v1(
228                shared_object_refs,
229                transaction_digest,
230                transaction_dependencies,
231                gas_cost_summary,
232                status,
233                gas_charger,
234                epoch,
235            )
236        }
237    }
238
239    fn into_effects_v1(
240        self,
241        shared_object_refs: Vec<ObjectRef>,
242        transaction_digest: &TransactionDigest,
243        transaction_dependencies: BTreeSet<TransactionDigest>,
244        gas_cost_summary: GasCostSummary,
245        status: ExecutionStatus,
246        gas_charger: &mut GasCharger,
247        epoch: EpochId,
248    ) -> (InnerTemporaryStore, TransactionEffects) {
249        let updated_gas_object_info = if let Some(coin_id) = gas_charger.gas_coin() {
250            let object = &self.execution_results.written_objects[&coin_id];
251            (object.compute_object_reference(), object.owner.clone())
252        } else {
253            (
254                (ObjectID::ZERO, SequenceNumber::default(), ObjectDigest::MIN),
255                Owner::AddressOwner(SuiAddress::default()),
256            )
257        };
258        let lampot_version = self.lamport_timestamp;
259
260        let mut created = vec![];
261        let mut mutated = vec![];
262        let mut unwrapped = vec![];
263        let mut deleted = vec![];
264        let mut unwrapped_then_deleted = vec![];
265        let mut wrapped = vec![];
266        // It is important that we constructs `modified_at_versions` and `deleted_at_versions`
267        // separately, and merge them latter to achieve the exact same order as in v1.
268        let mut modified_at_versions = vec![];
269        let mut deleted_at_versions = vec![];
270        self.execution_results
271            .written_objects
272            .iter()
273            .for_each(|(id, object)| {
274                let object_ref = object.compute_object_reference();
275                let owner = object.owner.clone();
276                if let Some(old_object_meta) = self.get_object_modified_at(id) {
277                    modified_at_versions.push((*id, old_object_meta.version));
278                    mutated.push((object_ref, owner));
279                } else if self.execution_results.created_object_ids.contains(id) {
280                    created.push((object_ref, owner));
281                } else {
282                    unwrapped.push((object_ref, owner));
283                }
284            });
285        self.execution_results
286            .modified_objects
287            .iter()
288            .filter(|id| !self.execution_results.written_objects.contains_key(id))
289            .for_each(|id| {
290                let old_object_meta = self.get_object_modified_at(id).unwrap();
291                deleted_at_versions.push((*id, old_object_meta.version));
292                if self.execution_results.deleted_object_ids.contains(id) {
293                    deleted.push((*id, lampot_version, ObjectDigest::OBJECT_DIGEST_DELETED));
294                } else {
295                    wrapped.push((*id, lampot_version, ObjectDigest::OBJECT_DIGEST_WRAPPED));
296                }
297            });
298        self.execution_results
299            .deleted_object_ids
300            .iter()
301            .filter(|id| !self.execution_results.modified_objects.contains(id))
302            .for_each(|id| {
303                unwrapped_then_deleted.push((
304                    *id,
305                    lampot_version,
306                    ObjectDigest::OBJECT_DIGEST_DELETED,
307                ));
308            });
309        modified_at_versions.extend(deleted_at_versions);
310
311        let inner = self.into_inner();
312        let effects = TransactionEffects::new_from_execution_v1(
313            status,
314            epoch,
315            gas_cost_summary,
316            modified_at_versions,
317            shared_object_refs,
318            *transaction_digest,
319            created,
320            mutated,
321            unwrapped,
322            deleted,
323            unwrapped_then_deleted,
324            wrapped,
325            updated_gas_object_info,
326            if inner.events.data.is_empty() {
327                None
328            } else {
329                Some(inner.events.digest())
330            },
331            transaction_dependencies.into_iter().collect(),
332        );
333        (inner, effects)
334    }
335
336    fn into_effects_v2(
337        self,
338        shared_object_refs: Vec<SharedInput>,
339        transaction_digest: &TransactionDigest,
340        transaction_dependencies: BTreeSet<TransactionDigest>,
341        gas_cost_summary: GasCostSummary,
342        status: ExecutionStatus,
343        gas_charger: &mut GasCharger,
344        epoch: EpochId,
345    ) -> (InnerTemporaryStore, TransactionEffects) {
346        // In the case of special transactions that don't require a gas object,
347        // we don't really care about the effects to gas, just use the input for it.
348        // Gas coins are guaranteed to be at least size 1 and if more than 1
349        // the first coin is where all the others are merged.
350        let gas_coin = gas_charger.gas_coin();
351
352        let object_changes = self.get_object_changes();
353
354        let lamport_version = self.lamport_timestamp;
355        let inner = self.into_inner();
356
357        let effects = TransactionEffects::new_from_execution_v2(
358            status,
359            epoch,
360            gas_cost_summary,
361            // TODO: Provide the list of read-only shared objects directly.
362            shared_object_refs,
363            BTreeSet::new(),
364            *transaction_digest,
365            lamport_version,
366            object_changes,
367            gas_coin,
368            if inner.events.data.is_empty() {
369                None
370            } else {
371                Some(inner.events.digest())
372            },
373            transaction_dependencies.into_iter().collect(),
374        );
375
376        (inner, effects)
377    }
378
379    /// An internal check of the invariants (will only fire in debug)
380    #[cfg(debug_assertions)]
381    fn check_invariants(&self) {
382        // Check not both deleted and written
383        debug_assert!(
384            {
385                self.execution_results
386                    .written_objects
387                    .keys()
388                    .all(|id| !self.execution_results.deleted_object_ids.contains(id))
389            },
390            "Object both written and deleted."
391        );
392
393        // Check all mutable inputs are modified
394        debug_assert!(
395            {
396                self.mutable_input_refs
397                    .keys()
398                    .all(|id| self.execution_results.modified_objects.contains(id))
399            },
400            "Mutable input not modified."
401        );
402
403        debug_assert!(
404            {
405                self.execution_results
406                    .written_objects
407                    .values()
408                    .all(|obj| obj.previous_transaction == self.tx_digest)
409            },
410            "Object previous transaction not properly set",
411        );
412    }
413
414    /// Mutate a mutable input object. This is used to mutate input objects outside of PT execution.
415    pub fn mutate_input_object(&mut self, object: Object) {
416        let id = object.id();
417        self.execution_results.modified_objects.insert(id);
418        self.execution_results.written_objects.insert(id, object);
419    }
420
421    /// Mutate a child object outside of PT. This should be used extremely rarely.
422    /// Currently it's only used by advance_epoch_safe_mode because it's all native
423    /// without PT. This should almost never be used otherwise.
424    pub fn mutate_child_object(&mut self, old_object: Object, new_object: Object) {
425        let id = new_object.id();
426        let old_ref = old_object.compute_object_reference();
427        debug_assert_eq!(old_ref.0, id);
428        self.loaded_runtime_objects.insert(
429            id,
430            DynamicallyLoadedObjectMetadata {
431                version: old_ref.1,
432                digest: old_ref.2,
433                owner: old_object.owner.clone(),
434                storage_rebate: old_object.storage_rebate,
435                previous_transaction: old_object.previous_transaction,
436            },
437        );
438        self.execution_results.modified_objects.insert(id);
439        self.execution_results
440            .written_objects
441            .insert(id, new_object);
442    }
443
444    /// Upgrade system package during epoch change. This requires special treatment
445    /// since the system package to be upgraded is not in the input objects.
446    /// We could probably fix above to make it less special.
447    /// Due to the special treatment, we need to read from object store explicitly
448    /// to obtain the modified_at information.
449    pub fn upgrade_system_package(&mut self, package: Object) {
450        let id = package.id();
451        assert!(package.is_package() && is_system_package(id));
452        self.execution_results.modified_objects.insert(id);
453        self.execution_results.written_objects.insert(id, package);
454    }
455
456    /// Crate a new objcet. This is used to create objects outside of PT execution.
457    pub fn create_object(&mut self, object: Object) {
458        // Created mutable objects' versions are set to the store's lamport timestamp when it is
459        // committed to effects. Creating an object at a non-zero version risks violating the
460        // lamport timestamp invariant (that a transaction's lamport timestamp is strictly greater
461        // than all versions witnessed by the transaction).
462        debug_assert!(
463            object.is_immutable() || object.version() == SequenceNumber::MIN,
464            "Created mutable objects should not have a version set",
465        );
466        let id = object.id();
467        self.execution_results.created_object_ids.insert(id);
468        self.execution_results.written_objects.insert(id, object);
469    }
470
471    /// Delete a mutable input object. This is used to delete input objects outside of PT execution.
472    pub fn delete_input_object(&mut self, id: &ObjectID) {
473        // there should be no deletion after write
474        debug_assert!(!self.execution_results.written_objects.contains_key(id));
475        self.execution_results.modified_objects.insert(*id);
476        self.execution_results.deleted_object_ids.insert(*id);
477    }
478
479    pub fn drop_writes(&mut self) {
480        self.execution_results.drop_writes();
481    }
482
483    pub fn read_object(&self, id: &ObjectID) -> Option<&Object> {
484        // there should be no read after delete
485        debug_assert!(!self.execution_results.deleted_object_ids.contains(id));
486        self.execution_results
487            .written_objects
488            .get(id)
489            .or_else(|| self.input_objects.get(id))
490    }
491
492    pub fn save_loaded_runtime_objects(
493        &mut self,
494        loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
495    ) {
496        #[cfg(debug_assertions)]
497        {
498            for (id, v1) in &loaded_runtime_objects {
499                if let Some(v2) = self.loaded_runtime_objects.get(id) {
500                    assert_eq!(v1, v2);
501                }
502            }
503            for (id, v1) in &self.loaded_runtime_objects {
504                if let Some(v2) = loaded_runtime_objects.get(id) {
505                    assert_eq!(v1, v2);
506                }
507            }
508        }
509        // Merge the two maps because we may be calling the execution engine more than once
510        // (e.g. in advance epoch transaction, where we may be publishing a new system package).
511        self.loaded_runtime_objects.extend(loaded_runtime_objects);
512    }
513
514    pub fn estimate_effects_size_upperbound(&self) -> usize {
515        if self.protocol_config.enable_effects_v2() {
516            TransactionEffects::estimate_effects_size_upperbound_v2(
517                self.execution_results.written_objects.len(),
518                self.execution_results.modified_objects.len(),
519                self.input_objects.len(),
520            )
521        } else {
522            let num_deletes = self.execution_results.deleted_object_ids.len()
523                + self
524                    .execution_results
525                    .modified_objects
526                    .iter()
527                    .filter(|id| {
528                        // Filter for wrapped objects.
529                        !self.execution_results.written_objects.contains_key(id)
530                            && !self.execution_results.deleted_object_ids.contains(id)
531                    })
532                    .count();
533            // In the worst case, the number of deps is equal to the number of input objects
534            TransactionEffects::estimate_effects_size_upperbound_v1(
535                self.execution_results.written_objects.len(),
536                self.mutable_input_refs.len(),
537                num_deletes,
538                self.input_objects.len(),
539            )
540        }
541    }
542
543    pub fn written_objects_size(&self) -> usize {
544        self.execution_results
545            .written_objects
546            .values()
547            .fold(0, |sum, obj| sum + obj.object_size_for_gas_metering())
548    }
549
550    /// If there are unmetered storage rebate (due to system transaction), we put them into
551    /// the storage rebate of 0x5 object.
552    /// TODO: This will not work for potential future new system transactions if 0x5 is not in the input.
553    /// We should fix this.
554    pub fn conserve_unmetered_storage_rebate(&mut self, unmetered_storage_rebate: u64) {
555        if unmetered_storage_rebate == 0 {
556            // If unmetered_storage_rebate is 0, we are most likely executing the genesis transaction.
557            // And in that case we cannot mutate the 0x5 object because it's newly created.
558            // And there is no storage rebate that needs distribution anyway.
559            return;
560        }
561        tracing::debug!(
562            "Amount of unmetered storage rebate from system tx: {:?}",
563            unmetered_storage_rebate
564        );
565        let mut system_state_wrapper = self
566            .read_object(&SUI_SYSTEM_STATE_OBJECT_ID)
567            .expect("0x5 object must be muated in system tx with unmetered storage rebate")
568            .clone();
569        // In unmetered execution, storage_rebate field of mutated object must be 0.
570        // If not, we would be dropping SUI on the floor by overriding it.
571        assert_eq!(system_state_wrapper.storage_rebate, 0);
572        system_state_wrapper.storage_rebate = unmetered_storage_rebate;
573        self.mutate_input_object(system_state_wrapper);
574    }
575
576    /// Given an object ID, if it's not modified, returns None.
577    /// Otherwise returns its metadata, including version, digest, owner and storage rebate.
578    /// A modified object must be either a mutable input, or a loaded child object.
579    /// The only exception is when we upgrade system packages, in which case the upgraded
580    /// system packages are not part of input, but are modified.
581    fn get_object_modified_at(
582        &self,
583        object_id: &ObjectID,
584    ) -> Option<DynamicallyLoadedObjectMetadata> {
585        if self.execution_results.modified_objects.contains(object_id) {
586            Some(
587                self.mutable_input_refs
588                    .get(object_id)
589                    .map(
590                        |((version, digest), owner)| DynamicallyLoadedObjectMetadata {
591                            version: *version,
592                            digest: *digest,
593                            owner: owner.clone(),
594                            // It's guaranteed that a mutable input object is an input object.
595                            storage_rebate: self.input_objects[object_id].storage_rebate,
596                            previous_transaction: self.input_objects[object_id]
597                                .previous_transaction,
598                        },
599                    )
600                    .or_else(|| self.loaded_runtime_objects.get(object_id).cloned())
601                    .unwrap_or_else(|| {
602                        debug_assert!(is_system_package(*object_id));
603                        let obj = self.store.get_object(object_id).unwrap();
604                        DynamicallyLoadedObjectMetadata {
605                            version: obj.version(),
606                            digest: obj.digest(),
607                            owner: obj.owner.clone(),
608                            storage_rebate: obj.storage_rebate,
609                            previous_transaction: obj.previous_transaction,
610                        }
611                    }),
612            )
613        } else {
614            None
615        }
616    }
617}
618
619impl TemporaryStore<'_> {
620    /// returns lists of (objects whose owner we must authenticate, objects whose owner has already been authenticated)
621    fn get_objects_to_authenticate(
622        &self,
623        sender: &SuiAddress,
624        gas_charger: &mut GasCharger,
625        is_epoch_change: bool,
626    ) -> SuiResult<(Vec<ObjectID>, HashSet<ObjectID>)> {
627        let gas_objs: HashSet<&ObjectID> = gas_charger.gas_coins().iter().map(|g| &g.0).collect();
628        let mut objs_to_authenticate = Vec::new();
629        let mut authenticated_objs = HashSet::new();
630        for (id, obj) in &self.input_objects {
631            if gas_objs.contains(id) {
632                // gas could be owned by either the sender (common case) or sponsor (if this is a sponsored tx,
633                // which we do not know inside this function).
634                // either way, no object ownership chain should be rooted in a gas object
635                // thus, consider object authenticated, but don't add it to authenticated_objs
636                continue;
637            }
638            match &obj.owner {
639                Owner::AddressOwner(a) => {
640                    assert!(sender == a, "Input object not owned by sender");
641                    authenticated_objs.insert(*id);
642                }
643                Owner::Shared { .. } => {
644                    authenticated_objs.insert(*id);
645                }
646                Owner::Immutable => {
647                    // object is authenticated, but it cannot own other objects,
648                    // so we should not add it to `authenticated_objs`
649                    // However, we would definitely want to add immutable objects
650                    // to the set of autehnticated roots if we were doing runtime
651                    // checks inside the VM instead of after-the-fact in the temporary
652                    // store. Here, we choose not to add them because this will catch a
653                    // bug where we mutate or delete an object that belongs to an immutable
654                    // object (though it will show up somewhat opaquely as an authentication
655                    // failure), whereas adding the immutable object to the roots will prevent
656                    // us from catching this.
657                }
658                Owner::ObjectOwner(_parent) => {
659                    unreachable!("Input objects must be address owned, shared, or immutable")
660                }
661                Owner::ConsensusAddressOwner { .. } => {
662                    unimplemented!(
663                        "ConsensusAddressOwner does not exist for this execution version"
664                    )
665                }
666            }
667        }
668
669        for id in &self.execution_results.modified_objects {
670            if authenticated_objs.contains(id) || gas_objs.contains(id) {
671                continue;
672            }
673            let old_obj = self.store.get_object(id).unwrap_or_else(|| {
674                panic!("Modified object must exist in the store: ID = {:?}", id)
675            });
676            match &old_obj.owner {
677                // ObjectOwner = dynamic field mutations
678                // AddressOwner = received object
679                Owner::ObjectOwner(_) | Owner::AddressOwner(_) => {
680                    objs_to_authenticate.push(*id);
681                }
682                Owner::Shared { .. } => {
683                    unreachable!("Should already be in authenticated_objs")
684                }
685                Owner::Immutable => {
686                    assert!(is_epoch_change, "Immutable objects cannot be written, except for Sui Framework/Move stdlib upgrades at epoch change boundaries");
687                    // Note: this assumes that the only immutable objects an epoch change tx can update are system packages,
688                    // but in principle we could allow others.
689                    assert!(
690                        is_system_package(*id),
691                        "Only system packages can be upgraded"
692                    );
693                }
694                Owner::ConsensusAddressOwner { .. } => {
695                    unimplemented!(
696                        "ConsensusAddressOwner does not exist for this execution version"
697                    )
698                }
699            }
700        }
701        Ok((objs_to_authenticate, authenticated_objs))
702    }
703
704    // check that every object read is owned directly or indirectly by sender, sponsor, or a shared object input
705    pub fn check_ownership_invariants(
706        &self,
707        sender: &SuiAddress,
708        gas_charger: &mut GasCharger,
709        is_epoch_change: bool,
710    ) -> SuiResult<()> {
711        let (mut objects_to_authenticate, mut authenticated_objects) =
712            self.get_objects_to_authenticate(sender, gas_charger, is_epoch_change)?;
713
714        // Map from an ObjectID to the ObjectID that covers it.
715        let mut covered = BTreeMap::new();
716        while let Some(to_authenticate) = objects_to_authenticate.pop() {
717            let Some(old_obj) = self.store.get_object(&to_authenticate) else {
718                // lookup failure is expected when the parent is an "object-less" UID (e.g., the ID of a table or bag)
719                // we cannot distinguish this case from an actual authentication failure, so continue
720                continue;
721            };
722            let parent = match &old_obj.owner {
723                Owner::ObjectOwner(parent) | Owner::AddressOwner(parent) => ObjectID::from(*parent),
724                owner => panic!(
725                    "Unauthenticated root at {to_authenticate:?} with owner {owner:?}\n\
726             Potentially covering objects in: {covered:#?}",
727                ),
728            };
729
730            if authenticated_objects.contains(&parent) {
731                authenticated_objects.insert(to_authenticate);
732            } else if !covered.contains_key(&parent) {
733                objects_to_authenticate.push(parent);
734            }
735
736            covered.insert(to_authenticate, parent);
737        }
738        Ok(())
739    }
740}
741
742impl TemporaryStore<'_> {
743    /// Track storage gas for each mutable input object (including the gas coin)
744    /// and each created object. Compute storage refunds for each deleted object.
745    /// Will *not* charge anything, gas status keeps track of storage cost and rebate.
746    /// All objects will be updated with their new (current) storage rebate/cost.
747    /// `SuiGasStatus` `storage_rebate` and `storage_gas_units` track the transaction
748    /// overall storage rebate and cost.
749    pub(crate) fn collect_storage_and_rebate(&mut self, gas_charger: &mut GasCharger) {
750        // Use two loops because we cannot mut iterate written while calling get_object_modified_at.
751        let old_storage_rebates: Vec<_> = self
752            .execution_results
753            .written_objects
754            .keys()
755            .map(|object_id| {
756                self.get_object_modified_at(object_id)
757                    .map(|metadata| metadata.storage_rebate)
758                    .unwrap_or_default()
759            })
760            .collect();
761        for (object, old_storage_rebate) in self
762            .execution_results
763            .written_objects
764            .values_mut()
765            .zip(old_storage_rebates)
766        {
767            // new object size
768            let new_object_size = object.object_size_for_gas_metering();
769            // track changes and compute the new object `storage_rebate`
770            let new_storage_rebate = gas_charger.track_storage_mutation(
771                object.id(),
772                new_object_size,
773                old_storage_rebate,
774            );
775            object.storage_rebate = new_storage_rebate;
776        }
777
778        self.collect_rebate(gas_charger);
779    }
780
781    pub(crate) fn collect_rebate(&self, gas_charger: &mut GasCharger) {
782        for object_id in &self.execution_results.modified_objects {
783            if self
784                .execution_results
785                .written_objects
786                .contains_key(object_id)
787            {
788                continue;
789            }
790            // get and track the deleted object `storage_rebate`
791            let storage_rebate = self
792                .get_object_modified_at(object_id)
793                // Unwrap is safe because this loop iterates through all modified objects.
794                .unwrap()
795                .storage_rebate;
796            gas_charger.track_storage_mutation(*object_id, 0, storage_rebate);
797        }
798    }
799
800    pub fn check_execution_results_consistency(&self) -> Result<(), ExecutionError> {
801        assert_invariant!(
802            self.execution_results
803                .created_object_ids
804                .iter()
805                .all(|id| !self.execution_results.deleted_object_ids.contains(id)
806                    && !self.execution_results.modified_objects.contains(id)),
807            "Created object IDs cannot also be deleted or modified"
808        );
809        assert_invariant!(
810            self.execution_results.modified_objects.iter().all(|id| {
811                self.mutable_input_refs.contains_key(id)
812                    || self.loaded_runtime_objects.contains_key(id)
813                    || is_system_package(*id)
814            }),
815            "A modified object must be either a mutable input, a loaded child object, or a system package"
816        );
817        Ok(())
818    }
819}
820//==============================================================================
821// Charge gas current - end
822//==============================================================================
823
824impl TemporaryStore<'_> {
825    pub fn advance_epoch_safe_mode(
826        &mut self,
827        params: &AdvanceEpochParams,
828        protocol_config: &ProtocolConfig,
829    ) {
830        let wrapper = get_sui_system_state_wrapper(self.store.as_object_store())
831            .expect("System state wrapper object must exist");
832        let (old_object, new_object) =
833            wrapper.advance_epoch_safe_mode(params, self.store.as_object_store(), protocol_config);
834        self.mutate_child_object(old_object, new_object);
835    }
836}
837
838type ModifiedObjectInfo<'a> = (
839    ObjectID,
840    // old object metadata, including version, digest, owner, and storage rebate.
841    Option<DynamicallyLoadedObjectMetadata>,
842    Option<&'a Object>,
843);
844
845impl TemporaryStore<'_> {
846    fn get_input_sui(
847        &self,
848        id: &ObjectID,
849        expected_version: SequenceNumber,
850        layout_resolver: &mut impl LayoutResolver,
851    ) -> Result<u64, ExecutionError> {
852        if let Some(obj) = self.input_objects.get(id) {
853            // the assumption here is that if it is in the input objects must be the right one
854            if obj.version() != expected_version {
855                invariant_violation!(
856                    "Version mismatching when resolving input object to check conservation--\
857                     expected {}, got {}",
858                    expected_version,
859                    obj.version(),
860                );
861            }
862            obj.get_total_sui(layout_resolver).map_err(|e| {
863                make_invariant_violation!(
864                    "Failed looking up input SUI in SUI conservation checking for input with \
865                         type {:?}: {e:#?}",
866                    obj.struct_tag(),
867                )
868            })
869        } else {
870            // not in input objects, must be a dynamic field
871            let Some(obj) = self.store.get_object_by_key(id, expected_version) else {
872                invariant_violation!(
873                    "Failed looking up dynamic field {id} in SUI conservation checking"
874                );
875            };
876            obj.get_total_sui(layout_resolver).map_err(|e| {
877                make_invariant_violation!(
878                    "Failed looking up input SUI in SUI conservation checking for type \
879                         {:?}: {e:#?}",
880                    obj.struct_tag(),
881                )
882            })
883        }
884    }
885
886    /// Return the list of all modified objects, for each object, returns
887    /// - Object ID,
888    /// - Input: If the object existed prior to this transaction, include their version and storage_rebate,
889    /// - Output: If a new version of the object is written, include the new object.
890    fn get_modified_objects(&self) -> Vec<ModifiedObjectInfo<'_>> {
891        self.execution_results
892            .modified_objects
893            .iter()
894            .map(|id| {
895                let metadata = self.get_object_modified_at(id);
896                let output = self.execution_results.written_objects.get(id);
897                (*id, metadata, output)
898            })
899            .chain(
900                self.execution_results
901                    .written_objects
902                    .iter()
903                    .filter_map(|(id, object)| {
904                        if self.execution_results.modified_objects.contains(id) {
905                            None
906                        } else {
907                            Some((*id, None, Some(object)))
908                        }
909                    }),
910            )
911            .collect()
912    }
913
914    /// Check that this transaction neither creates nor destroys SUI. This should hold for all txes
915    /// except the epoch change tx, which mints staking rewards equal to the gas fees burned in the
916    /// previous epoch.  Specifically, this checks two key invariants about storage
917    /// fees and storage rebate:
918    ///
919    /// 1. all SUI in storage rebate fields of input objects should flow either to the transaction
920    ///    storage rebate, or the transaction non-refundable storage rebate
921    /// 2. all SUI charged for storage should flow into the storage rebate field of some output
922    ///    object
923    ///
924    /// This function is intended to be called *after* we have charged for
925    /// gas + applied the storage rebate to the gas object, but *before* we
926    /// have updated object versions.
927    pub fn check_sui_conserved(
928        &self,
929        simple_conservation_checks: bool,
930        gas_summary: &GasCostSummary,
931    ) -> Result<(), ExecutionError> {
932        if !simple_conservation_checks {
933            return Ok(());
934        }
935        // total amount of SUI in storage rebate of input objects
936        let mut total_input_rebate = 0;
937        // total amount of SUI in storage rebate of output objects
938        let mut total_output_rebate = 0;
939        for (_, input, output) in self.get_modified_objects() {
940            if let Some(input) = input {
941                total_input_rebate += input.storage_rebate;
942            }
943            if let Some(object) = output {
944                total_output_rebate += object.storage_rebate;
945            }
946        }
947
948        if gas_summary.storage_cost == 0 {
949            // this condition is usually true when the transaction went OOG and no
950            // gas is left for storage charges.
951            // The storage cost has to be there at least for the gas coin which
952            // will not be deleted even when going to 0.
953            // However if the storage cost is 0 and if there is any object touched
954            // or deleted the value in input must be equal to the output plus rebate and
955            // non refundable.
956            // Rebate and non refundable will be positive when there are object deleted
957            // (gas smashing being the primary and possibly only example).
958            // A more typical condition is for all storage charges in summary to be 0 and
959            // then input and output must be the same value
960            if total_input_rebate
961                != total_output_rebate
962                    + gas_summary.storage_rebate
963                    + gas_summary.non_refundable_storage_fee
964            {
965                return Err(ExecutionError::invariant_violation(format!(
966                    "SUI conservation failed -- no storage charges in gas summary \
967                        and total storage input rebate {} not equal  \
968                        to total storage output rebate {}",
969                    total_input_rebate, total_output_rebate,
970                )));
971            }
972        } else {
973            // all SUI in storage rebate fields of input objects should flow either to
974            // the transaction storage rebate, or the non-refundable storage rebate pool
975            if total_input_rebate
976                != gas_summary.storage_rebate + gas_summary.non_refundable_storage_fee
977            {
978                return Err(ExecutionError::invariant_violation(format!(
979                    "SUI conservation failed -- {} SUI in storage rebate field of input objects, \
980                        {} SUI in tx storage rebate or tx non-refundable storage rebate",
981                    total_input_rebate, gas_summary.non_refundable_storage_fee,
982                )));
983            }
984
985            // all SUI charged for storage should flow into the storage rebate field
986            // of some output object
987            if gas_summary.storage_cost != total_output_rebate {
988                return Err(ExecutionError::invariant_violation(format!(
989                    "SUI conservation failed -- {} SUI charged for storage, \
990                        {} SUI in storage rebate field of output objects",
991                    gas_summary.storage_cost, total_output_rebate
992                )));
993            }
994        }
995        Ok(())
996    }
997
998    /// Check that this transaction neither creates nor destroys SUI.
999    /// This more expensive check will check a third invariant on top of the 2 performed
1000    /// by `check_sui_conserved` above:
1001    ///
1002    /// * all SUI in input objects (including coins etc in the Move part of an object) should flow
1003    ///   either to an output object, or be burned as part of computation fees or non-refundable
1004    ///   storage rebate
1005    ///
1006    /// This function is intended to be called *after* we have charged for gas + applied the
1007    /// storage rebate to the gas object, but *before* we have updated object versions. The
1008    /// advance epoch transaction would mint `epoch_fees` amount of SUI, and burn `epoch_rebates`
1009    /// amount of SUI. We need these information for this check.
1010    pub fn check_sui_conserved_expensive(
1011        &self,
1012        gas_summary: &GasCostSummary,
1013        advance_epoch_gas_summary: Option<(u64, u64)>,
1014        layout_resolver: &mut impl LayoutResolver,
1015    ) -> Result<(), ExecutionError> {
1016        // total amount of SUI in input objects, including both coins and storage rebates
1017        let mut total_input_sui = 0;
1018        // total amount of SUI in output objects, including both coins and storage rebates
1019        let mut total_output_sui = 0;
1020        for (id, input, output) in self.get_modified_objects() {
1021            if let Some(input) = input {
1022                total_input_sui += self.get_input_sui(&id, input.version, layout_resolver)?;
1023            }
1024            if let Some(object) = output {
1025                total_output_sui += object.get_total_sui(layout_resolver).map_err(|e| {
1026                    make_invariant_violation!(
1027                        "Failed looking up output SUI in SUI conservation checking for \
1028                         mutated type {:?}: {e:#?}",
1029                        object.struct_tag(),
1030                    )
1031                })?;
1032            }
1033        }
1034        // note: storage_cost flows into the storage_rebate field of the output objects, which is
1035        // why it is not accounted for here.
1036        // similarly, all of the storage_rebate *except* the storage_fund_rebate_inflow
1037        // gets credited to the gas coin both computation costs and storage rebate inflow are
1038        total_output_sui += gas_summary.computation_cost + gas_summary.non_refundable_storage_fee;
1039        if let Some((epoch_fees, epoch_rebates)) = advance_epoch_gas_summary {
1040            total_input_sui += epoch_fees;
1041            total_output_sui += epoch_rebates;
1042        }
1043        if total_input_sui != total_output_sui {
1044            return Err(ExecutionError::invariant_violation(format!(
1045                "SUI conservation failed: input={}, output={}, \
1046                    this transaction either mints or burns SUI",
1047                total_input_sui, total_output_sui,
1048            )));
1049        }
1050        Ok(())
1051    }
1052}
1053
1054impl ChildObjectResolver for TemporaryStore<'_> {
1055    fn read_child_object(
1056        &self,
1057        parent: &ObjectID,
1058        child: &ObjectID,
1059        child_version_upper_bound: SequenceNumber,
1060    ) -> SuiResult<Option<Object>> {
1061        let obj_opt = self.execution_results.written_objects.get(child);
1062        if obj_opt.is_some() {
1063            Ok(obj_opt.cloned())
1064        } else {
1065            self.store
1066                .read_child_object(parent, child, child_version_upper_bound)
1067        }
1068    }
1069
1070    fn get_object_received_at_version(
1071        &self,
1072        owner: &ObjectID,
1073        receiving_object_id: &ObjectID,
1074        receive_object_at_version: SequenceNumber,
1075        epoch_id: EpochId,
1076    ) -> SuiResult<Option<Object>> {
1077        // You should never be able to try and receive an object after deleting it or writing it in the same
1078        // transaction since `Receiving` doesn't have copy.
1079        debug_assert!(!self
1080            .execution_results
1081            .written_objects
1082            .contains_key(receiving_object_id));
1083        debug_assert!(!self
1084            .execution_results
1085            .deleted_object_ids
1086            .contains(receiving_object_id));
1087        self.store.get_object_received_at_version(
1088            owner,
1089            receiving_object_id,
1090            receive_object_at_version,
1091            epoch_id,
1092        )
1093    }
1094}
1095
1096impl Storage for TemporaryStore<'_> {
1097    fn reset(&mut self) {
1098        self.drop_writes();
1099    }
1100
1101    fn read_object(&self, id: &ObjectID) -> Option<&Object> {
1102        TemporaryStore::read_object(self, id)
1103    }
1104
1105    /// Take execution results v2, and translate it back to be compatible with effects v1.
1106    fn record_execution_results(
1107        &mut self,
1108        results: ExecutionResults,
1109    ) -> Result<(), ExecutionError> {
1110        let ExecutionResults::V2(results) = results else {
1111            panic!("ExecutionResults::V2 expected in sui-execution v1 and above");
1112        };
1113        // It's important to merge instead of override results because it's
1114        // possible to execute PT more than once during tx execution.
1115        self.execution_results.merge_results(results);
1116        Ok(())
1117    }
1118
1119    fn save_loaded_runtime_objects(
1120        &mut self,
1121        loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
1122    ) {
1123        TemporaryStore::save_loaded_runtime_objects(self, loaded_runtime_objects)
1124    }
1125
1126    fn save_wrapped_object_containers(
1127        &mut self,
1128        _wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
1129    ) {
1130        unreachable!("Unused in v1")
1131    }
1132
1133    fn check_coin_deny_list(
1134        &self,
1135        _receiving_funds_type_and_owners: BTreeMap<TypeTag, BTreeSet<SuiAddress>>,
1136    ) -> DenyListResult {
1137        unreachable!("Coin denylist v2 is not supported in sui-execution v1");
1138    }
1139
1140    fn record_generated_object_ids(&mut self, _generated_ids: BTreeSet<ObjectID>) {
1141        unreachable!(
1142            "Generated object IDs are not recorded in ExecutionResults in sui-execution v1"
1143        );
1144    }
1145}
1146
1147impl BackingPackageStore for TemporaryStore<'_> {
1148    fn get_package_object(&self, package_id: &ObjectID) -> SuiResult<Option<PackageObject>> {
1149        // We first check the objects in the temporary store because in non-production code path,
1150        // it is possible to read packages that are just written in the same transaction.
1151        // This can happen for example when we run the expensive conservation checks, where we may
1152        // look into the types of each written object in the output, and some of them need the
1153        // newly written packages for type checking.
1154        // In production path though, this should never happen.
1155        if let Some(obj) = self.read_object(package_id) {
1156            Ok(Some(PackageObject::new(obj.clone())))
1157        } else {
1158            self.store.get_package_object(package_id).inspect(|obj| {
1159                // Track object but leave unchanged
1160                if let Some(v) = obj {
1161                    if !self
1162                        .runtime_packages_loaded_from_db
1163                        .read()
1164                        .contains_key(package_id)
1165                    {
1166                        // TODO: Can this lock ever block execution?
1167                        // TODO: Why do we need a RwLock anyway???
1168                        self.runtime_packages_loaded_from_db
1169                            .write()
1170                            .insert(*package_id, v.clone());
1171                    }
1172                }
1173            })
1174        }
1175    }
1176}
1177
1178impl ParentSync for TemporaryStore<'_> {
1179    fn get_latest_parent_entry_ref_deprecated(&self, _object_id: ObjectID) -> Option<ObjectRef> {
1180        unreachable!("Never called in newer protocol versions")
1181    }
1182}