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