1use crate::gas_charger::GasCharger;
5use mysten_metrics::monitored_scope;
6use parking_lot::RwLock;
7use std::collections::{BTreeMap, BTreeSet, HashSet};
8use sui_protocol_config::ProtocolConfig;
9use sui_types::accumulator_event::AccumulatorEvent;
10use sui_types::base_types::VersionDigest;
11use sui_types::committee::EpochId;
12use sui_types::deny_list_v2::check_coin_deny_list_v2_during_execution;
13use sui_types::effects::{AccumulatorWriteV1, TransactionEffects, TransactionEvents};
14use sui_types::error::ExecutionErrorKind;
15use sui_types::execution::{
16 DynamicallyLoadedObjectMetadata, ExecutionResults, ExecutionResultsV2, SharedInput,
17};
18use sui_types::execution_status::ExecutionStatus;
19use sui_types::inner_temporary_store::InnerTemporaryStore;
20use sui_types::layout_resolver::LayoutResolver;
21use sui_types::object::Data;
22use sui_types::storage::{BackingStore, DenyListResult, PackageObject};
23use sui_types::sui_system_state::{AdvanceEpochParams, get_sui_system_state_wrapper};
24use sui_types::{
25 SUI_DENY_LIST_OBJECT_ID,
26 base_types::{ObjectID, ObjectRef, SequenceNumber, SuiAddress, TransactionDigest},
27 effects::EffectsObjectChange,
28 error::{ExecutionError, SuiResult},
29 gas::GasCostSummary,
30 object::Object,
31 object::Owner,
32 storage::{BackingPackageStore, ChildObjectResolver, ParentSync, Storage},
33 transaction::InputObjects,
34};
35use sui_types::{SUI_SYSTEM_STATE_OBJECT_ID, TypeTag, is_system_package};
36
37pub struct TemporaryStore<'backing> {
38 store: &'backing dyn BackingStore,
44 tx_digest: TransactionDigest,
45 input_objects: BTreeMap<ObjectID, Object>,
46
47 non_exclusive_input_original_versions: BTreeMap<ObjectID, Object>,
50
51 stream_ended_consensus_objects: BTreeMap<ObjectID, SequenceNumber >,
52 lamport_timestamp: SequenceNumber,
54 mutable_input_refs: BTreeMap<ObjectID, (VersionDigest, Owner)>,
57 execution_results: ExecutionResultsV2,
58 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
60 wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
62 protocol_config: &'backing ProtocolConfig,
63
64 runtime_packages_loaded_from_db: RwLock<BTreeMap<ObjectID, PackageObject>>,
67
68 receiving_objects: Vec<ObjectRef>,
71
72 generated_runtime_ids: BTreeSet<ObjectID>,
76
77 cur_epoch: EpochId,
80
81 loaded_per_epoch_config_objects: RwLock<BTreeSet<ObjectID>>,
84}
85
86impl<'backing> TemporaryStore<'backing> {
87 pub fn new(
90 store: &'backing dyn BackingStore,
91 input_objects: InputObjects,
92 receiving_objects: Vec<ObjectRef>,
93 tx_digest: TransactionDigest,
94 protocol_config: &'backing ProtocolConfig,
95 cur_epoch: EpochId,
96 ) -> Self {
97 let mutable_input_refs = input_objects.exclusive_mutable_inputs();
98 let non_exclusive_input_original_versions = input_objects.non_exclusive_input_objects();
99
100 let lamport_timestamp = input_objects.lamport_timestamp(&receiving_objects);
101 let stream_ended_consensus_objects = input_objects.consensus_stream_ended_objects();
102 let objects = input_objects.into_object_map();
103 #[cfg(debug_assertions)]
104 {
105 assert!(
107 objects
108 .keys()
109 .collect::<HashSet<_>>()
110 .intersection(
111 &receiving_objects
112 .iter()
113 .map(|oref| &oref.0)
114 .collect::<HashSet<_>>()
115 )
116 .next()
117 .is_none()
118 );
119 }
120 Self {
121 store,
122 tx_digest,
123 input_objects: objects,
124 non_exclusive_input_original_versions,
125 stream_ended_consensus_objects,
126 lamport_timestamp,
127 mutable_input_refs,
128 execution_results: ExecutionResultsV2::default(),
129 protocol_config,
130 loaded_runtime_objects: BTreeMap::new(),
131 wrapped_object_containers: BTreeMap::new(),
132 runtime_packages_loaded_from_db: RwLock::new(BTreeMap::new()),
133 receiving_objects,
134 generated_runtime_ids: BTreeSet::new(),
135 cur_epoch,
136 loaded_per_epoch_config_objects: RwLock::new(BTreeSet::new()),
137 }
138 }
139
140 pub fn objects(&self) -> &BTreeMap<ObjectID, Object> {
142 &self.input_objects
143 }
144
145 pub fn update_object_version_and_prev_tx(&mut self) {
146 self.execution_results.update_version_and_previous_tx(
147 self.lamport_timestamp,
148 self.tx_digest,
149 &self.input_objects,
150 self.protocol_config.reshare_at_same_initial_version(),
151 );
152
153 #[cfg(debug_assertions)]
154 {
155 self.check_invariants();
156 }
157 }
158
159 fn merge_accumulator_events(
160 accumulator_events: &[AccumulatorEvent],
161 ) -> impl Iterator<Item = (ObjectID, EffectsObjectChange)> + '_ {
162 accumulator_events
163 .iter()
164 .fold(
165 BTreeMap::<ObjectID, Vec<AccumulatorWriteV1>>::new(),
166 |mut map, event| {
167 map.entry(*event.accumulator_obj.inner())
168 .or_default()
169 .push(event.write.clone());
170 map
171 },
172 )
173 .into_iter()
174 .map(|(obj_id, writes)| {
175 (
176 obj_id,
177 EffectsObjectChange::new_from_accumulator_write(AccumulatorWriteV1::merge(
178 writes,
179 )),
180 )
181 })
182 }
183
184 pub fn into_inner(self) -> InnerTemporaryStore {
186 let results = self.execution_results;
187 InnerTemporaryStore {
188 input_objects: self.input_objects,
189 stream_ended_consensus_objects: self.stream_ended_consensus_objects,
190 mutable_inputs: self.mutable_input_refs,
191 written: results.written_objects,
192 events: TransactionEvents {
193 data: results.user_events,
194 },
195 accumulator_events: results.accumulator_events,
196 loaded_runtime_objects: self.loaded_runtime_objects,
197 runtime_packages_loaded_from_db: self.runtime_packages_loaded_from_db.into_inner(),
198 lamport_version: self.lamport_timestamp,
199 binary_config: self.protocol_config.binary_config(None),
200 }
201 }
202
203 pub(crate) fn ensure_active_inputs_mutated(&mut self) {
207 let mut to_be_updated = vec![];
208 for id in self.mutable_input_refs.keys() {
210 if !self.execution_results.modified_objects.contains(id) {
211 to_be_updated.push(self.input_objects[id].clone());
215 }
216 }
217 for object in to_be_updated {
218 self.mutate_input_object(object.clone());
220 }
221 }
222
223 fn get_object_changes(&self) -> BTreeMap<ObjectID, EffectsObjectChange> {
224 let results = &self.execution_results;
225 let all_ids = results
226 .created_object_ids
227 .iter()
228 .chain(&results.deleted_object_ids)
229 .chain(&results.modified_objects)
230 .chain(results.written_objects.keys())
231 .collect::<BTreeSet<_>>();
232 all_ids
233 .into_iter()
234 .map(|id| {
235 (
236 *id,
237 EffectsObjectChange::new(
238 self.get_object_modified_at(id)
239 .map(|metadata| ((metadata.version, metadata.digest), metadata.owner)),
240 results.written_objects.get(id),
241 results.created_object_ids.contains(id),
242 results.deleted_object_ids.contains(id),
243 ),
244 )
245 })
246 .chain(Self::merge_accumulator_events(&results.accumulator_events))
247 .collect()
248 }
249
250 pub fn into_effects(
251 mut self,
252 shared_object_refs: Vec<SharedInput>,
253 transaction_digest: &TransactionDigest,
254 mut transaction_dependencies: BTreeSet<TransactionDigest>,
255 gas_cost_summary: GasCostSummary,
256 status: ExecutionStatus,
257 gas_charger: &mut GasCharger,
258 epoch: EpochId,
259 ) -> (InnerTemporaryStore, TransactionEffects) {
260 self.update_object_version_and_prev_tx();
261
262 for (id, expected_version, expected_digest) in &self.receiving_objects {
265 if let Some(obj_meta) = self.loaded_runtime_objects.get(id) {
269 let loaded_via_receive = obj_meta.version == *expected_version
273 && obj_meta.digest == *expected_digest
274 && obj_meta.owner.is_address_owned();
275 if loaded_via_receive {
276 transaction_dependencies.insert(obj_meta.previous_transaction);
277 }
278 }
279 }
280
281 assert!(self.protocol_config.enable_effects_v2());
282
283 let gas_coin = gas_charger.gas_coin();
288
289 let object_changes = self.get_object_changes();
290
291 let lamport_version = self.lamport_timestamp;
292 let loaded_per_epoch_config_objects = self.loaded_per_epoch_config_objects.read().clone();
294 let inner = self.into_inner();
295
296 let effects = TransactionEffects::new_from_execution_v2(
297 status,
298 epoch,
299 gas_cost_summary,
300 shared_object_refs,
302 loaded_per_epoch_config_objects,
303 *transaction_digest,
304 lamport_version,
305 object_changes,
306 gas_coin,
307 if inner.events.data.is_empty() {
308 None
309 } else {
310 Some(inner.events.digest())
311 },
312 transaction_dependencies.into_iter().collect(),
313 );
314
315 (inner, effects)
316 }
317
318 #[cfg(debug_assertions)]
320 fn check_invariants(&self) {
321 debug_assert!(
323 {
324 self.execution_results
325 .written_objects
326 .keys()
327 .all(|id| !self.execution_results.deleted_object_ids.contains(id))
328 },
329 "Object both written and deleted."
330 );
331
332 debug_assert!(
334 {
335 self.mutable_input_refs
336 .keys()
337 .all(|id| self.execution_results.modified_objects.contains(id))
338 },
339 "Mutable input not modified."
340 );
341
342 debug_assert!(
343 {
344 self.execution_results
345 .written_objects
346 .values()
347 .all(|obj| obj.previous_transaction == self.tx_digest)
348 },
349 "Object previous transaction not properly set",
350 );
351 }
352
353 pub fn mutate_input_object(&mut self, object: Object) {
355 let id = object.id();
356 debug_assert!(self.input_objects.contains_key(&id));
357 debug_assert!(!object.is_immutable());
358 self.execution_results.modified_objects.insert(id);
359 self.execution_results.written_objects.insert(id, object);
360 }
361
362 pub fn mutate_child_object(&mut self, old_object: Object, new_object: Object) {
366 let id = new_object.id();
367 let old_ref = old_object.compute_object_reference();
368 debug_assert_eq!(old_ref.0, id);
369 self.loaded_runtime_objects.insert(
370 id,
371 DynamicallyLoadedObjectMetadata {
372 version: old_ref.1,
373 digest: old_ref.2,
374 owner: old_object.owner.clone(),
375 storage_rebate: old_object.storage_rebate,
376 previous_transaction: old_object.previous_transaction,
377 },
378 );
379 self.execution_results.modified_objects.insert(id);
380 self.execution_results
381 .written_objects
382 .insert(id, new_object);
383 }
384
385 pub fn upgrade_system_package(&mut self, package: Object) {
389 let id = package.id();
390 assert!(package.is_package() && is_system_package(id));
391 self.execution_results.modified_objects.insert(id);
392 self.execution_results.written_objects.insert(id, package);
393 }
394
395 pub fn create_object(&mut self, object: Object) {
397 debug_assert!(
402 object.is_immutable() || object.version() == SequenceNumber::MIN,
403 "Created mutable objects should not have a version set",
404 );
405 let id = object.id();
406 self.execution_results.created_object_ids.insert(id);
407 self.execution_results.written_objects.insert(id, object);
408 }
409
410 pub fn delete_input_object(&mut self, id: &ObjectID) {
412 debug_assert!(!self.execution_results.written_objects.contains_key(id));
414 debug_assert!(self.input_objects.contains_key(id));
415 self.execution_results.modified_objects.insert(*id);
416 self.execution_results.deleted_object_ids.insert(*id);
417 }
418
419 pub fn drop_writes(&mut self) {
420 self.execution_results.drop_writes();
421 }
422
423 pub fn read_object(&self, id: &ObjectID) -> Option<&Object> {
424 debug_assert!(!self.execution_results.deleted_object_ids.contains(id));
426 self.execution_results
427 .written_objects
428 .get(id)
429 .or_else(|| self.input_objects.get(id))
430 }
431
432 pub fn save_loaded_runtime_objects(
433 &mut self,
434 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
435 ) {
436 #[cfg(debug_assertions)]
437 {
438 for (id, v1) in &loaded_runtime_objects {
439 if let Some(v2) = self.loaded_runtime_objects.get(id) {
440 assert_eq!(v1, v2);
441 }
442 }
443 for (id, v1) in &self.loaded_runtime_objects {
444 if let Some(v2) = loaded_runtime_objects.get(id) {
445 assert_eq!(v1, v2);
446 }
447 }
448 }
449 self.loaded_runtime_objects.extend(loaded_runtime_objects);
452 }
453
454 pub fn save_wrapped_object_containers(
455 &mut self,
456 wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
457 ) {
458 #[cfg(debug_assertions)]
459 {
460 for (id, container1) in &wrapped_object_containers {
461 if let Some(container2) = self.wrapped_object_containers.get(id) {
462 assert_eq!(container1, container2);
463 }
464 }
465 for (id, container1) in &self.wrapped_object_containers {
466 if let Some(container2) = wrapped_object_containers.get(id) {
467 assert_eq!(container1, container2);
468 }
469 }
470 }
471 self.wrapped_object_containers
474 .extend(wrapped_object_containers);
475 }
476
477 pub fn save_generated_object_ids(&mut self, generated_ids: BTreeSet<ObjectID>) {
478 #[cfg(debug_assertions)]
479 {
480 for id in &self.generated_runtime_ids {
481 assert!(!generated_ids.contains(id))
482 }
483 for id in &generated_ids {
484 assert!(!self.generated_runtime_ids.contains(id));
485 }
486 }
487 self.generated_runtime_ids.extend(generated_ids);
488 }
489
490 pub fn estimate_effects_size_upperbound(&self) -> usize {
491 TransactionEffects::estimate_effects_size_upperbound_v2(
492 self.execution_results.written_objects.len(),
493 self.execution_results.modified_objects.len(),
494 self.input_objects.len(),
495 )
496 }
497
498 pub fn written_objects_size(&self) -> usize {
499 self.execution_results
500 .written_objects
501 .values()
502 .fold(0, |sum, obj| sum + obj.object_size_for_gas_metering())
503 }
504
505 pub fn conserve_unmetered_storage_rebate(&mut self, unmetered_storage_rebate: u64) {
510 if unmetered_storage_rebate == 0 {
511 return;
515 }
516 tracing::debug!(
517 "Amount of unmetered storage rebate from system tx: {:?}",
518 unmetered_storage_rebate
519 );
520 let mut system_state_wrapper = self
521 .read_object(&SUI_SYSTEM_STATE_OBJECT_ID)
522 .expect("0x5 object must be mutated in system tx with unmetered storage rebate")
523 .clone();
524 assert_eq!(system_state_wrapper.storage_rebate, 0);
527 system_state_wrapper.storage_rebate = unmetered_storage_rebate;
528 self.mutate_input_object(system_state_wrapper);
529 }
530
531 pub fn add_accumulator_event(&mut self, event: AccumulatorEvent) {
533 self.execution_results.accumulator_events.push(event);
534 }
535
536 fn get_object_modified_at(
542 &self,
543 object_id: &ObjectID,
544 ) -> Option<DynamicallyLoadedObjectMetadata> {
545 if self.execution_results.modified_objects.contains(object_id) {
546 Some(
547 self.mutable_input_refs
548 .get(object_id)
549 .map(
550 |((version, digest), owner)| DynamicallyLoadedObjectMetadata {
551 version: *version,
552 digest: *digest,
553 owner: owner.clone(),
554 storage_rebate: self.input_objects[object_id].storage_rebate,
556 previous_transaction: self.input_objects[object_id]
557 .previous_transaction,
558 },
559 )
560 .or_else(|| self.loaded_runtime_objects.get(object_id).cloned())
561 .unwrap_or_else(|| {
562 debug_assert!(is_system_package(*object_id));
563 let package_obj =
564 self.store.get_package_object(object_id).unwrap().unwrap();
565 let obj = package_obj.object();
566 DynamicallyLoadedObjectMetadata {
567 version: obj.version(),
568 digest: obj.digest(),
569 owner: obj.owner.clone(),
570 storage_rebate: obj.storage_rebate,
571 previous_transaction: obj.previous_transaction,
572 }
573 }),
574 )
575 } else {
576 None
577 }
578 }
579}
580
581impl TemporaryStore<'_> {
582 pub fn check_ownership_invariants(
585 &self,
586 sender: &SuiAddress,
587 sponsor: &Option<SuiAddress>,
588 gas_charger: &mut GasCharger,
589 mutable_inputs: &HashSet<ObjectID>,
590 is_epoch_change: bool,
591 ) -> SuiResult<()> {
592 let gas_objs: HashSet<&ObjectID> = gas_charger.gas_coins().iter().map(|g| &g.0).collect();
593 let gas_owner = sponsor.as_ref().unwrap_or(sender);
594
595 let mut authenticated_for_mutation: HashSet<_> = self
597 .input_objects
598 .iter()
599 .filter_map(|(id, obj)| {
600 match &obj.owner {
601 Owner::AddressOwner(a) => {
602 if gas_objs.contains(id) {
603 assert!(
605 a == gas_owner,
606 "Gas object must be owned by sender or sponsor"
607 );
608 } else {
609 assert!(sender == a, "Input object must be owned by sender");
610 }
611 Some(id)
612 }
613 Owner::Shared { .. } | Owner::ConsensusAddressOwner { .. } => Some(id),
614 Owner::Immutable => {
615 None
626 }
627 Owner::ObjectOwner(_parent) => {
628 unreachable!(
629 "Input objects must be address owned, shared, consensus, or immutable"
630 )
631 }
632 }
633 })
634 .filter(|id| {
635 mutable_inputs.contains(id)
638 })
639 .copied()
640 .chain(self.generated_runtime_ids.iter().copied())
643 .collect();
644
645 authenticated_for_mutation.insert((*sender).into());
647 if let Some(sponsor) = sponsor {
648 authenticated_for_mutation.insert((*sponsor).into());
649 }
650
651 let mut objects_to_authenticate = self
653 .execution_results
654 .modified_objects
655 .iter()
656 .copied()
657 .collect::<Vec<_>>();
658
659 while let Some(to_authenticate) = objects_to_authenticate.pop() {
660 if authenticated_for_mutation.contains(&to_authenticate) {
661 continue;
663 }
664
665 let parent = if let Some(container_id) =
666 self.wrapped_object_containers.get(&to_authenticate)
667 {
668 *container_id
670 } else {
671 let Some(old_obj) = self.store.get_object(&to_authenticate) else {
674 panic!(
675 "Failed to load object {to_authenticate:?}.\n \
676 If it cannot be loaded, we would expect it to be in the wrapped object map: {:#?}",
677 &self.wrapped_object_containers
678 )
679 };
680
681 match &old_obj.owner {
682 Owner::ObjectOwner(parent) => ObjectID::from(*parent),
685 Owner::AddressOwner(parent)
690 | Owner::ConsensusAddressOwner { owner: parent, .. } => {
691 ObjectID::from(*parent)
695 }
696 owner @ Owner::Shared { .. } => {
699 panic!(
700 "Unauthenticated root at {to_authenticate:?} with owner {owner:?}\n\
701 Potentially covering objects in: {authenticated_for_mutation:#?}"
702 );
703 }
704 Owner::Immutable => {
705 assert!(
706 is_epoch_change,
707 "Immutable objects cannot be written, except for \
708 Sui Framework/Move stdlib upgrades at epoch change boundaries"
709 );
710 assert!(
714 is_system_package(to_authenticate),
715 "Only system packages can be upgraded"
716 );
717 continue;
718 }
719 }
720 };
721
722 authenticated_for_mutation.insert(to_authenticate);
724 objects_to_authenticate.push(parent);
725 }
726 Ok(())
727 }
728}
729
730impl TemporaryStore<'_> {
731 pub(crate) fn collect_storage_and_rebate(&mut self, gas_charger: &mut GasCharger) {
738 let old_storage_rebates: Vec<_> = self
740 .execution_results
741 .written_objects
742 .keys()
743 .map(|object_id| {
744 self.get_object_modified_at(object_id)
745 .map(|metadata| metadata.storage_rebate)
746 .unwrap_or_default()
747 })
748 .collect();
749 for (object, old_storage_rebate) in self
750 .execution_results
751 .written_objects
752 .values_mut()
753 .zip(old_storage_rebates)
754 {
755 let new_object_size = object.object_size_for_gas_metering();
757 let new_storage_rebate = gas_charger.track_storage_mutation(
759 object.id(),
760 new_object_size,
761 old_storage_rebate,
762 );
763 object.storage_rebate = new_storage_rebate;
764 }
765
766 self.collect_rebate(gas_charger);
767 }
768
769 pub(crate) fn collect_rebate(&self, gas_charger: &mut GasCharger) {
770 for object_id in &self.execution_results.modified_objects {
771 if self
772 .execution_results
773 .written_objects
774 .contains_key(object_id)
775 {
776 continue;
777 }
778 let storage_rebate = self
780 .get_object_modified_at(object_id)
781 .unwrap()
783 .storage_rebate;
784 gas_charger.track_storage_mutation(*object_id, 0, storage_rebate);
785 }
786 }
787
788 pub fn check_execution_results_consistency(&self) -> Result<(), ExecutionError> {
789 assert_invariant!(
790 self.execution_results
791 .created_object_ids
792 .iter()
793 .all(|id| !self.execution_results.deleted_object_ids.contains(id)
794 && !self.execution_results.modified_objects.contains(id)),
795 "Created object IDs cannot also be deleted or modified"
796 );
797 assert_invariant!(
798 self.execution_results.modified_objects.iter().all(|id| {
799 self.mutable_input_refs.contains_key(id)
800 || self.loaded_runtime_objects.contains_key(id)
801 || is_system_package(*id)
802 }),
803 "A modified object must be either a mutable input, a loaded child object, or a system package"
804 );
805 Ok(())
806 }
807}
808impl TemporaryStore<'_> {
813 pub fn advance_epoch_safe_mode(
814 &mut self,
815 params: &AdvanceEpochParams,
816 protocol_config: &ProtocolConfig,
817 ) {
818 let wrapper = get_sui_system_state_wrapper(self.store.as_object_store())
819 .expect("System state wrapper object must exist");
820 let (old_object, new_object) =
821 wrapper.advance_epoch_safe_mode(params, self.store.as_object_store(), protocol_config);
822 self.mutate_child_object(old_object, new_object);
823 }
824}
825
826type ModifiedObjectInfo<'a> = (
827 ObjectID,
828 Option<DynamicallyLoadedObjectMetadata>,
830 Option<&'a Object>,
831);
832
833impl TemporaryStore<'_> {
834 fn get_input_sui(
835 &self,
836 id: &ObjectID,
837 expected_version: SequenceNumber,
838 layout_resolver: &mut impl LayoutResolver,
839 ) -> Result<u64, ExecutionError> {
840 if let Some(obj) = self.input_objects.get(id) {
841 if obj.version() != expected_version {
843 invariant_violation!(
844 "Version mismatching when resolving input object to check conservation--\
845 expected {}, got {}",
846 expected_version,
847 obj.version(),
848 );
849 }
850 obj.get_total_sui(layout_resolver).map_err(|e| {
851 make_invariant_violation!(
852 "Failed looking up input SUI in SUI conservation checking for input with \
853 type {:?}: {e:#?}",
854 obj.struct_tag(),
855 )
856 })
857 } else {
858 let Some(obj) = self.store.get_object_by_key(id, expected_version) else {
860 invariant_violation!(
861 "Failed looking up dynamic field {id} in SUI conservation checking"
862 );
863 };
864 obj.get_total_sui(layout_resolver).map_err(|e| {
865 make_invariant_violation!(
866 "Failed looking up input SUI in SUI conservation checking for type \
867 {:?}: {e:#?}",
868 obj.struct_tag(),
869 )
870 })
871 }
872 }
873
874 fn get_modified_objects(&self) -> Vec<ModifiedObjectInfo<'_>> {
879 self.execution_results
880 .modified_objects
881 .iter()
882 .map(|id| {
883 let metadata = self.get_object_modified_at(id);
884 let output = self.execution_results.written_objects.get(id);
885 (*id, metadata, output)
886 })
887 .chain(
888 self.execution_results
889 .written_objects
890 .iter()
891 .filter_map(|(id, object)| {
892 if self.execution_results.modified_objects.contains(id) {
893 None
894 } else {
895 Some((*id, None, Some(object)))
896 }
897 }),
898 )
899 .collect()
900 }
901
902 pub fn check_sui_conserved(
916 &self,
917 simple_conservation_checks: bool,
918 gas_summary: &GasCostSummary,
919 ) -> Result<(), ExecutionError> {
920 if !simple_conservation_checks {
921 return Ok(());
922 }
923 let mut total_input_rebate = 0;
925 let mut total_output_rebate = 0;
927 for (_, input, output) in self.get_modified_objects() {
928 if let Some(input) = input {
929 total_input_rebate += input.storage_rebate;
930 }
931 if let Some(object) = output {
932 total_output_rebate += object.storage_rebate;
933 }
934 }
935
936 if gas_summary.storage_cost == 0 {
937 if total_input_rebate
949 != total_output_rebate
950 + gas_summary.storage_rebate
951 + gas_summary.non_refundable_storage_fee
952 {
953 return Err(ExecutionError::invariant_violation(format!(
954 "SUI conservation failed -- no storage charges in gas summary \
955 and total storage input rebate {} not equal \
956 to total storage output rebate {}",
957 total_input_rebate, total_output_rebate,
958 )));
959 }
960 } else {
961 if total_input_rebate
964 != gas_summary.storage_rebate + gas_summary.non_refundable_storage_fee
965 {
966 return Err(ExecutionError::invariant_violation(format!(
967 "SUI conservation failed -- {} SUI in storage rebate field of input objects, \
968 {} SUI in tx storage rebate or tx non-refundable storage rebate",
969 total_input_rebate, gas_summary.non_refundable_storage_fee,
970 )));
971 }
972
973 if gas_summary.storage_cost != total_output_rebate {
976 return Err(ExecutionError::invariant_violation(format!(
977 "SUI conservation failed -- {} SUI charged for storage, \
978 {} SUI in storage rebate field of output objects",
979 gas_summary.storage_cost, total_output_rebate
980 )));
981 }
982 }
983 Ok(())
984 }
985
986 pub fn check_sui_conserved_expensive(
999 &self,
1000 gas_summary: &GasCostSummary,
1001 advance_epoch_gas_summary: Option<(u64, u64)>,
1002 layout_resolver: &mut impl LayoutResolver,
1003 ) -> Result<(), ExecutionError> {
1004 let mut total_input_sui = 0;
1006 let mut total_output_sui = 0;
1008
1009 total_input_sui += self.execution_results.settlement_input_sui;
1013 total_output_sui += self.execution_results.settlement_output_sui;
1014
1015 for (id, input, output) in self.get_modified_objects() {
1016 if let Some(input) = input {
1017 total_input_sui += self.get_input_sui(&id, input.version, layout_resolver)?;
1018 }
1019 if let Some(object) = output {
1020 total_output_sui += object.get_total_sui(layout_resolver).map_err(|e| {
1021 make_invariant_violation!(
1022 "Failed looking up output SUI in SUI conservation checking for \
1023 mutated type {:?}: {e:#?}",
1024 object.struct_tag(),
1025 )
1026 })?;
1027 }
1028 }
1029
1030 for event in &self.execution_results.accumulator_events {
1031 let (input, output) = event.total_sui_in_event();
1032 total_input_sui += input;
1033 total_output_sui += output;
1034 }
1035
1036 total_output_sui += gas_summary.computation_cost + gas_summary.non_refundable_storage_fee;
1041 if let Some((epoch_fees, epoch_rebates)) = advance_epoch_gas_summary {
1042 total_input_sui += epoch_fees;
1043 total_output_sui += epoch_rebates;
1044 }
1045 if total_input_sui != total_output_sui {
1046 return Err(ExecutionError::invariant_violation(format!(
1047 "SUI conservation failed: input={}, output={}, \
1048 this transaction either mints or burns SUI",
1049 total_input_sui, total_output_sui,
1050 )));
1051 }
1052 Ok(())
1053 }
1054}
1055
1056impl ChildObjectResolver for TemporaryStore<'_> {
1057 fn read_child_object(
1058 &self,
1059 parent: &ObjectID,
1060 child: &ObjectID,
1061 child_version_upper_bound: SequenceNumber,
1062 ) -> SuiResult<Option<Object>> {
1063 let obj_opt = self.execution_results.written_objects.get(child);
1064 if obj_opt.is_some() {
1065 Ok(obj_opt.cloned())
1066 } else {
1067 let _scope = monitored_scope("Execution::read_child_object");
1068 self.store
1069 .read_child_object(parent, child, child_version_upper_bound)
1070 }
1071 }
1072
1073 fn get_object_received_at_version(
1074 &self,
1075 owner: &ObjectID,
1076 receiving_object_id: &ObjectID,
1077 receive_object_at_version: SequenceNumber,
1078 epoch_id: EpochId,
1079 ) -> SuiResult<Option<Object>> {
1080 debug_assert!(
1083 !self
1084 .execution_results
1085 .written_objects
1086 .contains_key(receiving_object_id)
1087 );
1088 debug_assert!(
1089 !self
1090 .execution_results
1091 .deleted_object_ids
1092 .contains(receiving_object_id)
1093 );
1094 self.store.get_object_received_at_version(
1095 owner,
1096 receiving_object_id,
1097 receive_object_at_version,
1098 epoch_id,
1099 )
1100 }
1101}
1102
1103fn was_object_mutated(object: &Object, original: &Object) -> bool {
1106 let data_equal = match (&object.data, &original.data) {
1107 (Data::Move(a), Data::Move(b)) => a.contents_and_type_equal(b),
1108 (Data::Package(a), Data::Package(b)) => a == b,
1111 _ => false,
1112 };
1113
1114 let owner_equal = match (&object.owner, &original.owner) {
1115 (Owner::Shared { .. }, Owner::Shared { .. }) => true,
1119 (
1120 Owner::ConsensusAddressOwner { owner: a, .. },
1121 Owner::ConsensusAddressOwner { owner: b, .. },
1122 ) => a == b,
1123 (Owner::AddressOwner(a), Owner::AddressOwner(b)) => a == b,
1124 (Owner::Immutable, Owner::Immutable) => true,
1125 (Owner::ObjectOwner(a), Owner::ObjectOwner(b)) => a == b,
1126
1127 (Owner::AddressOwner(_), _)
1130 | (Owner::Immutable, _)
1131 | (Owner::ObjectOwner(_), _)
1132 | (Owner::Shared { .. }, _)
1133 | (Owner::ConsensusAddressOwner { .. }, _) => false,
1134 };
1135
1136 !data_equal || !owner_equal
1137}
1138
1139impl Storage for TemporaryStore<'_> {
1140 fn reset(&mut self) {
1141 self.drop_writes();
1142 }
1143
1144 fn read_object(&self, id: &ObjectID) -> Option<&Object> {
1145 TemporaryStore::read_object(self, id)
1146 }
1147
1148 fn record_execution_results(
1150 &mut self,
1151 results: ExecutionResults,
1152 ) -> Result<(), ExecutionError> {
1153 let ExecutionResults::V2(mut results) = results else {
1154 panic!("ExecutionResults::V2 expected in sui-execution v1 and above");
1155 };
1156
1157 let mut to_remove = Vec::new();
1159 for (id, original) in &self.non_exclusive_input_original_versions {
1160 if results
1162 .written_objects
1163 .get(id)
1164 .map(|obj| was_object_mutated(obj, original))
1165 .unwrap_or(true)
1166 {
1167 return Err(ExecutionError::new_with_source(
1168 ExecutionErrorKind::NonExclusiveWriteInputObjectModified { id: *id },
1169 "Non-exclusive write input object has been modified or deleted",
1170 ));
1171 }
1172 to_remove.push(*id);
1173 }
1174
1175 for id in to_remove {
1176 results.written_objects.remove(&id);
1177 results.modified_objects.remove(&id);
1178 }
1179
1180 self.execution_results.merge_results(results);
1183
1184 Ok(())
1185 }
1186
1187 fn save_loaded_runtime_objects(
1188 &mut self,
1189 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
1190 ) {
1191 TemporaryStore::save_loaded_runtime_objects(self, loaded_runtime_objects)
1192 }
1193
1194 fn save_wrapped_object_containers(
1195 &mut self,
1196 wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
1197 ) {
1198 TemporaryStore::save_wrapped_object_containers(self, wrapped_object_containers)
1199 }
1200
1201 fn check_coin_deny_list(
1202 &self,
1203 receiving_funds_type_and_owners: BTreeMap<TypeTag, BTreeSet<SuiAddress>>,
1204 ) -> DenyListResult {
1205 let result = check_coin_deny_list_v2_during_execution(
1206 receiving_funds_type_and_owners,
1207 self.cur_epoch,
1208 self.store.as_object_store(),
1209 );
1210 if result.num_non_gas_coin_owners > 0
1213 && !self.input_objects.contains_key(&SUI_DENY_LIST_OBJECT_ID)
1214 {
1215 self.loaded_per_epoch_config_objects
1216 .write()
1217 .insert(SUI_DENY_LIST_OBJECT_ID);
1218 }
1219 result
1220 }
1221
1222 fn record_generated_object_ids(&mut self, generated_ids: BTreeSet<ObjectID>) {
1223 TemporaryStore::save_generated_object_ids(self, generated_ids)
1224 }
1225}
1226
1227impl BackingPackageStore for TemporaryStore<'_> {
1228 fn get_package_object(&self, package_id: &ObjectID) -> SuiResult<Option<PackageObject>> {
1229 if let Some(obj) = self.execution_results.written_objects.get(package_id) {
1236 Ok(Some(PackageObject::new(obj.clone())))
1237 } else {
1238 self.store.get_package_object(package_id).inspect(|obj| {
1239 if let Some(v) = obj
1241 && !self
1242 .runtime_packages_loaded_from_db
1243 .read()
1244 .contains_key(package_id)
1245 {
1246 self.runtime_packages_loaded_from_db
1251 .write()
1252 .insert(*package_id, v.clone());
1253 }
1254 })
1255 }
1256 }
1257}
1258
1259impl ParentSync for TemporaryStore<'_> {
1260 fn get_latest_parent_entry_ref_deprecated(&self, _object_id: ObjectID) -> Option<ObjectRef> {
1261 unreachable!("Never called in newer protocol versions")
1262 }
1263}