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