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                }
715            })
716            .filter(|id| {
717                // remove any non-mutable inputs. This will remove deleted or readonly shared
718                // objects
719                mutable_inputs.contains(id)
720            })
721            .copied()
722            .collect();
723
724        // check all modified objects are authenticated (excluding gas objects)
725        let mut objects_to_authenticate = self
726            .execution_results
727            .modified_objects
728            .iter()
729            .filter(|id| !gas_objs.contains(id))
730            .copied()
731            .collect::<Vec<_>>();
732        // Map from an ObjectID to the ObjectID that covers it.
733        while let Some(to_authenticate) = objects_to_authenticate.pop() {
734            if authenticated_for_mutation.contains(&to_authenticate) {
735                // object has been authenticated
736                continue;
737            }
738            let wrapped_parent = self.wrapped_object_containers.get(&to_authenticate);
739            let parent = if let Some(container_id) = wrapped_parent {
740                // If the object is wrapped, then the container must be authenticated.
741                // For example, the ID is for a wrapped table or bag.
742                *container_id
743            } else {
744                let Some(old_obj) = self.store.get_object(&to_authenticate) else {
745                    panic!(
746                        "
747                        Failed to load object {to_authenticate:?}. \n\
748                        If it cannot be loaded, \
749                        we would expect it to be in the wrapped object map: {:?}",
750                        &self.wrapped_object_containers
751                    )
752                };
753                match &old_obj.owner {
754                    Owner::ObjectOwner(parent) => ObjectID::from(*parent),
755                    Owner::AddressOwner(parent) => {
756                        // For Receiving<_> objects, the address owner is actually an object.
757                        // If it was actually an address, we should have caught it as an input and
758                        // it would already have been in authenticated_for_mutation
759                        ObjectID::from(*parent)
760                    }
761                    owner @ Owner::Shared { .. } => panic!(
762                        "Unauthenticated root at {to_authenticate:?} with owner {owner:?}\n\
763                        Potentially covering objects in: {authenticated_for_mutation:#?}",
764                    ),
765                    Owner::Immutable => {
766                        assert!(
767                            is_epoch_change,
768                            "Immutable objects cannot be written, except for \
769                            Sui Framework/Move stdlib upgrades at epoch change boundaries"
770                        );
771                        // Note: this assumes that the only immutable objects an epoch change
772                        // tx can update are system packages,
773                        // but in principle we could allow others.
774                        assert!(
775                            is_system_package(to_authenticate),
776                            "Only system packages can be upgraded"
777                        );
778                        continue;
779                    }
780                    Owner::ConsensusAddressOwner { .. } => {
781                        unimplemented!(
782                            "ConsensusAddressOwner does not exist for this execution version"
783                        )
784                    }
785                }
786            };
787            // we now assume the object is authenticated and must check the parent
788            authenticated_for_mutation.insert(to_authenticate);
789            objects_to_authenticate.push(parent);
790        }
791        Ok(())
792    }
793}
794
795impl TemporaryStore<'_> {
796    /// Track storage gas for each mutable input object (including the gas coin)
797    /// and each created object. Compute storage refunds for each deleted object.
798    /// Will *not* charge anything, gas status keeps track of storage cost and rebate.
799    /// All objects will be updated with their new (current) storage rebate/cost.
800    /// `SuiGasStatus` `storage_rebate` and `storage_gas_units` track the transaction
801    /// overall storage rebate and cost.
802    pub(crate) fn collect_storage_and_rebate(&mut self, gas_charger: &mut GasCharger) {
803        // Use two loops because we cannot mut iterate written while calling get_object_modified_at.
804        let old_storage_rebates: Vec<_> = self
805            .execution_results
806            .written_objects
807            .keys()
808            .map(|object_id| {
809                self.get_object_modified_at(object_id)
810                    .map(|metadata| metadata.storage_rebate)
811                    .unwrap_or_default()
812            })
813            .collect();
814        for (object, old_storage_rebate) in self
815            .execution_results
816            .written_objects
817            .values_mut()
818            .zip(old_storage_rebates)
819        {
820            // new object size
821            let new_object_size = object.object_size_for_gas_metering();
822            // track changes and compute the new object `storage_rebate`
823            let new_storage_rebate = gas_charger.track_storage_mutation(
824                object.id(),
825                new_object_size,
826                old_storage_rebate,
827            );
828            object.storage_rebate = new_storage_rebate;
829        }
830
831        self.collect_rebate(gas_charger);
832    }
833
834    pub(crate) fn collect_rebate(&self, gas_charger: &mut GasCharger) {
835        for object_id in &self.execution_results.modified_objects {
836            if self
837                .execution_results
838                .written_objects
839                .contains_key(object_id)
840            {
841                continue;
842            }
843            // get and track the deleted object `storage_rebate`
844            let storage_rebate = self
845                .get_object_modified_at(object_id)
846                // Unwrap is safe because this loop iterates through all modified objects.
847                .unwrap()
848                .storage_rebate;
849            gas_charger.track_storage_mutation(*object_id, 0, storage_rebate);
850        }
851    }
852
853    pub fn check_execution_results_consistency(&self) -> Result<(), ExecutionError> {
854        assert_invariant!(
855            self.execution_results
856                .created_object_ids
857                .iter()
858                .all(|id| !self.execution_results.deleted_object_ids.contains(id)
859                    && !self.execution_results.modified_objects.contains(id)),
860            "Created object IDs cannot also be deleted or modified"
861        );
862        assert_invariant!(
863            self.execution_results.modified_objects.iter().all(|id| {
864                self.mutable_input_refs.contains_key(id)
865                    || self.loaded_runtime_objects.contains_key(id)
866                    || is_system_package(*id)
867            }),
868            "A modified object must be either a mutable input, a loaded child object, or a system package"
869        );
870        Ok(())
871    }
872}
873//==============================================================================
874// Charge gas current - end
875//==============================================================================
876
877impl TemporaryStore<'_> {
878    pub fn advance_epoch_safe_mode(
879        &mut self,
880        params: &AdvanceEpochParams,
881        protocol_config: &ProtocolConfig,
882    ) {
883        let wrapper = get_sui_system_state_wrapper(self.store.as_object_store())
884            .expect("System state wrapper object must exist");
885        let (old_object, new_object) =
886            wrapper.advance_epoch_safe_mode(params, self.store.as_object_store(), protocol_config);
887        self.mutate_child_object(old_object, new_object);
888    }
889}
890
891type ModifiedObjectInfo<'a> = (
892    ObjectID,
893    // old object metadata, including version, digest, owner, and storage rebate.
894    Option<DynamicallyLoadedObjectMetadata>,
895    Option<&'a Object>,
896);
897
898impl TemporaryStore<'_> {
899    fn get_input_sui(
900        &self,
901        id: &ObjectID,
902        expected_version: SequenceNumber,
903        layout_resolver: &mut impl LayoutResolver,
904    ) -> Result<u64, ExecutionError> {
905        if let Some(obj) = self.input_objects.get(id) {
906            // the assumption here is that if it is in the input objects must be the right one
907            if obj.version() != expected_version {
908                invariant_violation!(
909                    "Version mismatching when resolving input object to check conservation--\
910                     expected {}, got {}",
911                    expected_version,
912                    obj.version(),
913                );
914            }
915            obj.get_total_sui(layout_resolver).map_err(|e| {
916                make_invariant_violation!(
917                    "Failed looking up input SUI in SUI conservation checking for input with \
918                         type {:?}: {e:#?}",
919                    obj.struct_tag(),
920                )
921            })
922        } else {
923            // not in input objects, must be a dynamic field
924            let Some(obj) = self.store.get_object_by_key(id, expected_version) else {
925                invariant_violation!(
926                    "Failed looking up dynamic field {id} in SUI conservation checking"
927                );
928            };
929            obj.get_total_sui(layout_resolver).map_err(|e| {
930                make_invariant_violation!(
931                    "Failed looking up input SUI in SUI conservation checking for type \
932                         {:?}: {e:#?}",
933                    obj.struct_tag(),
934                )
935            })
936        }
937    }
938
939    /// Return the list of all modified objects, for each object, returns
940    /// - Object ID,
941    /// - Input: If the object existed prior to this transaction, include their version and storage_rebate,
942    /// - Output: If a new version of the object is written, include the new object.
943    fn get_modified_objects(&self) -> Vec<ModifiedObjectInfo<'_>> {
944        self.execution_results
945            .modified_objects
946            .iter()
947            .map(|id| {
948                let metadata = self.get_object_modified_at(id);
949                let output = self.execution_results.written_objects.get(id);
950                (*id, metadata, output)
951            })
952            .chain(
953                self.execution_results
954                    .written_objects
955                    .iter()
956                    .filter_map(|(id, object)| {
957                        if self.execution_results.modified_objects.contains(id) {
958                            None
959                        } else {
960                            Some((*id, None, Some(object)))
961                        }
962                    }),
963            )
964            .collect()
965    }
966
967    /// Check that this transaction neither creates nor destroys SUI. This should hold for all txes
968    /// except the epoch change tx, which mints staking rewards equal to the gas fees burned in the
969    /// previous epoch.  Specifically, this checks two key invariants about storage
970    /// fees and storage rebate:
971    ///
972    /// 1. all SUI in storage rebate fields of input objects should flow either to the transaction
973    ///    storage rebate, or the transaction non-refundable storage rebate
974    /// 2. all SUI charged for storage should flow into the storage rebate field of some output
975    ///    object
976    ///
977    /// This function is intended to be called *after* we have charged for
978    /// gas + applied the storage rebate to the gas object, but *before* we
979    /// have updated object versions.
980    pub fn check_sui_conserved(
981        &self,
982        simple_conservation_checks: bool,
983        gas_summary: &GasCostSummary,
984    ) -> Result<(), ExecutionError> {
985        if !simple_conservation_checks {
986            return Ok(());
987        }
988        // total amount of SUI in storage rebate of input objects
989        let mut total_input_rebate = 0;
990        // total amount of SUI in storage rebate of output objects
991        let mut total_output_rebate = 0;
992        for (_, input, output) in self.get_modified_objects() {
993            if let Some(input) = input {
994                total_input_rebate += input.storage_rebate;
995            }
996            if let Some(object) = output {
997                total_output_rebate += object.storage_rebate;
998            }
999        }
1000
1001        if gas_summary.storage_cost == 0 {
1002            // this condition is usually true when the transaction went OOG and no
1003            // gas is left for storage charges.
1004            // The storage cost has to be there at least for the gas coin which
1005            // will not be deleted even when going to 0.
1006            // However if the storage cost is 0 and if there is any object touched
1007            // or deleted the value in input must be equal to the output plus rebate and
1008            // non refundable.
1009            // Rebate and non refundable will be positive when there are object deleted
1010            // (gas smashing being the primary and possibly only example).
1011            // A more typical condition is for all storage charges in summary to be 0 and
1012            // then input and output must be the same value
1013            if total_input_rebate
1014                != total_output_rebate
1015                    + gas_summary.storage_rebate
1016                    + gas_summary.non_refundable_storage_fee
1017            {
1018                return Err(ExecutionError::invariant_violation(format!(
1019                    "SUI conservation failed -- no storage charges in gas summary \
1020                        and total storage input rebate {} not equal  \
1021                        to total storage output rebate {}",
1022                    total_input_rebate, total_output_rebate,
1023                )));
1024            }
1025        } else {
1026            // all SUI in storage rebate fields of input objects should flow either to
1027            // the transaction storage rebate, or the non-refundable storage rebate pool
1028            if total_input_rebate
1029                != gas_summary.storage_rebate + gas_summary.non_refundable_storage_fee
1030            {
1031                return Err(ExecutionError::invariant_violation(format!(
1032                    "SUI conservation failed -- {} SUI in storage rebate field of input objects, \
1033                        {} SUI in tx storage rebate or tx non-refundable storage rebate",
1034                    total_input_rebate, gas_summary.non_refundable_storage_fee,
1035                )));
1036            }
1037
1038            // all SUI charged for storage should flow into the storage rebate field
1039            // of some output object
1040            if gas_summary.storage_cost != total_output_rebate {
1041                return Err(ExecutionError::invariant_violation(format!(
1042                    "SUI conservation failed -- {} SUI charged for storage, \
1043                        {} SUI in storage rebate field of output objects",
1044                    gas_summary.storage_cost, total_output_rebate
1045                )));
1046            }
1047        }
1048        Ok(())
1049    }
1050
1051    /// Check that this transaction neither creates nor destroys SUI.
1052    /// This more expensive check will check a third invariant on top of the 2 performed
1053    /// by `check_sui_conserved` above:
1054    ///
1055    /// * all SUI in input objects (including coins etc in the Move part of an object) should flow
1056    ///   either to an output object, or be burned as part of computation fees or non-refundable
1057    ///   storage rebate
1058    ///
1059    /// This function is intended to be called *after* we have charged for gas + applied the
1060    /// storage rebate to the gas object, but *before* we have updated object versions. The
1061    /// advance epoch transaction would mint `epoch_fees` amount of SUI, and burn `epoch_rebates`
1062    /// amount of SUI. We need these information for this check.
1063    pub fn check_sui_conserved_expensive(
1064        &self,
1065        gas_summary: &GasCostSummary,
1066        advance_epoch_gas_summary: Option<(u64, u64)>,
1067        layout_resolver: &mut impl LayoutResolver,
1068    ) -> Result<(), ExecutionError> {
1069        // total amount of SUI in input objects, including both coins and storage rebates
1070        let mut total_input_sui = 0;
1071        // total amount of SUI in output objects, including both coins and storage rebates
1072        let mut total_output_sui = 0;
1073        for (id, input, output) in self.get_modified_objects() {
1074            if let Some(input) = input {
1075                total_input_sui += self.get_input_sui(&id, input.version, layout_resolver)?;
1076            }
1077            if let Some(object) = output {
1078                total_output_sui += object.get_total_sui(layout_resolver).map_err(|e| {
1079                    make_invariant_violation!(
1080                        "Failed looking up output SUI in SUI conservation checking for \
1081                         mutated type {:?}: {e:#?}",
1082                        object.struct_tag(),
1083                    )
1084                })?;
1085            }
1086        }
1087        // note: storage_cost flows into the storage_rebate field of the output objects, which is
1088        // why it is not accounted for here.
1089        // similarly, all of the storage_rebate *except* the storage_fund_rebate_inflow
1090        // gets credited to the gas coin both computation costs and storage rebate inflow are
1091        total_output_sui += gas_summary.computation_cost + gas_summary.non_refundable_storage_fee;
1092        if let Some((epoch_fees, epoch_rebates)) = advance_epoch_gas_summary {
1093            total_input_sui += epoch_fees;
1094            total_output_sui += epoch_rebates;
1095        }
1096        if total_input_sui != total_output_sui {
1097            return Err(ExecutionError::invariant_violation(format!(
1098                "SUI conservation failed: input={}, output={}, \
1099                    this transaction either mints or burns SUI",
1100                total_input_sui, total_output_sui,
1101            )));
1102        }
1103        Ok(())
1104    }
1105}
1106
1107impl ChildObjectResolver for TemporaryStore<'_> {
1108    fn read_child_object(
1109        &self,
1110        parent: &ObjectID,
1111        child: &ObjectID,
1112        child_version_upper_bound: SequenceNumber,
1113    ) -> SuiResult<Option<Object>> {
1114        let obj_opt = self.execution_results.written_objects.get(child);
1115        if obj_opt.is_some() {
1116            Ok(obj_opt.cloned())
1117        } else {
1118            self.store
1119                .read_child_object(parent, child, child_version_upper_bound)
1120        }
1121    }
1122
1123    fn get_object_received_at_version(
1124        &self,
1125        owner: &ObjectID,
1126        receiving_object_id: &ObjectID,
1127        receive_object_at_version: SequenceNumber,
1128        epoch_id: EpochId,
1129    ) -> SuiResult<Option<Object>> {
1130        // You should never be able to try and receive an object after deleting it or writing it in the same
1131        // transaction since `Receiving` doesn't have copy.
1132        debug_assert!(!self
1133            .execution_results
1134            .written_objects
1135            .contains_key(receiving_object_id));
1136        debug_assert!(!self
1137            .execution_results
1138            .deleted_object_ids
1139            .contains(receiving_object_id));
1140        self.store.get_object_received_at_version(
1141            owner,
1142            receiving_object_id,
1143            receive_object_at_version,
1144            epoch_id,
1145        )
1146    }
1147}
1148
1149impl Storage for TemporaryStore<'_> {
1150    fn reset(&mut self) {
1151        self.drop_writes();
1152    }
1153
1154    fn read_object(&self, id: &ObjectID) -> Option<&Object> {
1155        TemporaryStore::read_object(self, id)
1156    }
1157
1158    /// Take execution results v2, and translate it back to be compatible with effects v1.
1159    fn record_execution_results(
1160        &mut self,
1161        results: ExecutionResults,
1162    ) -> Result<(), ExecutionError> {
1163        let ExecutionResults::V2(results) = results else {
1164            panic!("ExecutionResults::V2 expected in sui-execution v1 and above");
1165        };
1166        // It's important to merge instead of override results because it's
1167        // possible to execute PT more than once during tx execution.
1168        self.execution_results.merge_results(results);
1169        Ok(())
1170    }
1171
1172    fn save_loaded_runtime_objects(
1173        &mut self,
1174        loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
1175    ) {
1176        TemporaryStore::save_loaded_runtime_objects(self, loaded_runtime_objects)
1177    }
1178
1179    fn save_wrapped_object_containers(
1180        &mut self,
1181        wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
1182    ) {
1183        TemporaryStore::save_wrapped_object_containers(self, wrapped_object_containers)
1184    }
1185
1186    fn check_coin_deny_list(
1187        &self,
1188        _receiving_funds_type_and_owners: BTreeMap<TypeTag, BTreeSet<SuiAddress>>,
1189    ) -> DenyListResult {
1190        unreachable!("Coin denylist v2 is not supported in sui-execution v2");
1191    }
1192
1193    fn record_generated_object_ids(&mut self, _generated_ids: BTreeSet<ObjectID>) {
1194        unreachable!(
1195            "Generated object IDs are not recorded in ExecutionResults in sui-execution v2"
1196        );
1197    }
1198}
1199
1200impl BackingPackageStore for TemporaryStore<'_> {
1201    fn get_package_object(&self, package_id: &ObjectID) -> SuiResult<Option<PackageObject>> {
1202        // We first check the objects in the temporary store because in non-production code path,
1203        // it is possible to read packages that are just written in the same transaction.
1204        // This can happen for example when we run the expensive conservation checks, where we may
1205        // look into the types of each written object in the output, and some of them need the
1206        // newly written packages for type checking.
1207        // In production path though, this should never happen.
1208        if let Some(obj) = self.execution_results.written_objects.get(package_id) {
1209            Ok(Some(PackageObject::new(obj.clone())))
1210        } else {
1211            self.store.get_package_object(package_id).inspect(|obj| {
1212                // Track object but leave unchanged
1213                if let Some(v) = obj {
1214                    if !self
1215                        .runtime_packages_loaded_from_db
1216                        .read()
1217                        .contains_key(package_id)
1218                    {
1219                        // TODO: Can this lock ever block execution?
1220                        // TODO: Another way to avoid the cost of maintaining this map is to not
1221                        // enable it in normal runs, and if a fork is detected, rerun it with a flag
1222                        // turned on and start populating this field.
1223                        self.runtime_packages_loaded_from_db
1224                            .write()
1225                            .insert(*package_id, v.clone());
1226                    }
1227                }
1228            })
1229        }
1230    }
1231}
1232
1233impl ParentSync for TemporaryStore<'_> {
1234    fn get_latest_parent_entry_ref_deprecated(&self, _object_id: ObjectID) -> Option<ObjectRef> {
1235        unreachable!("Never called in newer protocol versions")
1236    }
1237}