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