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