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                Owner::Party { .. } => {
547                    unimplemented!("Party does not exist for this execution version")
548                }
549            }
550        }
551
552        for (id, (_new_obj, kind)) in &self.written {
553            if authenticated_objs.contains(id) || gas_objs.contains(id) {
554                continue;
555            }
556            match kind {
557                WriteKind::Mutate => {
558                    // get owner at beginning of tx, since that's what we have to authenticate against
559                    // _new_obj.owner is not relevant here
560                    let old_obj = self.store.get_object(id).unwrap_or_else(|| {
561                        panic!("Mutated object must exist in the store: ID = {:?}", id)
562                    });
563                    match &old_obj.owner {
564                        Owner::ObjectOwner(_parent) => {
565                            objs_to_authenticate.push(*id);
566                        }
567                        Owner::AddressOwner(_) | Owner::Shared { .. } => {
568                            unreachable!("Should already be in authenticated_objs")
569                        }
570                        Owner::Immutable => {
571                            assert!(is_epoch_change, "Immutable objects cannot be written, except for Sui Framework/Move stdlib upgrades at epoch change boundaries");
572                            // Note: this assumes that the only immutable objects an epoch change tx can update are system packages,
573                            // but in principle we could allow others.
574                            assert!(
575                                is_system_package(*id),
576                                "Only system packages can be upgraded"
577                            );
578                        }
579                        Owner::ConsensusAddressOwner { .. } => {
580                            unimplemented!(
581                                "ConsensusAddressOwner does not exist for this execution version"
582                            )
583                        }
584                        Owner::Party { .. } => {
585                            unimplemented!("Party does not exist for this execution version")
586                        }
587                    }
588                }
589                WriteKind::Create | WriteKind::Unwrap => {
590                    // created and unwrapped objects were not inputs to the tx
591                    // or dynamic fields accessed at runtime, no ownership checks needed
592                }
593            }
594        }
595
596        for (id, kind) in &self.deleted {
597            if authenticated_objs.contains(id) || gas_objs.contains(id) {
598                continue;
599            }
600            match kind {
601                DeleteKindWithOldVersion::Normal(_) | DeleteKindWithOldVersion::Wrap(_) => {
602                    // get owner at beginning of tx
603                    let old_obj = self.store.get_object(id).unwrap();
604                    match &old_obj.owner {
605                        Owner::ObjectOwner(_) => {
606                            objs_to_authenticate.push(*id);
607                        }
608                        Owner::AddressOwner(_) | Owner::Shared { .. } => {
609                            unreachable!("Should already be in authenticated_objs")
610                        }
611                        Owner::Immutable => unreachable!("Immutable objects cannot be deleted"),
612                        Owner::ConsensusAddressOwner { .. } => {
613                            unimplemented!(
614                                "ConsensusAddressOwner does not exist for this execution version"
615                            )
616                        }
617                        Owner::Party { .. } => {
618                            unimplemented!("Party does not exist for this execution version")
619                        }
620                    }
621                }
622                DeleteKindWithOldVersion::UnwrapThenDelete
623                | DeleteKindWithOldVersion::UnwrapThenDeleteDEPRECATED(_) => {
624                    // unwrapped-then-deleted object was not an input to the tx,
625                    // no ownership checks needed
626                }
627            }
628        }
629        Ok((objs_to_authenticate, authenticated_objs))
630    }
631
632    // check that every object read is owned directly or indirectly by sender, sponsor, or a shared object input
633    pub fn check_ownership_invariants(
634        &self,
635        sender: &SuiAddress,
636        gas_charger: &mut GasCharger,
637        is_epoch_change: bool,
638    ) -> SuiResult<()> {
639        let (mut objects_to_authenticate, mut authenticated_objects) =
640            self.get_objects_to_authenticate(sender, gas_charger, is_epoch_change)?;
641
642        // Map from an ObjectID to the ObjectID that covers it.
643        let mut covered = BTreeMap::new();
644        while let Some(to_authenticate) = objects_to_authenticate.pop() {
645            let Some(old_obj) = self.store.get_object(&to_authenticate) else {
646                // lookup failure is expected when the parent is an "object-less" UID (e.g., the ID of a table or bag)
647                // we cannot distinguish this case from an actual authentication failure, so continue
648                continue;
649            };
650            let parent = match &old_obj.owner {
651                Owner::ObjectOwner(parent) => ObjectID::from(*parent),
652                owner => panic!(
653                    "Unauthenticated root at {to_authenticate:?} with owner {owner:?}\n\
654             Potentially covering objects in: {covered:#?}",
655                ),
656            };
657
658            if authenticated_objects.contains(&parent) {
659                authenticated_objects.insert(to_authenticate);
660            } else if !covered.contains_key(&parent) {
661                objects_to_authenticate.push(parent);
662            }
663
664            covered.insert(to_authenticate, parent);
665        }
666        Ok(())
667    }
668}
669
670impl TemporaryStore<'_> {
671    /// Return the storage rebate of object `id`
672    fn get_input_storage_rebate(&self, id: &ObjectID, expected_version: SequenceNumber) -> u64 {
673        // A mutated object must either be from input object or child object.
674        if let Some(old_obj) = self.input_objects.get(id) {
675            old_obj.storage_rebate
676        } else if let Some(metadata) = self.loaded_child_objects.get(id) {
677            debug_assert_eq!(metadata.version, expected_version);
678            metadata.storage_rebate
679        } else if let Some(obj) = self.store.get_object_by_key(id, expected_version) {
680            // The only case where an modified input object is not in the input list nor child object,
681            // is when we upgrade a system package during epoch change.
682            debug_assert!(obj.is_package());
683            obj.storage_rebate
684        } else {
685            // not a lot we can do safely and under this condition everything is broken
686            panic!(
687                "Looking up storage rebate of mutated object {:?} should not fail",
688                id
689            )
690        }
691    }
692
693    pub(crate) fn ensure_gas_and_input_mutated(&mut self, gas_charger: &mut GasCharger) {
694        if let Some(gas_object_id) = gas_charger.gas_coin() {
695            let gas_object = self
696                .read_object(&gas_object_id)
697                .expect("We constructed the object map so it should always have the gas object id")
698                .clone();
699            self.written
700                .entry(gas_object_id)
701                .or_insert_with(|| (gas_object, WriteKind::Mutate));
702        }
703        self.ensure_active_inputs_mutated();
704    }
705
706    /// Track storage gas for each mutable input object (including the gas coin)
707    /// and each created object. Compute storage refunds for each deleted object.
708    /// Will *not* charge anything, gas status keeps track of storage cost and rebate.
709    /// All objects will be updated with their new (current) storage rebate/cost.
710    /// `SuiGasStatus` `storage_rebate` and `storage_gas_units` track the transaction
711    /// overall storage rebate and cost.
712    pub(crate) fn collect_storage_and_rebate(&mut self, gas_charger: &mut GasCharger) {
713        let mut objects_to_update = vec![];
714        for (object_id, (object, write_kind)) in &mut self.written {
715            // get the object storage_rebate in input for mutated objects
716            let old_storage_rebate = match write_kind {
717                WriteKind::Create | WriteKind::Unwrap => 0,
718                WriteKind::Mutate => {
719                    if let Some(old_obj) = self.input_objects.get(object_id) {
720                        old_obj.storage_rebate
721                    } else {
722                        // else, this is a dynamic field, not an input object
723                        let expected_version = object.version();
724                        if let Some(old_obj) =
725                            self.store.get_object_by_key(object_id, expected_version)
726                        {
727                            old_obj.storage_rebate
728                        } else {
729                            // not a lot we can do safely and under this condition everything is broken
730                            panic!("Looking up storage rebate of mutated object should not fail");
731                        }
732                    }
733                }
734            };
735            // new object size
736            let new_object_size = object.object_size_for_gas_metering();
737            // track changes and compute the new object `storage_rebate`
738            let new_storage_rebate =
739                gas_charger.track_storage_mutation(*object_id, new_object_size, old_storage_rebate);
740            object.storage_rebate = new_storage_rebate;
741            if !object.is_immutable() {
742                objects_to_update.push((object.clone(), *write_kind));
743            }
744        }
745
746        self.collect_rebate(gas_charger);
747
748        // Write all objects at the end only if all previous gas charges succeeded.
749        // This avoids polluting the temporary store state if this function failed.
750        for (object, write_kind) in objects_to_update {
751            self.write_object(object, write_kind);
752        }
753    }
754
755    pub(crate) fn collect_rebate(&self, gas_charger: &mut GasCharger) {
756        for (object_id, kind) in &self.deleted {
757            match kind {
758                DeleteKindWithOldVersion::Wrap(version)
759                | DeleteKindWithOldVersion::Normal(version) => {
760                    // get and track the deleted object `storage_rebate`
761                    let storage_rebate = self.get_input_storage_rebate(object_id, *version);
762                    gas_charger.track_storage_mutation(*object_id, 0, storage_rebate);
763                }
764                DeleteKindWithOldVersion::UnwrapThenDelete
765                | DeleteKindWithOldVersion::UnwrapThenDeleteDEPRECATED(_) => {
766                    // an unwrapped object does not have a storage rebate, we will charge for storage changes via its wrapper object
767                }
768            }
769        }
770    }
771}
772//==============================================================================
773// Charge gas current - end
774//==============================================================================
775
776impl TemporaryStore<'_> {
777    pub fn advance_epoch_safe_mode(
778        &mut self,
779        params: &AdvanceEpochParams,
780        protocol_config: &ProtocolConfig,
781    ) {
782        let wrapper = get_sui_system_state_wrapper(self.store.as_object_store())
783            .expect("System state wrapper object must exist");
784        let (new_object, _) =
785            wrapper.advance_epoch_safe_mode(params, self.store.as_object_store(), protocol_config);
786        self.write_object(new_object, WriteKind::Mutate);
787    }
788}
789
790type ModifiedObjectInfo<'a> = (ObjectID, Option<(SequenceNumber, u64)>, Option<&'a Object>);
791
792impl TemporaryStore<'_> {
793    fn get_input_sui(
794        &self,
795        id: &ObjectID,
796        expected_version: SequenceNumber,
797        layout_resolver: &mut impl LayoutResolver,
798    ) -> Result<u64, ExecutionError> {
799        if let Some(obj) = self.input_objects.get(id) {
800            // the assumption here is that if it is in the input objects must be the right one
801            if obj.version() != expected_version {
802                invariant_violation!(
803                    "Version mismatching when resolving input object to check conservation--\
804                     expected {}, got {}",
805                    expected_version,
806                    obj.version(),
807                );
808            }
809            obj.get_total_sui(layout_resolver).map_err(|e| {
810                make_invariant_violation!(
811                    "Failed looking up input SUI in SUI conservation checking for input with \
812                         type {:?}: {e:#?}",
813                    obj.struct_tag(),
814                )
815            })
816        } else {
817            // not in input objects, must be a dynamic field
818            let Some(obj) = self.store.get_object_by_key(id, expected_version) else {
819                invariant_violation!(
820                    "Failed looking up dynamic field {id} in SUI conservation checking"
821                );
822            };
823            obj.get_total_sui(layout_resolver).map_err(|e| {
824                make_invariant_violation!(
825                    "Failed looking up input SUI in SUI conservation checking for type \
826                         {:?}: {e:#?}",
827                    obj.struct_tag(),
828                )
829            })
830        }
831    }
832
833    /// Return the list of all modified objects, for each object, returns
834    /// - Object ID,
835    /// - Input: If the object existed prior to this transaction, include their version and storage_rebate,
836    /// - Output: If a new version of the object is written, include the new object.
837    fn get_modified_objects(&self) -> Vec<ModifiedObjectInfo<'_>> {
838        self.written
839            .iter()
840            .map(|(id, (object, kind))| match kind {
841                WriteKind::Mutate => {
842                    // When an object is mutated, its version remains the old version until the end.
843                    let version = object.version();
844                    let storage_rebate = self.get_input_storage_rebate(id, version);
845                    (*id, Some((object.version(), storage_rebate)), Some(object))
846                }
847                WriteKind::Create | WriteKind::Unwrap => (*id, None, Some(object)),
848            })
849            .chain(self.deleted.iter().filter_map(|(id, kind)| match kind {
850                DeleteKindWithOldVersion::Normal(version)
851                | DeleteKindWithOldVersion::Wrap(version) => {
852                    let storage_rebate = self.get_input_storage_rebate(id, *version);
853                    Some((*id, Some((*version, storage_rebate)), None))
854                }
855                DeleteKindWithOldVersion::UnwrapThenDelete
856                | DeleteKindWithOldVersion::UnwrapThenDeleteDEPRECATED(_) => None,
857            }))
858            .collect()
859    }
860
861    /// Check that this transaction neither creates nor destroys SUI. This should hold for all txes
862    /// except the epoch change tx, which mints staking rewards equal to the gas fees burned in the
863    /// previous epoch.  Specifically, this checks two key invariants about storage fees and storage
864    /// rebate:
865    ///
866    /// 1. all SUI in storage rebate fields of input objects should flow either to the transaction
867    ///    storage rebate, or the transaction non-refundable storage rebate
868    /// 2. all SUI charged for storage should flow into the storage rebate field of some output
869    ///    object
870    ///
871    /// If `do_expensive_checks` is true, this will also check a third invariant:
872    ///
873    /// 3. all SUI in input objects (including coins etc in the Move part of an object) should flow
874    ///    either to an output object, or be burned as part of computation fees or non-refundable
875    ///    storage rebate
876    ///
877    /// This function is intended to be called *after* we have charged for gas + applied the storage
878    /// rebate to the gas object, but *before* we have updated object versions.  If
879    /// `do_expensive_checks` is false, this function will only check conservation of object storage
880    /// rea `epoch_fees` and `epoch_rebates` are only set for advance epoch transactions.  The
881    /// advance epoch transaction would mint `epoch_fees` amount of SUI, and burn `epoch_rebates`
882    /// amount of SUI. We need these information for conservation check.
883    pub fn check_sui_conserved(
884        &self,
885        gas_summary: &GasCostSummary,
886        advance_epoch_gas_summary: Option<(u64, u64)>,
887        layout_resolver: &mut impl LayoutResolver,
888        do_expensive_checks: bool,
889    ) -> Result<(), ExecutionError> {
890        // total amount of SUI in input objects, including both coins and storage rebates
891        let mut total_input_sui = 0;
892        // total amount of SUI in output objects, including both coins and storage rebates
893        let mut total_output_sui = 0;
894        // total amount of SUI in storage rebate of input objects
895        let mut total_input_rebate = 0;
896        // total amount of SUI in storage rebate of output objects
897        let mut total_output_rebate = 0;
898        for (id, input, output) in self.get_modified_objects() {
899            if let Some((version, storage_rebate)) = input {
900                total_input_rebate += storage_rebate;
901                if do_expensive_checks {
902                    total_input_sui += self.get_input_sui(&id, version, layout_resolver)?;
903                }
904            }
905            if let Some(object) = output {
906                total_output_rebate += object.storage_rebate;
907                if do_expensive_checks {
908                    total_output_sui += object.get_total_sui(layout_resolver).map_err(|e| {
909                        make_invariant_violation!(
910                            "Failed looking up output SUI in SUI conservation checking for \
911                             mutated type {:?}: {e:#?}",
912                            object.struct_tag(),
913                        )
914                    })?;
915                }
916            }
917        }
918        if do_expensive_checks {
919            // note: storage_cost flows into the storage_rebate field of the output objects, which is why it is not accounted for here.
920            // similarly, all of the storage_rebate *except* the storage_fund_rebate_inflow gets credited to the gas coin
921            // both computation costs and storage rebate inflow are
922            total_output_sui +=
923                gas_summary.computation_cost + gas_summary.non_refundable_storage_fee;
924            if let Some((epoch_fees, epoch_rebates)) = advance_epoch_gas_summary {
925                total_input_sui += epoch_fees;
926                total_output_sui += epoch_rebates;
927            }
928            if total_input_sui != total_output_sui {
929                return Err(ExecutionError::invariant_violation(
930                format!("SUI conservation failed: input={}, output={}, this transaction either mints or burns SUI",
931                total_input_sui,
932                total_output_sui))
933            );
934            }
935        }
936
937        // all SUI in storage rebate fields of input objects should flow either to the transaction storage rebate, or the non-refundable
938        // storage rebate pool
939        if total_input_rebate != gas_summary.storage_rebate + gas_summary.non_refundable_storage_fee
940        {
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 in storage rebate field of input objects, {} SUI in tx storage rebate or tx non-refundable storage rebate",
944                total_input_rebate,
945                gas_summary.non_refundable_storage_fee))
946            );*/
947        }
948
949        // all SUI charged for storage should flow into the storage rebate field of some output object
950        if gas_summary.storage_cost != total_output_rebate {
951            // TODO: re-enable once we fix the edge case with OOG, gas smashing, and storage rebate
952            /*return Err(ExecutionError::invariant_violation(
953                format!("SUI conservation failed--{} SUI charged for storage, {} SUI in storage rebate field of output objects",
954                gas_summary.storage_cost,
955                total_output_rebate))
956            );*/
957        }
958        Ok(())
959    }
960}
961
962impl ChildObjectResolver for TemporaryStore<'_> {
963    fn read_child_object(
964        &self,
965        parent: &ObjectID,
966        child: &ObjectID,
967        child_version_upper_bound: SequenceNumber,
968    ) -> SuiResult<Option<Object>> {
969        // there should be no read after delete
970        debug_assert!(!self.deleted.contains_key(child));
971        let obj_opt = self.written.get(child).map(|(obj, _kind)| obj);
972        if obj_opt.is_some() {
973            Ok(obj_opt.cloned())
974        } else {
975            self.store
976                .read_child_object(parent, child, child_version_upper_bound)
977        }
978    }
979
980    fn get_object_received_at_version(
981        &self,
982        owner: &ObjectID,
983        receiving_object_id: &ObjectID,
984        receive_object_at_version: SequenceNumber,
985        epoch_id: EpochId,
986    ) -> SuiResult<Option<Object>> {
987        // You should never be able to try and receive an object after deleting it or writing it in the same
988        // transaction since `Receiving` doesn't have copy.
989        debug_assert!(!self.deleted.contains_key(receiving_object_id));
990        debug_assert!(!self.written.contains_key(receiving_object_id));
991        self.store.get_object_received_at_version(
992            owner,
993            receiving_object_id,
994            receive_object_at_version,
995            epoch_id,
996        )
997    }
998}
999
1000impl Storage for TemporaryStore<'_> {
1001    fn reset(&mut self) {
1002        TemporaryStore::drop_writes(self);
1003    }
1004
1005    fn read_object(&self, id: &ObjectID) -> Option<&Object> {
1006        TemporaryStore::read_object(self, id)
1007    }
1008
1009    fn record_execution_results(
1010        &mut self,
1011        results: ExecutionResults,
1012    ) -> Result<(), ExecutionError> {
1013        let ExecutionResults::V1(results) = results else {
1014            panic!("ExecutionResults::V1 expected in sui-execution v0");
1015        };
1016        TemporaryStore::apply_object_changes(self, results.object_changes);
1017        for event in results.user_events {
1018            TemporaryStore::log_event(self, event);
1019        }
1020        Ok(())
1021    }
1022
1023    fn save_loaded_runtime_objects(
1024        &mut self,
1025        loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
1026    ) {
1027        TemporaryStore::save_loaded_runtime_objects(self, loaded_runtime_objects)
1028    }
1029
1030    fn save_wrapped_object_containers(
1031        &mut self,
1032        _wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
1033    ) {
1034        unreachable!("Unused in v0")
1035    }
1036
1037    fn check_coin_deny_list(
1038        &self,
1039        _receiving_funds_type_and_owners: BTreeMap<TypeTag, BTreeSet<SuiAddress>>,
1040    ) -> DenyListResult {
1041        unreachable!("Coin denylist v2 is not supported in sui-execution v0");
1042    }
1043
1044    fn record_generated_object_ids(&mut self, _generated_ids: BTreeSet<ObjectID>) {
1045        unreachable!(
1046            "Generated object IDs are not recorded in ExecutionResults in sui-execution v0"
1047        );
1048    }
1049}
1050
1051impl BackingPackageStore for TemporaryStore<'_> {
1052    fn get_package_object(&self, package_id: &ObjectID) -> SuiResult<Option<PackageObject>> {
1053        if let Some((obj, _)) = self.written.get(package_id) {
1054            Ok(Some(PackageObject::new(obj.clone())))
1055        } else {
1056            self.store.get_package_object(package_id).inspect(|obj| {
1057                // Track object but leave unchanged
1058                if let Some(v) = obj {
1059                    if !self
1060                        .runtime_packages_loaded_from_db
1061                        .read()
1062                        .contains_key(package_id)
1063                    {
1064                        // TODO: Can this lock ever block execution?
1065                        // TODO: Why do we need a RwLock anyway???
1066                        self.runtime_packages_loaded_from_db
1067                            .write()
1068                            .insert(*package_id, v.clone());
1069                    }
1070                }
1071            })
1072        }
1073    }
1074}
1075
1076impl ParentSync for TemporaryStore<'_> {
1077    fn get_latest_parent_entry_ref_deprecated(&self, object_id: ObjectID) -> Option<ObjectRef> {
1078        self.store.get_latest_parent_entry_ref_deprecated(object_id)
1079    }
1080}