iota_adapter_latest/
temporary_store.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::collections::{BTreeMap, BTreeSet, HashSet};
6
7use iota_metrics::monitored_scope;
8use iota_protocol_config::ProtocolConfig;
9use iota_types::{
10    IOTA_DENY_LIST_OBJECT_ID, IOTA_SYSTEM_STATE_OBJECT_ID,
11    base_types::{
12        IotaAddress, ObjectID, ObjectRef, SequenceNumber, TransactionDigest, VersionDigest,
13    },
14    committee::EpochId,
15    deny_list_v1::check_coin_deny_list_v1_during_execution,
16    effects::{EffectsObjectChange, TransactionEffects, TransactionEvents},
17    error::{ExecutionError, IotaError, IotaResult},
18    execution::{
19        DynamicallyLoadedObjectMetadata, ExecutionResults, ExecutionResultsV1, SharedInput,
20    },
21    execution_config_utils::to_binary_config,
22    execution_status::ExecutionStatus,
23    fp_bail,
24    gas::GasCostSummary,
25    inner_temporary_store::InnerTemporaryStore,
26    iota_system_state::{AdvanceEpochParams, get_iota_system_state_wrapper},
27    is_system_package,
28    layout_resolver::LayoutResolver,
29    object::{Data, Object, Owner},
30    storage::{
31        BackingPackageStore, BackingStore, ChildObjectResolver, DenyListResult, PackageObject,
32        Storage,
33    },
34    transaction::InputObjects,
35};
36use move_core_types::{
37    account_address::AccountAddress, language_storage::StructTag, resolver::ResourceResolver,
38};
39use parking_lot::RwLock;
40
41use crate::gas_charger::GasCharger;
42
43pub struct TemporaryStore<'backing> {
44    // The backing store for retrieving Move packages onchain.
45    // When executing a Move call, the dependent packages are not going to be
46    // in the input objects. They will be fetched from the backing store.
47    // Also used for fetching the backing parent_sync to get the last known version for wrapped
48    // objects
49    store: &'backing dyn BackingStore,
50    tx_digest: TransactionDigest,
51    input_objects: BTreeMap<ObjectID, Object>,
52    /// The version to assign to all objects written by the transaction using
53    /// this store.
54    lamport_timestamp: SequenceNumber,
55    mutable_input_refs: BTreeMap<ObjectID, (VersionDigest, Owner)>, // Inputs that are mutable
56    execution_results: ExecutionResultsV1,
57    /// Objects that were loaded during execution (dynamic fields + received
58    /// objects).
59    loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
60    /// A map from wrapped object to its container. Used during expensive
61    /// invariant checks.
62    wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
63    protocol_config: &'backing ProtocolConfig,
64
65    /// Every package that was loaded from DB store during execution.
66    /// These packages were not previously loaded into the temporary store.
67    runtime_packages_loaded_from_db: RwLock<BTreeMap<ObjectID, PackageObject>>,
68
69    /// The set of objects that we may receive during execution. Not guaranteed
70    /// to receive all, or any of the objects referenced in this set.
71    receiving_objects: Vec<ObjectRef>,
72
73    // TODO: Now that we track epoch here, there are a few places we don't need to pass it around.
74    /// The current epoch.
75    cur_epoch: EpochId,
76
77    /// The set of per-epoch config objects that were loaded during execution,
78    /// and are not in the input objects. This allows us to commit them to
79    /// the effects.
80    loaded_per_epoch_config_objects: RwLock<BTreeSet<ObjectID>>,
81}
82
83impl<'backing> TemporaryStore<'backing> {
84    /// Creates a new store associated with an authority store, and populates it
85    /// with initial objects.
86    pub fn new(
87        store: &'backing dyn BackingStore,
88        input_objects: InputObjects,
89        receiving_objects: Vec<ObjectRef>,
90        tx_digest: TransactionDigest,
91        protocol_config: &'backing ProtocolConfig,
92        cur_epoch: EpochId,
93    ) -> Self {
94        let mutable_input_refs = input_objects.mutable_inputs();
95        let lamport_timestamp = input_objects.lamport_timestamp(&receiving_objects);
96        let objects = input_objects.into_object_map();
97        #[cfg(debug_assertions)]
98        {
99            // Ensure that input objects and receiving objects must not overlap.
100            assert!(
101                objects
102                    .keys()
103                    .collect::<HashSet<_>>()
104                    .intersection(
105                        &receiving_objects
106                            .iter()
107                            .map(|oref| &oref.0)
108                            .collect::<HashSet<_>>()
109                    )
110                    .next()
111                    .is_none()
112            );
113        }
114        Self {
115            store,
116            tx_digest,
117            input_objects: objects,
118            lamport_timestamp,
119            mutable_input_refs,
120            execution_results: ExecutionResultsV1::default(),
121            protocol_config,
122            loaded_runtime_objects: BTreeMap::new(),
123            wrapped_object_containers: BTreeMap::new(),
124            runtime_packages_loaded_from_db: RwLock::new(BTreeMap::new()),
125            receiving_objects,
126            cur_epoch,
127            loaded_per_epoch_config_objects: RwLock::new(BTreeSet::new()),
128        }
129    }
130
131    // Helpers to access private fields
132    pub fn objects(&self) -> &BTreeMap<ObjectID, Object> {
133        &self.input_objects
134    }
135
136    pub fn update_object_version_and_prev_tx(&mut self) {
137        self.execution_results.update_version_and_previous_tx(
138            self.lamport_timestamp,
139            self.tx_digest,
140            &self.input_objects,
141        );
142
143        #[cfg(debug_assertions)]
144        {
145            self.check_invariants();
146        }
147    }
148
149    /// Break up the structure and return its internal stores (objects,
150    /// active_inputs, written, deleted)
151    pub fn into_inner(self) -> InnerTemporaryStore {
152        let results = self.execution_results;
153        InnerTemporaryStore {
154            input_objects: self.input_objects,
155            mutable_inputs: self.mutable_input_refs,
156            written: results.written_objects,
157            events: TransactionEvents {
158                data: results.user_events,
159            },
160            loaded_runtime_objects: self.loaded_runtime_objects,
161            runtime_packages_loaded_from_db: self.runtime_packages_loaded_from_db.into_inner(),
162            lamport_version: self.lamport_timestamp,
163            binary_config: to_binary_config(self.protocol_config),
164        }
165    }
166
167    /// For every object from active_inputs (i.e. all mutable objects), if they
168    /// are not mutated during the transaction execution, force mutating
169    /// them by incrementing the sequence number. This is required to
170    /// achieve safety.
171    pub(crate) fn ensure_active_inputs_mutated(&mut self) {
172        let mut to_be_updated = vec![];
173        for id in self.mutable_input_refs.keys() {
174            if !self.execution_results.modified_objects.contains(id) {
175                // We cannot update here but have to push to `to_be_updated` and update later
176                // because the for loop is holding a reference to `self`, and calling
177                // `self.mutate_input_object` requires a mutable reference to `self`.
178                to_be_updated.push(self.input_objects[id].clone());
179            }
180        }
181        for object in to_be_updated {
182            // The object must be mutated as it was present in the input objects
183            self.mutate_input_object(object.clone());
184        }
185    }
186
187    fn get_object_changes(&self) -> BTreeMap<ObjectID, EffectsObjectChange> {
188        let results = &self.execution_results;
189        let all_ids = results
190            .created_object_ids
191            .iter()
192            .chain(&results.deleted_object_ids)
193            .chain(&results.modified_objects)
194            .chain(results.written_objects.keys())
195            .collect::<BTreeSet<_>>();
196        all_ids
197            .into_iter()
198            .map(|id| {
199                (
200                    *id,
201                    EffectsObjectChange::new(
202                        self.get_object_modified_at(id)
203                            .map(|metadata| ((metadata.version, metadata.digest), metadata.owner)),
204                        results.written_objects.get(id),
205                        results.created_object_ids.contains(id),
206                        results.deleted_object_ids.contains(id),
207                    ),
208                )
209            })
210            .collect()
211    }
212
213    pub fn into_effects(
214        mut self,
215        shared_object_refs: Vec<SharedInput>,
216        transaction_digest: &TransactionDigest,
217        mut transaction_dependencies: BTreeSet<TransactionDigest>,
218        gas_cost_summary: GasCostSummary,
219        status: ExecutionStatus,
220        gas_charger: &mut GasCharger,
221        epoch: EpochId,
222    ) -> (InnerTemporaryStore, TransactionEffects) {
223        self.update_object_version_and_prev_tx();
224
225        // Regardless of execution status (including aborts), we insert the previous
226        // transaction for any successfully received objects during the
227        // transaction.
228        for (id, expected_version, expected_digest) in &self.receiving_objects {
229            // If the receiving object is in the loaded runtime objects, then that means
230            // that it was actually successfully loaded (so existed, and there
231            // was authenticated mutable access to it). So we insert the
232            // previous transaction as a dependency.
233            if let Some(obj_meta) = self.loaded_runtime_objects.get(id) {
234                // Check that the expected version, digest, and owner match the loaded version,
235                // digest, and owner. If they don't then don't register a dependency.
236                // This is because this could be "spoofed" by loading a dynamic object field.
237                let loaded_via_receive = obj_meta.version == *expected_version
238                    && obj_meta.digest == *expected_digest
239                    && obj_meta.owner.is_address_owned();
240                if loaded_via_receive {
241                    transaction_dependencies.insert(obj_meta.previous_transaction);
242                }
243            }
244        }
245
246        // In the case of special transactions that don't require a gas object,
247        // we don't really care about the effects to gas, just use the input for it.
248        // Gas coins are guaranteed to be at least size 1 and if more than 1
249        // the first coin is where all the others are merged.
250        let gas_coin = gas_charger.gas_coin();
251
252        let object_changes = self.get_object_changes();
253
254        let lamport_version = self.lamport_timestamp;
255        // TODO: Cleanup this clone. Potentially add unchanged_shraed_objects directly
256        // to InnerTempStore.
257        let loaded_per_epoch_config_objects = self.loaded_per_epoch_config_objects.read().clone();
258        let inner = self.into_inner();
259
260        let effects = TransactionEffects::new_from_execution_v1(
261            status,
262            epoch,
263            gas_cost_summary,
264            // TODO: Provide the list of read-only shared objects directly.
265            shared_object_refs,
266            loaded_per_epoch_config_objects,
267            *transaction_digest,
268            lamport_version,
269            object_changes,
270            gas_coin,
271            if inner.events.data.is_empty() {
272                None
273            } else {
274                Some(inner.events.digest())
275            },
276            transaction_dependencies.into_iter().collect(),
277        );
278
279        (inner, effects)
280    }
281
282    /// An internal check of the invariants (will only fire in debug)
283    #[cfg(debug_assertions)]
284    fn check_invariants(&self) {
285        // Check not both deleted and written
286        debug_assert!(
287            {
288                self.execution_results
289                    .written_objects
290                    .keys()
291                    .all(|id| !self.execution_results.deleted_object_ids.contains(id))
292            },
293            "Object both written and deleted."
294        );
295
296        // Check all mutable inputs are modified
297        debug_assert!(
298            {
299                self.mutable_input_refs
300                    .keys()
301                    .all(|id| self.execution_results.modified_objects.contains(id))
302            },
303            "Mutable input not modified."
304        );
305
306        debug_assert!(
307            {
308                self.execution_results
309                    .written_objects
310                    .values()
311                    .all(|obj| obj.previous_transaction == self.tx_digest)
312            },
313            "Object previous transaction not properly set",
314        );
315    }
316
317    /// Mutate a mutable input object. This is used to mutate input objects
318    /// outside of PT execution.
319    pub fn mutate_input_object(&mut self, object: Object) {
320        let id = object.id();
321        debug_assert!(self.input_objects.contains_key(&id));
322        debug_assert!(!object.is_immutable());
323        self.execution_results.modified_objects.insert(id);
324        self.execution_results.written_objects.insert(id, object);
325    }
326
327    /// Mutate a child object outside of PT. This should be used extremely
328    /// rarely. Currently it's only used by advance_epoch_safe_mode because
329    /// it's all native without PT. This should almost never be used
330    /// otherwise.
331    pub fn mutate_child_object(&mut self, old_object: Object, new_object: Object) {
332        let id = new_object.id();
333        let old_ref = old_object.compute_object_reference();
334        debug_assert_eq!(old_ref.0, id);
335        self.loaded_runtime_objects.insert(
336            id,
337            DynamicallyLoadedObjectMetadata {
338                version: old_ref.1,
339                digest: old_ref.2,
340                owner: old_object.owner,
341                storage_rebate: old_object.storage_rebate,
342                previous_transaction: old_object.previous_transaction,
343            },
344        );
345        self.execution_results.modified_objects.insert(id);
346        self.execution_results
347            .written_objects
348            .insert(id, new_object);
349    }
350
351    /// Upgrade system package during epoch change. This requires special
352    /// treatment since the system package to be upgraded is not in the
353    /// input objects. We could probably fix above to make it less special.
354    pub fn upgrade_system_package(&mut self, package: Object) {
355        let id = package.id();
356        assert!(package.is_package() && is_system_package(id));
357        self.execution_results.modified_objects.insert(id);
358        self.execution_results.written_objects.insert(id, package);
359    }
360
361    /// Crate a new objcet. This is used to create objects outside of PT
362    /// execution.
363    pub fn create_object(&mut self, object: Object) {
364        // Created mutable objects' versions are set to the store's lamport timestamp
365        // when it is committed to effects. Creating an object at a non-zero
366        // version risks violating the lamport timestamp invariant (that a
367        // transaction's lamport timestamp is strictly greater than all versions
368        // witnessed by the transaction).
369        debug_assert!(
370            object.is_immutable() || object.version() == SequenceNumber::MIN_VALID_INCL,
371            "Created mutable objects should not have a version set",
372        );
373        let id = object.id();
374        self.execution_results.created_object_ids.insert(id);
375        self.execution_results.written_objects.insert(id, object);
376    }
377
378    /// Delete a mutable input object. This is used to delete input objects
379    /// outside of PT execution.
380    pub fn delete_input_object(&mut self, id: &ObjectID) {
381        // there should be no deletion after write
382        debug_assert!(!self.execution_results.written_objects.contains_key(id));
383        debug_assert!(self.input_objects.contains_key(id));
384        self.execution_results.modified_objects.insert(*id);
385        self.execution_results.deleted_object_ids.insert(*id);
386    }
387
388    pub fn drop_writes(&mut self) {
389        self.execution_results.drop_writes();
390    }
391
392    pub fn read_object(&self, id: &ObjectID) -> Option<&Object> {
393        // there should be no read after delete
394        debug_assert!(!self.execution_results.deleted_object_ids.contains(id));
395        self.execution_results
396            .written_objects
397            .get(id)
398            .or_else(|| self.input_objects.get(id))
399    }
400
401    pub fn save_loaded_runtime_objects(
402        &mut self,
403        loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
404    ) {
405        #[cfg(debug_assertions)]
406        {
407            for (id, v1) in &loaded_runtime_objects {
408                if let Some(v2) = self.loaded_runtime_objects.get(id) {
409                    assert_eq!(v1, v2);
410                }
411            }
412            for (id, v1) in &self.loaded_runtime_objects {
413                if let Some(v2) = loaded_runtime_objects.get(id) {
414                    assert_eq!(v1, v2);
415                }
416            }
417        }
418        // Merge the two maps because we may be calling the execution engine more than
419        // once (e.g. in advance epoch transaction, where we may be publishing a
420        // new system package).
421        self.loaded_runtime_objects.extend(loaded_runtime_objects);
422    }
423
424    pub fn save_wrapped_object_containers(
425        &mut self,
426        wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
427    ) {
428        #[cfg(debug_assertions)]
429        {
430            for (id, container1) in &wrapped_object_containers {
431                if let Some(container2) = self.wrapped_object_containers.get(id) {
432                    assert_eq!(container1, container2);
433                }
434            }
435            for (id, container1) in &self.wrapped_object_containers {
436                if let Some(container2) = wrapped_object_containers.get(id) {
437                    assert_eq!(container1, container2);
438                }
439            }
440        }
441        // Merge the two maps because we may be calling the execution engine more than
442        // once (e.g. in advance epoch transaction, where we may be publishing a
443        // new system package).
444        self.wrapped_object_containers
445            .extend(wrapped_object_containers);
446    }
447
448    pub fn estimate_effects_size_upperbound(&self) -> usize {
449        TransactionEffects::estimate_effects_size_upperbound_v1(
450            self.execution_results.written_objects.len(),
451            self.execution_results.modified_objects.len(),
452            self.input_objects.len(),
453        )
454    }
455
456    pub fn written_objects_size(&self) -> usize {
457        self.execution_results
458            .written_objects
459            .values()
460            .fold(0, |sum, obj| sum + obj.object_size_for_gas_metering())
461    }
462
463    /// If there are unmetered storage rebate (due to system transaction), we
464    /// put them into the storage rebate of 0x5 object.
465    /// TODO: This will not work for potential future new system transactions if
466    /// 0x5 is not in the input. We should fix this.
467    pub fn conserve_unmetered_storage_rebate(&mut self, unmetered_storage_rebate: u64) {
468        if unmetered_storage_rebate == 0 {
469            // If unmetered_storage_rebate is 0, we are most likely executing the genesis
470            // transaction. And in that case we cannot mutate the 0x5 object
471            // because it's newly created. And there is no storage rebate that
472            // needs distribution anyway.
473            return;
474        }
475        tracing::debug!(
476            "Amount of unmetered storage rebate from system tx: {:?}",
477            unmetered_storage_rebate
478        );
479        let mut system_state_wrapper = self
480            .read_object(&IOTA_SYSTEM_STATE_OBJECT_ID)
481            .expect("0x5 object must be mutated in system tx with unmetered storage rebate")
482            .clone();
483        // In unmetered execution, storage_rebate field of mutated object must be 0.
484        // If not, we would be dropping IOTA on the floor by overriding it.
485        assert_eq!(system_state_wrapper.storage_rebate, 0);
486        system_state_wrapper.storage_rebate = unmetered_storage_rebate;
487        self.mutate_input_object(system_state_wrapper);
488    }
489
490    /// Given an object ID, if it's not modified, returns None.
491    /// Otherwise returns its metadata, including version, digest, owner and
492    /// storage rebate. A modified object must be either a mutable input, or
493    /// a loaded child object. The only exception is when we upgrade system
494    /// packages, in which case the upgraded system packages are not part of
495    /// input, but are modified.
496    fn get_object_modified_at(
497        &self,
498        object_id: &ObjectID,
499    ) -> Option<DynamicallyLoadedObjectMetadata> {
500        if self.execution_results.modified_objects.contains(object_id) {
501            Some(
502                self.mutable_input_refs
503                    .get(object_id)
504                    .map(
505                        |((version, digest), owner)| DynamicallyLoadedObjectMetadata {
506                            version: *version,
507                            digest: *digest,
508                            owner: *owner,
509                            // It's guaranteed that a mutable input object is an input object.
510                            storage_rebate: self.input_objects[object_id].storage_rebate,
511                            previous_transaction: self.input_objects[object_id]
512                                .previous_transaction,
513                        },
514                    )
515                    .or_else(|| self.loaded_runtime_objects.get(object_id).cloned())
516                    .unwrap_or_else(|| {
517                        debug_assert!(is_system_package(*object_id));
518                        let package_obj =
519                            self.store.get_package_object(object_id).unwrap().unwrap();
520                        let obj = package_obj.object();
521                        DynamicallyLoadedObjectMetadata {
522                            version: obj.version(),
523                            digest: obj.digest(),
524                            owner: obj.owner,
525                            storage_rebate: obj.storage_rebate,
526                            previous_transaction: obj.previous_transaction,
527                        }
528                    }),
529            )
530        } else {
531            None
532        }
533    }
534}
535
536impl TemporaryStore<'_> {
537    // check that every object read is owned directly or indirectly by sender,
538    // sponsor, or a shared object input
539    pub fn check_ownership_invariants(
540        &self,
541        sender: &IotaAddress,
542        gas_charger: &mut GasCharger,
543        mutable_inputs: &HashSet<ObjectID>,
544        is_epoch_change: bool,
545    ) -> IotaResult<()> {
546        let gas_objs: HashSet<&ObjectID> = gas_charger.gas_coins().iter().map(|g| &g.0).collect();
547        // mark input objects as authenticated
548        let mut authenticated_for_mutation: HashSet<_> = self
549            .input_objects
550            .iter()
551            .filter_map(|(id, obj)| {
552                if gas_objs.contains(id) {
553                    // gas could be owned by either the sender (common case) or sponsor
554                    // (if this is a sponsored tx, which we do not know inside this function).
555                    // Either way, no object ownership chain should be rooted in a gas object
556                    // thus, consider object authenticated, but don't add it to authenticated_objs
557                    return None;
558                }
559                match &obj.owner {
560                    Owner::AddressOwner(a) => {
561                        assert!(sender == a, "Input object not owned by sender");
562                        Some(id)
563                    }
564                    Owner::Shared { .. } => Some(id),
565                    Owner::Immutable => {
566                        // object is authenticated, but it cannot own other objects,
567                        // so we should not add it to `authenticated_objs`
568                        // However, we would definitely want to add immutable objects
569                        // to the set of authenticated roots if we were doing runtime
570                        // checks inside the VM instead of after-the-fact in the temporary
571                        // store. Here, we choose not to add them because this will catch a
572                        // bug where we mutate or delete an object that belongs to an immutable
573                        // object (though it will show up somewhat opaquely as an authentication
574                        // failure), whereas adding the immutable object to the roots will prevent
575                        // us from catching this.
576                        None
577                    }
578                    Owner::ObjectOwner(_parent) => {
579                        unreachable!("Input objects must be address owned, shared, or immutable")
580                    }
581                }
582            })
583            .filter(|id| {
584                // remove any non-mutable inputs. This will remove deleted or readonly shared
585                // objects
586                mutable_inputs.contains(id)
587            })
588            .copied()
589            .collect();
590
591        // check all modified objects are authenticated (excluding gas objects)
592        let mut objects_to_authenticate = self
593            .execution_results
594            .modified_objects
595            .iter()
596            .filter(|id| !gas_objs.contains(id))
597            .copied()
598            .collect::<Vec<_>>();
599        // Map from an ObjectID to the ObjectID that covers it.
600        while let Some(to_authenticate) = objects_to_authenticate.pop() {
601            if authenticated_for_mutation.contains(&to_authenticate) {
602                // object has been authenticated
603                continue;
604            }
605            let wrapped_parent = self.wrapped_object_containers.get(&to_authenticate);
606            let parent = if let Some(container_id) = wrapped_parent {
607                // If the object is wrapped, then the container must be authenticated.
608                // For example, the ID is for a wrapped table or bag.
609                *container_id
610            } else {
611                let Some(old_obj) = self.store.try_get_object(&to_authenticate)? else {
612                    panic!(
613                        "
614                        Failed to load object {to_authenticate:?}. \n\
615                        If it cannot be loaded, \
616                        we would expect it to be in the wrapped object map: {:?}",
617                        &self.wrapped_object_containers
618                    )
619                };
620                match &old_obj.owner {
621                    Owner::ObjectOwner(parent) => ObjectID::from(*parent),
622                    Owner::AddressOwner(parent) => {
623                        // For Receiving<_> objects, the address owner is actually an object.
624                        // If it was actually an address, we should have caught it as an input and
625                        // it would already have been in authenticated_for_mutation
626                        ObjectID::from(*parent)
627                    }
628                    owner @ Owner::Shared { .. } => panic!(
629                        "Unauthenticated root at {to_authenticate:?} with owner {owner:?}\n\
630                        Potentially covering objects in: {authenticated_for_mutation:#?}",
631                    ),
632                    Owner::Immutable => {
633                        assert!(
634                            is_epoch_change,
635                            "Immutable objects cannot be written, except for \
636                            IOTA Framework/Move stdlib upgrades at epoch change boundaries"
637                        );
638                        // Note: this assumes that the only immutable objects an epoch change
639                        // tx can update are system packages,
640                        // but in principle we could allow others.
641                        assert!(
642                            is_system_package(to_authenticate),
643                            "Only system packages can be upgraded"
644                        );
645                        continue;
646                    }
647                }
648            };
649            // we now assume the object is authenticated and must check the parent
650            authenticated_for_mutation.insert(to_authenticate);
651            objects_to_authenticate.push(parent);
652        }
653        Ok(())
654    }
655}
656
657impl TemporaryStore<'_> {
658    /// Track storage gas for each mutable input object (including the gas coin)
659    /// and each created object. Compute storage refunds for each deleted
660    /// object. Will *not* charge anything, gas status keeps track of
661    /// storage cost and rebate. All objects will be updated with their new
662    /// (current) storage rebate/cost. `IotaGasStatus` `storage_rebate` and
663    /// `storage_gas_units` track the transaction overall storage rebate and
664    /// cost.
665    pub(crate) fn collect_storage_and_rebate(&mut self, gas_charger: &mut GasCharger) {
666        // Use two loops because we cannot mut iterate written while calling
667        // get_object_modified_at.
668        let old_storage_rebates: Vec<_> = self
669            .execution_results
670            .written_objects
671            .keys()
672            .map(|object_id| {
673                self.get_object_modified_at(object_id)
674                    .map(|metadata| metadata.storage_rebate)
675                    .unwrap_or_default()
676            })
677            .collect();
678        for (object, old_storage_rebate) in self
679            .execution_results
680            .written_objects
681            .values_mut()
682            .zip(old_storage_rebates)
683        {
684            // new object size
685            let new_object_size = object.object_size_for_gas_metering();
686            // track changes and compute the new object `storage_rebate`
687            let new_storage_rebate = gas_charger.track_storage_mutation(
688                object.id(),
689                new_object_size,
690                old_storage_rebate,
691            );
692            object.storage_rebate = new_storage_rebate;
693        }
694
695        self.collect_rebate(gas_charger);
696    }
697
698    pub(crate) fn collect_rebate(&self, gas_charger: &mut GasCharger) {
699        for object_id in &self.execution_results.modified_objects {
700            if self
701                .execution_results
702                .written_objects
703                .contains_key(object_id)
704            {
705                continue;
706            }
707            // get and track the deleted object `storage_rebate`
708            let storage_rebate = self
709                .get_object_modified_at(object_id)
710                // Unwrap is safe because this loop iterates through all modified objects.
711                .unwrap()
712                .storage_rebate;
713            gas_charger.track_storage_mutation(*object_id, 0, storage_rebate);
714        }
715    }
716
717    pub fn check_execution_results_consistency(&self) -> Result<(), ExecutionError> {
718        assert_invariant!(
719            self.execution_results
720                .created_object_ids
721                .iter()
722                .all(|id| !self.execution_results.deleted_object_ids.contains(id)
723                    && !self.execution_results.modified_objects.contains(id)),
724            "Created object IDs cannot also be deleted or modified"
725        );
726        assert_invariant!(
727            self.execution_results.modified_objects.iter().all(|id| {
728                self.mutable_input_refs.contains_key(id)
729                    || self.loaded_runtime_objects.contains_key(id)
730                    || is_system_package(*id)
731            }),
732            "A modified object must be either a mutable input, a loaded child object, or a system package"
733        );
734        Ok(())
735    }
736}
737//==============================================================================
738// Charge gas current - end
739//==============================================================================
740
741impl TemporaryStore<'_> {
742    pub fn advance_epoch_safe_mode(
743        &mut self,
744        params: &AdvanceEpochParams,
745        protocol_config: &ProtocolConfig,
746    ) {
747        let wrapper = get_iota_system_state_wrapper(self.store.as_object_store())
748            .expect("System state wrapper object must exist");
749        let (old_object, new_object) =
750            wrapper.advance_epoch_safe_mode(params, self.store.as_object_store(), protocol_config);
751        self.mutate_child_object(old_object, new_object);
752    }
753}
754
755type ModifiedObjectInfo<'a> = (
756    ObjectID,
757    // old object metadata, including version, digest, owner, and storage rebate.
758    Option<DynamicallyLoadedObjectMetadata>,
759    Option<&'a Object>,
760);
761
762impl TemporaryStore<'_> {
763    fn get_input_iota(
764        &self,
765        id: &ObjectID,
766        expected_version: SequenceNumber,
767        layout_resolver: &mut impl LayoutResolver,
768    ) -> Result<u64, ExecutionError> {
769        if let Some(obj) = self.input_objects.get(id) {
770            // the assumption here is that if it is in the input objects must be the right
771            // one
772            if obj.version() != expected_version {
773                invariant_violation!(
774                    "Version mismatching when resolving input object to check conservation--\
775                     expected {}, got {}",
776                    expected_version,
777                    obj.version(),
778                );
779            }
780            obj.get_total_iota(layout_resolver).map_err(|e| {
781                make_invariant_violation!(
782                    "Failed looking up input IOTA in IOTA conservation checking for input with \
783                         type {:?}: {e:#?}",
784                    obj.struct_tag(),
785                )
786            })
787        } else {
788            // not in input objects, must be a dynamic field
789            let Ok(Some(obj)) = self.store.try_get_object_by_key(id, expected_version) else {
790                invariant_violation!(
791                    "Failed looking up dynamic field {id} in IOTA conservation checking"
792                );
793            };
794            obj.get_total_iota(layout_resolver).map_err(|e| {
795                make_invariant_violation!(
796                    "Failed looking up input IOTA in IOTA conservation checking for type \
797                         {:?}: {e:#?}",
798                    obj.struct_tag(),
799                )
800            })
801        }
802    }
803
804    /// Return the list of all modified objects, for each object, returns
805    /// - Object ID,
806    /// - Input: If the object existed prior to this transaction, include their
807    ///   version and storage_rebate,
808    /// - Output: If a new version of the object is written, include the new
809    ///   object.
810    fn get_modified_objects(&self) -> Vec<ModifiedObjectInfo<'_>> {
811        self.execution_results
812            .modified_objects
813            .iter()
814            .map(|id| {
815                let metadata = self.get_object_modified_at(id);
816                let output = self.execution_results.written_objects.get(id);
817                (*id, metadata, output)
818            })
819            .chain(
820                self.execution_results
821                    .written_objects
822                    .iter()
823                    .filter_map(|(id, object)| {
824                        if self.execution_results.modified_objects.contains(id) {
825                            None
826                        } else {
827                            Some((*id, None, Some(object)))
828                        }
829                    }),
830            )
831            .collect()
832    }
833
834    /// Check that this transaction neither creates nor destroys IOTA. This
835    /// should hold for all txes except the epoch change tx, which mints
836    /// staking rewards equal to the gas fees burned in the previous epoch.
837    /// Specifically, this checks two key invariants about storage
838    /// fees and storage rebate:
839    ///
840    /// 1. all IOTA in storage rebate fields of input objects should flow either
841    ///    to the transaction storage rebate, or the transaction non-refundable
842    ///    storage rebate
843    /// 2. all IOTA charged for storage should flow into the storage rebate
844    ///    field of some output object
845    ///
846    /// This function is intended to be called *after* we have charged for
847    /// gas + applied the storage rebate to the gas object, but *before* we
848    /// have updated object versions.
849    pub fn check_iota_conserved(&self, gas_summary: &GasCostSummary) -> Result<(), ExecutionError> {
850        // total amount of IOTA in storage rebate of input objects
851        let mut total_input_rebate = 0;
852        // total amount of IOTA in storage rebate of output objects
853        let mut total_output_rebate = 0;
854        for (_, input, output) in self.get_modified_objects() {
855            if let Some(input) = input {
856                total_input_rebate += input.storage_rebate;
857            }
858            if let Some(object) = output {
859                total_output_rebate += object.storage_rebate;
860            }
861        }
862
863        if gas_summary.storage_cost == 0 {
864            // this condition is usually true when the transaction went OOG and no
865            // gas is left for storage charges.
866            // The storage cost has to be there at least for the gas coin which
867            // will not be deleted even when going to 0.
868            // However if the storage cost is 0 and if there is any object touched
869            // or deleted the value in input must be equal to the output plus rebate and
870            // non refundable.
871            // Rebate and non refundable will be positive when there are object deleted
872            // (gas smashing being the primary and possibly only example).
873            // A more typical condition is for all storage charges in summary to be 0 and
874            // then input and output must be the same value
875            if total_input_rebate
876                != total_output_rebate
877                    + gas_summary.storage_rebate
878                    + gas_summary.non_refundable_storage_fee
879            {
880                return Err(ExecutionError::invariant_violation(format!(
881                    "IOTA conservation failed -- no storage charges in gas summary \
882                        and total storage input rebate {total_input_rebate} not equal  \
883                        to total storage output rebate {total_output_rebate}",
884                )));
885            }
886        } else {
887            // all IOTA in storage rebate fields of input objects should flow either to
888            // the transaction storage rebate, or the non-refundable storage rebate pool
889            if total_input_rebate
890                != gas_summary.storage_rebate + gas_summary.non_refundable_storage_fee
891            {
892                return Err(ExecutionError::invariant_violation(format!(
893                    "IOTA conservation failed -- {} IOTA in storage rebate field of input objects, \
894                        {} IOTA in tx storage rebate or tx non-refundable storage rebate",
895                    total_input_rebate, gas_summary.non_refundable_storage_fee,
896                )));
897            }
898
899            // all IOTA charged for storage should flow into the storage rebate field
900            // of some output object
901            if gas_summary.storage_cost != total_output_rebate {
902                return Err(ExecutionError::invariant_violation(format!(
903                    "IOTA conservation failed -- {} IOTA charged for storage, \
904                        {} IOTA in storage rebate field of output objects",
905                    gas_summary.storage_cost, total_output_rebate
906                )));
907            }
908        }
909        Ok(())
910    }
911
912    /// Check that this transaction neither creates nor destroys IOTA.
913    /// This more expensive check will check a third invariant on top of the 2
914    /// performed by `check_iota_conserved` above:
915    ///
916    /// * all IOTA in input objects (including coins etc in the Move part of an
917    ///   object) should flow either to an output object, or be burned as part
918    ///   of computation fees or non-refundable storage rebate
919    ///
920    /// This function is intended to be called *after* we have charged for gas +
921    /// applied the storage rebate to the gas object, but *before* we have
922    /// updated object versions. The advance epoch transaction would mint
923    /// `epoch_fees` amount of IOTA, and burn `epoch_rebates` amount of IOTA.
924    /// We need these information for this check.
925    pub fn check_iota_conserved_expensive(
926        &self,
927        gas_summary: &GasCostSummary,
928        advance_epoch_gas_summary: Option<(u64, u64)>,
929        layout_resolver: &mut impl LayoutResolver,
930    ) -> Result<(), ExecutionError> {
931        // total amount of IOTA in input objects, including both coins and storage
932        // rebates
933        let mut total_input_iota = 0;
934        // total amount of IOTA in output objects, including both coins and storage
935        // rebates
936        let mut total_output_iota = 0;
937        for (id, input, output) in self.get_modified_objects() {
938            if let Some(input) = input {
939                total_input_iota += self.get_input_iota(&id, input.version, layout_resolver)?;
940            }
941            if let Some(object) = output {
942                total_output_iota += object.get_total_iota(layout_resolver).map_err(|e| {
943                    make_invariant_violation!(
944                        "Failed looking up output IOTA in IOTA conservation checking for \
945                         mutated type {:?}: {e:#?}",
946                        object.struct_tag(),
947                    )
948                })?;
949            }
950        }
951        // note: storage_cost flows into the storage_rebate field of the output objects,
952        // which is why it is not accounted for here.
953        // similarly, all of the storage_rebate *except* the storage_fund_rebate_inflow
954        // gets credited to the gas coin both computation costs and storage rebate
955        // inflow are
956        total_output_iota += gas_summary.computation_cost + gas_summary.non_refundable_storage_fee;
957        if let Some((epoch_fees, epoch_rebates)) = advance_epoch_gas_summary {
958            total_input_iota += epoch_fees;
959            total_output_iota += epoch_rebates;
960        }
961        if total_input_iota != total_output_iota {
962            return Err(ExecutionError::invariant_violation(format!(
963                "IOTA conservation failed: input={total_input_iota}, output={total_output_iota}, \
964                    this transaction either mints or burns IOTA",
965            )));
966        }
967        Ok(())
968    }
969}
970
971impl ChildObjectResolver for TemporaryStore<'_> {
972    fn read_child_object(
973        &self,
974        parent: &ObjectID,
975        child: &ObjectID,
976        child_version_upper_bound: SequenceNumber,
977    ) -> IotaResult<Option<Object>> {
978        let obj_opt = self.execution_results.written_objects.get(child);
979        if obj_opt.is_some() {
980            Ok(obj_opt.cloned())
981        } else {
982            let _scope = monitored_scope("Execution::read_child_object");
983            self.store
984                .read_child_object(parent, child, child_version_upper_bound)
985        }
986    }
987
988    fn get_object_received_at_version(
989        &self,
990        owner: &ObjectID,
991        receiving_object_id: &ObjectID,
992        receive_object_at_version: SequenceNumber,
993        epoch_id: EpochId,
994    ) -> IotaResult<Option<Object>> {
995        // You should never be able to try and receive an object after deleting it or
996        // writing it in the same transaction since `Receiving` doesn't have
997        // copy.
998        debug_assert!(
999            !self
1000                .execution_results
1001                .written_objects
1002                .contains_key(receiving_object_id)
1003        );
1004        debug_assert!(
1005            !self
1006                .execution_results
1007                .deleted_object_ids
1008                .contains(receiving_object_id)
1009        );
1010        self.store.get_object_received_at_version(
1011            owner,
1012            receiving_object_id,
1013            receive_object_at_version,
1014            epoch_id,
1015        )
1016    }
1017}
1018
1019impl Storage for TemporaryStore<'_> {
1020    fn reset(&mut self) {
1021        self.drop_writes();
1022    }
1023
1024    fn read_object(&self, id: &ObjectID) -> Option<&Object> {
1025        TemporaryStore::read_object(self, id)
1026    }
1027
1028    /// Take execution results v1.
1029    fn record_execution_results(&mut self, results: ExecutionResults) {
1030        let ExecutionResults::V1(results) = results;
1031
1032        // It's important to merge instead of override results because it's
1033        // possible to execute PT more than once during tx execution.
1034        self.execution_results.merge_results(results);
1035    }
1036
1037    fn save_loaded_runtime_objects(
1038        &mut self,
1039        loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
1040    ) {
1041        TemporaryStore::save_loaded_runtime_objects(self, loaded_runtime_objects)
1042    }
1043
1044    fn save_wrapped_object_containers(
1045        &mut self,
1046        wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
1047    ) {
1048        TemporaryStore::save_wrapped_object_containers(self, wrapped_object_containers)
1049    }
1050
1051    fn check_coin_deny_list(&self, written_objects: &BTreeMap<ObjectID, Object>) -> DenyListResult {
1052        let result = check_coin_deny_list_v1_during_execution(
1053            written_objects,
1054            self.cur_epoch,
1055            self.store.as_object_store(),
1056        );
1057        // The denylist object is only loaded if there are regulated transfers.
1058        // And also if we already have it in the input there is no need to commit it
1059        // again in the effects.
1060        if result.num_non_gas_coin_owners > 0
1061            && !self.input_objects.contains_key(&IOTA_DENY_LIST_OBJECT_ID)
1062        {
1063            self.loaded_per_epoch_config_objects
1064                .write()
1065                .insert(IOTA_DENY_LIST_OBJECT_ID);
1066        }
1067        result
1068    }
1069}
1070
1071impl BackingPackageStore for TemporaryStore<'_> {
1072    fn get_package_object(&self, package_id: &ObjectID) -> IotaResult<Option<PackageObject>> {
1073        // We first check the objects in the temporary store because in non-production
1074        // code path, it is possible to read packages that are just written in
1075        // the same transaction. This can happen for example when we run the
1076        // expensive conservation checks, where we may look into the types of
1077        // each written object in the output, and some of them need the
1078        // newly written packages for type checking.
1079        // In production path though, this should never happen.
1080        if let Some(obj) = self.execution_results.written_objects.get(package_id) {
1081            Ok(Some(PackageObject::new(obj.clone())))
1082        } else {
1083            self.store.get_package_object(package_id).inspect(|obj| {
1084                // Track object but leave unchanged
1085                if let Some(v) = obj {
1086                    if !self
1087                        .runtime_packages_loaded_from_db
1088                        .read()
1089                        .contains_key(package_id)
1090                    {
1091                        // TODO: Can this lock ever block execution?
1092                        // TODO: Another way to avoid the cost of maintaining this map is to not
1093                        // enable it in normal runs, and if a fork is detected, rerun it with a flag
1094                        // turned on and start populating this field.
1095                        self.runtime_packages_loaded_from_db
1096                            .write()
1097                            .insert(*package_id, v.clone());
1098                    }
1099                }
1100            })
1101        }
1102    }
1103}
1104
1105impl ResourceResolver for TemporaryStore<'_> {
1106    type Error = IotaError;
1107
1108    fn get_resource(
1109        &self,
1110        address: &AccountAddress,
1111        struct_tag: &StructTag,
1112    ) -> Result<Option<Vec<u8>>, Self::Error> {
1113        let object = match self.read_object(&ObjectID::from(*address)) {
1114            Some(x) => x,
1115            None => match self.read_object(&ObjectID::from(*address)) {
1116                None => return Ok(None),
1117                Some(x) => {
1118                    if !x.is_immutable() {
1119                        fp_bail!(IotaError::ExecutionInvariantViolation);
1120                    }
1121                    x
1122                }
1123            },
1124        };
1125
1126        match &object.data {
1127            Data::Move(m) => {
1128                assert!(
1129                    m.is_type(struct_tag),
1130                    "Invariant violation: ill-typed object in storage \
1131                    or bad object request from caller"
1132                );
1133                Ok(Some(m.contents().to_vec()))
1134            }
1135            other => unimplemented!(
1136                "Bad object lookup: expected Move object, but got {:?}",
1137                other
1138            ),
1139        }
1140    }
1141}