1use crate::gas_charger::{GasCharger, PaymentLocation};
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::accumulator_root::AccumulatorObjId;
11use sui_types::base_types::VersionDigest;
12use sui_types::committee::EpochId;
13use sui_types::deny_list_v2::check_coin_deny_list_v2_during_execution;
14use sui_types::effects::{
15 AccumulatorOperation, AccumulatorValue, AccumulatorWriteV1, TransactionEffects,
16 TransactionEvents,
17};
18use sui_types::execution::{
19 DynamicallyLoadedObjectMetadata, ExecutionResults, ExecutionResultsV2, SharedInput,
20};
21use sui_types::execution_status::{ExecutionErrorKind, ExecutionStatus};
22use sui_types::inner_temporary_store::InnerTemporaryStore;
23use sui_types::layout_resolver::LayoutResolver;
24use sui_types::object::Data;
25use sui_types::storage::{BackingStore, DenyListResult, PackageObject};
26use sui_types::sui_system_state::{AdvanceEpochParams, get_sui_system_state_wrapper};
27use sui_types::{
28 SUI_DENY_LIST_OBJECT_ID,
29 base_types::{ObjectID, ObjectRef, SequenceNumber, SuiAddress, TransactionDigest},
30 effects::EffectsObjectChange,
31 error::{ExecutionError, SuiResult},
32 gas::GasCostSummary,
33 object::Object,
34 object::Owner,
35 storage::{BackingPackageStore, ChildObjectResolver, ParentSync, Storage},
36 transaction::InputObjects,
37};
38use sui_types::{SUI_SYSTEM_STATE_OBJECT_ID, TypeTag, is_system_package};
39
40pub struct TemporaryStore<'backing> {
41 store: &'backing dyn BackingStore,
47 tx_digest: TransactionDigest,
48 input_objects: BTreeMap<ObjectID, Object>,
49
50 non_exclusive_input_original_versions: BTreeMap<ObjectID, Object>,
53
54 stream_ended_consensus_objects: BTreeMap<ObjectID, SequenceNumber >,
55 lamport_timestamp: SequenceNumber,
57 mutable_input_refs: BTreeMap<ObjectID, (VersionDigest, Owner)>,
60 execution_results: ExecutionResultsV2,
61 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
63 wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
65 protocol_config: &'backing ProtocolConfig,
66
67 runtime_packages_loaded_from_db: RwLock<BTreeMap<ObjectID, PackageObject>>,
70
71 receiving_objects: Vec<ObjectRef>,
74
75 generated_runtime_ids: BTreeSet<ObjectID>,
79
80 cur_epoch: EpochId,
83
84 loaded_per_epoch_config_objects: RwLock<BTreeSet<ObjectID>>,
87}
88
89impl<'backing> TemporaryStore<'backing> {
90 pub fn new(
93 store: &'backing dyn BackingStore,
94 input_objects: InputObjects,
95 receiving_objects: Vec<ObjectRef>,
96 tx_digest: TransactionDigest,
97 protocol_config: &'backing ProtocolConfig,
98 cur_epoch: EpochId,
99 ) -> Self {
100 let mutable_input_refs = input_objects.exclusive_mutable_inputs();
101 let non_exclusive_input_original_versions = input_objects.non_exclusive_input_objects();
102
103 let lamport_timestamp = input_objects.lamport_timestamp(&receiving_objects);
104 let stream_ended_consensus_objects = input_objects.consensus_stream_ended_objects();
105 let objects = input_objects.into_object_map();
106 #[cfg(debug_assertions)]
107 {
108 assert!(
110 objects
111 .keys()
112 .collect::<HashSet<_>>()
113 .intersection(
114 &receiving_objects
115 .iter()
116 .map(|oref| &oref.0)
117 .collect::<HashSet<_>>()
118 )
119 .next()
120 .is_none()
121 );
122 }
123 Self {
124 store,
125 tx_digest,
126 input_objects: objects,
127 non_exclusive_input_original_versions,
128 stream_ended_consensus_objects,
129 lamport_timestamp,
130 mutable_input_refs,
131 execution_results: ExecutionResultsV2::default(),
132 protocol_config,
133 loaded_runtime_objects: BTreeMap::new(),
134 wrapped_object_containers: BTreeMap::new(),
135 runtime_packages_loaded_from_db: RwLock::new(BTreeMap::new()),
136 receiving_objects,
137 generated_runtime_ids: BTreeSet::new(),
138 cur_epoch,
139 loaded_per_epoch_config_objects: RwLock::new(BTreeSet::new()),
140 }
141 }
142
143 pub fn objects(&self) -> &BTreeMap<ObjectID, Object> {
145 &self.input_objects
146 }
147
148 pub fn update_object_version_and_prev_tx(&mut self) {
149 self.execution_results.update_version_and_previous_tx(
150 self.lamport_timestamp,
151 self.tx_digest,
152 &self.input_objects,
153 self.protocol_config.reshare_at_same_initial_version(),
154 );
155
156 #[cfg(debug_assertions)]
157 {
158 self.check_invariants();
159 }
160 }
161
162 fn calculate_accumulator_running_max_withdraws(&self) -> BTreeMap<AccumulatorObjId, u128> {
163 let mut running_net_withdraws: BTreeMap<AccumulatorObjId, i128> = BTreeMap::new();
164 let mut running_max_withdraws: BTreeMap<AccumulatorObjId, u128> = BTreeMap::new();
165 for event in &self.execution_results.accumulator_events {
166 match &event.write.value {
167 AccumulatorValue::Integer(amount) => match event.write.operation {
168 AccumulatorOperation::Split => {
169 let entry = running_net_withdraws
170 .entry(event.accumulator_obj)
171 .or_default();
172 *entry += *amount as i128;
173 if *entry > 0 {
174 let max_entry = running_max_withdraws
175 .entry(event.accumulator_obj)
176 .or_default();
177 *max_entry = (*max_entry).max(*entry as u128);
178 }
179 }
180 AccumulatorOperation::Merge => {
181 let entry = running_net_withdraws
182 .entry(event.accumulator_obj)
183 .or_default();
184 *entry -= *amount as i128;
185 }
186 },
187 AccumulatorValue::IntegerTuple(_, _) | AccumulatorValue::EventDigest(_) => {}
188 }
189 }
190 running_max_withdraws
191 }
192
193 fn merge_accumulator_events(&mut self) {
195 self.execution_results.accumulator_events = self
196 .execution_results
197 .accumulator_events
198 .iter()
199 .fold(
200 BTreeMap::<AccumulatorObjId, Vec<AccumulatorWriteV1>>::new(),
201 |mut map, event| {
202 map.entry(event.accumulator_obj)
203 .or_default()
204 .push(event.write.clone());
205 map
206 },
207 )
208 .into_iter()
209 .map(|(obj_id, writes)| {
210 AccumulatorEvent::new(obj_id, AccumulatorWriteV1::merge(writes))
211 })
212 .collect();
213 }
214
215 pub fn into_inner(
217 self,
218 accumulator_running_max_withdraws: BTreeMap<AccumulatorObjId, u128>,
219 ) -> InnerTemporaryStore {
220 let results = self.execution_results;
221 InnerTemporaryStore {
222 input_objects: self.input_objects,
223 stream_ended_consensus_objects: self.stream_ended_consensus_objects,
224 mutable_inputs: self.mutable_input_refs,
225 written: results.written_objects,
226 events: TransactionEvents {
227 data: results.user_events,
228 },
229 accumulator_events: results.accumulator_events,
230 loaded_runtime_objects: self.loaded_runtime_objects,
231 runtime_packages_loaded_from_db: self.runtime_packages_loaded_from_db.into_inner(),
232 lamport_version: self.lamport_timestamp,
233 binary_config: self.protocol_config.binary_config(None),
234 accumulator_running_max_withdraws,
235 }
236 }
237
238 pub(crate) fn ensure_active_inputs_mutated(&mut self) {
242 let mut to_be_updated = vec![];
243 for id in self.mutable_input_refs.keys() {
245 if !self.execution_results.modified_objects.contains(id) {
246 to_be_updated.push(self.input_objects[id].clone());
250 }
251 }
252 for object in to_be_updated {
253 self.mutate_input_object(object.clone());
255 }
256 }
257
258 fn get_object_changes(&self) -> BTreeMap<ObjectID, EffectsObjectChange> {
259 let results = &self.execution_results;
260 let all_ids = results
261 .created_object_ids
262 .iter()
263 .chain(&results.deleted_object_ids)
264 .chain(&results.modified_objects)
265 .chain(results.written_objects.keys())
266 .collect::<BTreeSet<_>>();
267 all_ids
268 .into_iter()
269 .map(|id| {
270 (
271 *id,
272 EffectsObjectChange::new(
273 self.get_object_modified_at(id)
274 .map(|metadata| ((metadata.version, metadata.digest), metadata.owner)),
275 results.written_objects.get(id),
276 results.created_object_ids.contains(id),
277 results.deleted_object_ids.contains(id),
278 ),
279 )
280 })
281 .chain(results.accumulator_events.iter().cloned().map(
282 |AccumulatorEvent {
283 accumulator_obj,
284 write,
285 }| {
286 (
287 *accumulator_obj.inner(),
288 EffectsObjectChange::new_from_accumulator_write(write),
289 )
290 },
291 ))
292 .collect()
293 }
294
295 pub fn into_effects(
296 mut self,
297 shared_object_refs: Vec<SharedInput>,
298 transaction_digest: &TransactionDigest,
299 mut transaction_dependencies: BTreeSet<TransactionDigest>,
300 gas_cost_summary: GasCostSummary,
301 status: ExecutionStatus,
302 gas_charger: &mut GasCharger,
303 epoch: EpochId,
304 ) -> (InnerTemporaryStore, TransactionEffects) {
305 self.update_object_version_and_prev_tx();
306 let accumulator_running_max_withdraws = self.calculate_accumulator_running_max_withdraws();
308 self.merge_accumulator_events();
309
310 for (id, expected_version, expected_digest) in &self.receiving_objects {
313 if let Some(obj_meta) = self.loaded_runtime_objects.get(id) {
317 let loaded_via_receive = obj_meta.version == *expected_version
321 && obj_meta.digest == *expected_digest
322 && obj_meta.owner.is_address_owned();
323 if loaded_via_receive {
324 transaction_dependencies.insert(obj_meta.previous_transaction);
325 }
326 }
327 }
328
329 assert!(self.protocol_config.enable_effects_v2());
330
331 let gas_coin = gas_charger
336 .gas_payment_amount()
337 .and_then(|gp| match gp.location {
338 PaymentLocation::Coin(coin_id) => Some(coin_id),
339 PaymentLocation::AddressBalance(_) => None,
340 });
341
342 let object_changes = self.get_object_changes();
343
344 let lamport_version = self.lamport_timestamp;
345 let loaded_per_epoch_config_objects = self.loaded_per_epoch_config_objects.read().clone();
347 let inner = self.into_inner(accumulator_running_max_withdraws);
348
349 let effects = TransactionEffects::new_from_execution_v2(
350 status,
351 epoch,
352 gas_cost_summary,
353 shared_object_refs,
355 loaded_per_epoch_config_objects,
356 *transaction_digest,
357 lamport_version,
358 object_changes,
359 gas_coin,
360 if inner.events.data.is_empty() {
361 None
362 } else {
363 Some(inner.events.digest())
364 },
365 transaction_dependencies.into_iter().collect(),
366 );
367
368 (inner, effects)
369 }
370
371 #[cfg(debug_assertions)]
373 fn check_invariants(&self) {
374 debug_assert!(
376 {
377 self.execution_results
378 .written_objects
379 .keys()
380 .all(|id| !self.execution_results.deleted_object_ids.contains(id))
381 },
382 "Object both written and deleted."
383 );
384
385 debug_assert!(
387 {
388 self.mutable_input_refs
389 .keys()
390 .all(|id| self.execution_results.modified_objects.contains(id))
391 },
392 "Mutable input not modified."
393 );
394
395 debug_assert!(
396 {
397 self.execution_results
398 .written_objects
399 .values()
400 .all(|obj| obj.previous_transaction == self.tx_digest)
401 },
402 "Object previous transaction not properly set",
403 );
404 }
405
406 pub fn mutate_input_object(&mut self, object: Object) {
408 let id = object.id();
409 debug_assert!(self.input_objects.contains_key(&id));
410 debug_assert!(!object.is_immutable());
411 self.execution_results.modified_objects.insert(id);
412 self.execution_results.written_objects.insert(id, object);
413 }
414
415 pub fn mutate_new_or_input_object(&mut self, object: Object) {
416 let id = object.id();
417 debug_assert!(!object.is_immutable());
418 if self.input_objects.contains_key(&id) {
419 self.execution_results.modified_objects.insert(id);
420 }
421 self.execution_results.written_objects.insert(id, object);
422 }
423
424 pub fn mutate_child_object(&mut self, old_object: Object, new_object: Object) {
428 let id = new_object.id();
429 let old_ref = old_object.compute_object_reference();
430 debug_assert_eq!(old_ref.0, id);
431 self.loaded_runtime_objects.insert(
432 id,
433 DynamicallyLoadedObjectMetadata {
434 version: old_ref.1,
435 digest: old_ref.2,
436 owner: old_object.owner.clone(),
437 storage_rebate: old_object.storage_rebate,
438 previous_transaction: old_object.previous_transaction,
439 },
440 );
441 self.execution_results.modified_objects.insert(id);
442 self.execution_results
443 .written_objects
444 .insert(id, new_object);
445 }
446
447 pub fn upgrade_system_package(&mut self, package: Object) {
451 let id = package.id();
452 assert!(package.is_package() && is_system_package(id));
453 self.execution_results.modified_objects.insert(id);
454 self.execution_results.written_objects.insert(id, package);
455 }
456
457 pub fn create_object(&mut self, object: Object) {
459 debug_assert!(
464 object.is_immutable() || object.version() == SequenceNumber::MIN,
465 "Created mutable objects should not have a version set",
466 );
467 let id = object.id();
468 self.execution_results.created_object_ids.insert(id);
469 self.execution_results.written_objects.insert(id, object);
470 }
471
472 pub fn delete_input_object(&mut self, id: &ObjectID) {
474 debug_assert!(!self.execution_results.written_objects.contains_key(id));
476 debug_assert!(self.input_objects.contains_key(id));
477 self.execution_results.modified_objects.insert(*id);
478 self.execution_results.deleted_object_ids.insert(*id);
479 }
480
481 pub fn drop_writes(&mut self) {
482 self.execution_results.drop_writes();
483 }
484
485 pub fn read_object(&self, id: &ObjectID) -> Option<&Object> {
486 debug_assert!(!self.execution_results.deleted_object_ids.contains(id));
488 self.execution_results
489 .written_objects
490 .get(id)
491 .or_else(|| self.input_objects.get(id))
492 }
493
494 pub fn save_loaded_runtime_objects(
495 &mut self,
496 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
497 ) {
498 #[cfg(debug_assertions)]
499 {
500 for (id, v1) in &loaded_runtime_objects {
501 if let Some(v2) = self.loaded_runtime_objects.get(id) {
502 assert_eq!(v1, v2);
503 }
504 }
505 for (id, v1) in &self.loaded_runtime_objects {
506 if let Some(v2) = loaded_runtime_objects.get(id) {
507 assert_eq!(v1, v2);
508 }
509 }
510 }
511 self.loaded_runtime_objects.extend(loaded_runtime_objects);
514 }
515
516 pub fn save_wrapped_object_containers(
517 &mut self,
518 wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
519 ) {
520 #[cfg(debug_assertions)]
521 {
522 for (id, container1) in &wrapped_object_containers {
523 if let Some(container2) = self.wrapped_object_containers.get(id) {
524 assert_eq!(container1, container2);
525 }
526 }
527 for (id, container1) in &self.wrapped_object_containers {
528 if let Some(container2) = wrapped_object_containers.get(id) {
529 assert_eq!(container1, container2);
530 }
531 }
532 }
533 self.wrapped_object_containers
536 .extend(wrapped_object_containers);
537 }
538
539 pub fn save_generated_object_ids(&mut self, generated_ids: BTreeSet<ObjectID>) {
540 #[cfg(debug_assertions)]
541 {
542 for id in &self.generated_runtime_ids {
543 assert!(!generated_ids.contains(id))
544 }
545 for id in &generated_ids {
546 assert!(!self.generated_runtime_ids.contains(id));
547 }
548 }
549 self.generated_runtime_ids.extend(generated_ids);
550 }
551
552 pub fn estimate_effects_size_upperbound(&self) -> usize {
553 TransactionEffects::estimate_effects_size_upperbound_v2(
554 self.execution_results.written_objects.len(),
555 self.execution_results.modified_objects.len(),
556 self.input_objects.len(),
557 )
558 }
559
560 pub fn written_objects_size(&self) -> usize {
561 self.execution_results
562 .written_objects
563 .values()
564 .fold(0, |sum, obj| sum + obj.object_size_for_gas_metering())
565 }
566
567 pub fn conserve_unmetered_storage_rebate(&mut self, unmetered_storage_rebate: u64) {
572 if unmetered_storage_rebate == 0 {
573 return;
577 }
578 tracing::debug!(
579 "Amount of unmetered storage rebate from system tx: {:?}",
580 unmetered_storage_rebate
581 );
582 let mut system_state_wrapper = self
583 .read_object(&SUI_SYSTEM_STATE_OBJECT_ID)
584 .expect("0x5 object must be mutated in system tx with unmetered storage rebate")
585 .clone();
586 assert_eq!(system_state_wrapper.storage_rebate, 0);
589 system_state_wrapper.storage_rebate = unmetered_storage_rebate;
590 self.mutate_input_object(system_state_wrapper);
591 }
592
593 pub fn add_accumulator_event(&mut self, event: AccumulatorEvent) {
595 self.execution_results.accumulator_events.push(event);
596 }
597
598 fn get_object_modified_at(
604 &self,
605 object_id: &ObjectID,
606 ) -> Option<DynamicallyLoadedObjectMetadata> {
607 if self.execution_results.modified_objects.contains(object_id) {
608 Some(
609 self.mutable_input_refs
610 .get(object_id)
611 .map(
612 |((version, digest), owner)| DynamicallyLoadedObjectMetadata {
613 version: *version,
614 digest: *digest,
615 owner: owner.clone(),
616 storage_rebate: self.input_objects[object_id].storage_rebate,
618 previous_transaction: self.input_objects[object_id]
619 .previous_transaction,
620 },
621 )
622 .or_else(|| self.loaded_runtime_objects.get(object_id).cloned())
623 .unwrap_or_else(|| {
624 debug_assert!(is_system_package(*object_id));
625 let package_obj =
626 self.store.get_package_object(object_id).unwrap().unwrap();
627 let obj = package_obj.object();
628 DynamicallyLoadedObjectMetadata {
629 version: obj.version(),
630 digest: obj.digest(),
631 owner: obj.owner.clone(),
632 storage_rebate: obj.storage_rebate,
633 previous_transaction: obj.previous_transaction,
634 }
635 }),
636 )
637 } else {
638 None
639 }
640 }
641}
642
643impl TemporaryStore<'_> {
644 pub fn check_ownership_invariants(
647 &self,
648 sender: &SuiAddress,
649 sponsor: &Option<SuiAddress>,
650 gas_charger: &mut GasCharger,
651 mutable_inputs: &HashSet<ObjectID>,
652 is_epoch_change: bool,
653 ) -> SuiResult<()> {
654 let gas_objs: HashSet<&ObjectID> = gas_charger.used_coins().map(|g| &g.0).collect();
655 let gas_owner = sponsor.as_ref().unwrap_or(sender);
656
657 let mut authenticated_for_mutation: HashSet<_> = self
659 .input_objects
660 .iter()
661 .filter_map(|(id, obj)| {
662 match &obj.owner {
663 Owner::AddressOwner(a) => {
664 if gas_objs.contains(id) {
665 assert!(
667 a == gas_owner,
668 "Gas object must be owned by sender or sponsor"
669 );
670 } else {
671 assert!(sender == a, "Input object must be owned by sender");
672 }
673 Some(id)
674 }
675 Owner::Shared { .. } | Owner::ConsensusAddressOwner { .. } => Some(id),
676 Owner::Immutable => {
677 None
688 }
689 Owner::ObjectOwner(_parent) => {
690 unreachable!(
691 "Input objects must be address owned, shared, consensus, or immutable"
692 )
693 }
694 }
695 })
696 .filter(|id| {
697 mutable_inputs.contains(id)
700 })
701 .copied()
702 .chain(self.generated_runtime_ids.iter().copied())
705 .collect();
706
707 authenticated_for_mutation.insert((*sender).into());
709 if let Some(sponsor) = sponsor {
710 authenticated_for_mutation.insert((*sponsor).into());
711 }
712
713 let mut objects_to_authenticate = self
715 .execution_results
716 .modified_objects
717 .iter()
718 .copied()
719 .collect::<Vec<_>>();
720
721 while let Some(to_authenticate) = objects_to_authenticate.pop() {
722 if authenticated_for_mutation.contains(&to_authenticate) {
723 continue;
725 }
726
727 let parent = if let Some(container_id) =
728 self.wrapped_object_containers.get(&to_authenticate)
729 {
730 *container_id
732 } else {
733 let Some(old_obj) = self.store.get_object(&to_authenticate) else {
736 panic!(
737 "Failed to load object {to_authenticate:?}.\n \
738 If it cannot be loaded, we would expect it to be in the wrapped object map: {:#?}",
739 &self.wrapped_object_containers
740 )
741 };
742
743 match &old_obj.owner {
744 Owner::ObjectOwner(parent) => ObjectID::from(*parent),
747 Owner::AddressOwner(parent)
752 | Owner::ConsensusAddressOwner { owner: parent, .. } => {
753 ObjectID::from(*parent)
757 }
758 owner @ Owner::Shared { .. } => {
761 panic!(
762 "Unauthenticated root at {to_authenticate:?} with owner {owner:?}\n\
763 Potentially covering objects in: {authenticated_for_mutation:#?}"
764 );
765 }
766 Owner::Immutable => {
767 assert!(
768 is_epoch_change,
769 "Immutable objects cannot be written, except for \
770 Sui Framework/Move stdlib upgrades at epoch change boundaries"
771 );
772 assert!(
776 is_system_package(to_authenticate),
777 "Only system packages can be upgraded"
778 );
779 continue;
780 }
781 }
782 };
783
784 authenticated_for_mutation.insert(to_authenticate);
786 objects_to_authenticate.push(parent);
787 }
788 Ok(())
789 }
790}
791
792impl TemporaryStore<'_> {
793 pub(crate) fn collect_storage_and_rebate(&mut self, gas_charger: &mut GasCharger) {
800 let old_storage_rebates: Vec<_> = self
802 .execution_results
803 .written_objects
804 .keys()
805 .map(|object_id| {
806 self.get_object_modified_at(object_id)
807 .map(|metadata| metadata.storage_rebate)
808 .unwrap_or_default()
809 })
810 .collect();
811 for (object, old_storage_rebate) in self
812 .execution_results
813 .written_objects
814 .values_mut()
815 .zip(old_storage_rebates)
816 {
817 let new_object_size = object.object_size_for_gas_metering();
819 let new_storage_rebate = gas_charger.track_storage_mutation(
821 object.id(),
822 new_object_size,
823 old_storage_rebate,
824 );
825 object.storage_rebate = new_storage_rebate;
826 }
827
828 self.collect_rebate(gas_charger);
829 }
830
831 pub(crate) fn collect_rebate(&self, gas_charger: &mut GasCharger) {
832 for object_id in &self.execution_results.modified_objects {
833 if self
834 .execution_results
835 .written_objects
836 .contains_key(object_id)
837 {
838 continue;
839 }
840 let storage_rebate = self
842 .get_object_modified_at(object_id)
843 .unwrap()
845 .storage_rebate;
846 gas_charger.track_storage_mutation(*object_id, 0, storage_rebate);
847 }
848 }
849
850 pub fn check_execution_results_consistency(&self) -> Result<(), ExecutionError> {
851 assert_invariant!(
852 self.execution_results
853 .created_object_ids
854 .iter()
855 .all(|id| !self.execution_results.deleted_object_ids.contains(id)
856 && !self.execution_results.modified_objects.contains(id)),
857 "Created object IDs cannot also be deleted or modified"
858 );
859 assert_invariant!(
860 self.execution_results.modified_objects.iter().all(|id| {
861 self.mutable_input_refs.contains_key(id)
862 || self.loaded_runtime_objects.contains_key(id)
863 || is_system_package(*id)
864 }),
865 "A modified object must be either a mutable input, a loaded child object, or a system package"
866 );
867 Ok(())
868 }
869}
870impl TemporaryStore<'_> {
875 pub fn advance_epoch_safe_mode(
876 &mut self,
877 params: &AdvanceEpochParams,
878 protocol_config: &ProtocolConfig,
879 ) {
880 let wrapper = get_sui_system_state_wrapper(self.store.as_object_store())
881 .expect("System state wrapper object must exist");
882 let (old_object, new_object) =
883 wrapper.advance_epoch_safe_mode(params, self.store.as_object_store(), protocol_config);
884 self.mutate_child_object(old_object, new_object);
885 }
886}
887
888type ModifiedObjectInfo<'a> = (
889 ObjectID,
890 Option<DynamicallyLoadedObjectMetadata>,
892 Option<&'a Object>,
893);
894
895impl TemporaryStore<'_> {
896 fn get_input_sui(
897 &self,
898 id: &ObjectID,
899 expected_version: SequenceNumber,
900 layout_resolver: &mut impl LayoutResolver,
901 ) -> Result<u64, ExecutionError> {
902 if let Some(obj) = self.input_objects.get(id) {
903 if obj.version() != expected_version {
905 invariant_violation!(
906 "Version mismatching when resolving input object to check conservation--\
907 expected {}, got {}",
908 expected_version,
909 obj.version(),
910 );
911 }
912 obj.get_total_sui(layout_resolver).map_err(|e| {
913 make_invariant_violation!(
914 "Failed looking up input SUI in SUI conservation checking for input with \
915 type {:?}: {e:#?}",
916 obj.struct_tag(),
917 )
918 })
919 } else {
920 let Some(obj) = self.store.get_object_by_key(id, expected_version) else {
922 invariant_violation!(
923 "Failed looking up dynamic field {id} in SUI conservation checking"
924 );
925 };
926 obj.get_total_sui(layout_resolver).map_err(|e| {
927 make_invariant_violation!(
928 "Failed looking up input SUI in SUI conservation checking for type \
929 {:?}: {e:#?}",
930 obj.struct_tag(),
931 )
932 })
933 }
934 }
935
936 fn get_modified_objects(&self) -> Vec<ModifiedObjectInfo<'_>> {
941 self.execution_results
942 .modified_objects
943 .iter()
944 .map(|id| {
945 let metadata = self.get_object_modified_at(id);
946 let output = self.execution_results.written_objects.get(id);
947 (*id, metadata, output)
948 })
949 .chain(
950 self.execution_results
951 .written_objects
952 .iter()
953 .filter_map(|(id, object)| {
954 if self.execution_results.modified_objects.contains(id) {
955 None
956 } else {
957 Some((*id, None, Some(object)))
958 }
959 }),
960 )
961 .collect()
962 }
963
964 pub fn check_sui_conserved(
978 &self,
979 simple_conservation_checks: bool,
980 gas_summary: &GasCostSummary,
981 ) -> Result<(), ExecutionError> {
982 if !simple_conservation_checks {
983 return Ok(());
984 }
985 let mut total_input_rebate = 0;
987 let mut total_output_rebate = 0;
989 for (_id, input, output) in self.get_modified_objects() {
990 if let Some(input) = input {
991 total_input_rebate += input.storage_rebate;
992 }
993 if let Some(object) = output {
994 total_output_rebate += object.storage_rebate;
995 }
996 }
997
998 if gas_summary.storage_cost == 0 {
999 if total_input_rebate
1011 != total_output_rebate
1012 + gas_summary.storage_rebate
1013 + gas_summary.non_refundable_storage_fee
1014 {
1015 return Err(ExecutionError::invariant_violation(format!(
1016 "SUI conservation failed -- no storage charges in gas summary \
1017 and total storage input rebate {} not equal \
1018 to total storage output rebate {}",
1019 total_input_rebate, total_output_rebate,
1020 )));
1021 }
1022 } else {
1023 if total_input_rebate
1026 != gas_summary.storage_rebate + gas_summary.non_refundable_storage_fee
1027 {
1028 return Err(ExecutionError::invariant_violation(format!(
1029 "SUI conservation failed -- {} SUI in storage rebate field of input objects, \
1030 {} SUI in tx storage rebate or tx non-refundable storage rebate",
1031 total_input_rebate, gas_summary.non_refundable_storage_fee,
1032 )));
1033 }
1034
1035 if gas_summary.storage_cost != total_output_rebate {
1038 return Err(ExecutionError::invariant_violation(format!(
1039 "SUI conservation failed -- {} SUI charged for storage, \
1040 {} SUI in storage rebate field of output objects",
1041 gas_summary.storage_cost, total_output_rebate
1042 )));
1043 }
1044 }
1045 Ok(())
1046 }
1047
1048 pub fn check_sui_conserved_expensive(
1061 &self,
1062 gas_summary: &GasCostSummary,
1063 advance_epoch_gas_summary: Option<(u64, u64)>,
1064 layout_resolver: &mut impl LayoutResolver,
1065 ) -> Result<(), ExecutionError> {
1066 let mut total_input_sui = 0;
1068 let mut total_output_sui = 0;
1070
1071 total_input_sui += self.execution_results.settlement_input_sui;
1075 total_output_sui += self.execution_results.settlement_output_sui;
1076
1077 for (id, input, output) in self.get_modified_objects() {
1078 if let Some(input) = input {
1079 total_input_sui += self.get_input_sui(&id, input.version, layout_resolver)?;
1080 }
1081 if let Some(object) = output {
1082 total_output_sui += object.get_total_sui(layout_resolver).map_err(|e| {
1083 make_invariant_violation!(
1084 "Failed looking up output SUI in SUI conservation checking for \
1085 mutated type {:?}: {e:#?}",
1086 object.struct_tag(),
1087 )
1088 })?;
1089 }
1090 }
1091
1092 for event in &self.execution_results.accumulator_events {
1093 let (input, output) = event.total_sui_in_event();
1094 total_input_sui += input;
1095 total_output_sui += output;
1096 }
1097
1098 total_output_sui += gas_summary.computation_cost + gas_summary.non_refundable_storage_fee;
1103 if let Some((epoch_fees, epoch_rebates)) = advance_epoch_gas_summary {
1104 total_input_sui += epoch_fees;
1105 total_output_sui += epoch_rebates;
1106 }
1107 if total_input_sui != total_output_sui {
1108 return Err(ExecutionError::invariant_violation(format!(
1109 "SUI conservation failed: input={}, output={}, \
1110 this transaction either mints or burns SUI",
1111 total_input_sui, total_output_sui,
1112 )));
1113 }
1114 Ok(())
1115 }
1116}
1117
1118impl ChildObjectResolver for TemporaryStore<'_> {
1119 fn read_child_object(
1120 &self,
1121 parent: &ObjectID,
1122 child: &ObjectID,
1123 child_version_upper_bound: SequenceNumber,
1124 ) -> SuiResult<Option<Object>> {
1125 let obj_opt = self.execution_results.written_objects.get(child);
1126 if obj_opt.is_some() {
1127 Ok(obj_opt.cloned())
1128 } else {
1129 let _scope = monitored_scope("Execution::read_child_object");
1130 self.store
1131 .read_child_object(parent, child, child_version_upper_bound)
1132 }
1133 }
1134
1135 fn get_object_received_at_version(
1136 &self,
1137 owner: &ObjectID,
1138 receiving_object_id: &ObjectID,
1139 receive_object_at_version: SequenceNumber,
1140 epoch_id: EpochId,
1141 ) -> SuiResult<Option<Object>> {
1142 debug_assert!(
1145 !self
1146 .execution_results
1147 .written_objects
1148 .contains_key(receiving_object_id)
1149 );
1150 debug_assert!(
1151 !self
1152 .execution_results
1153 .deleted_object_ids
1154 .contains(receiving_object_id)
1155 );
1156 self.store.get_object_received_at_version(
1157 owner,
1158 receiving_object_id,
1159 receive_object_at_version,
1160 epoch_id,
1161 )
1162 }
1163}
1164
1165fn was_object_mutated(object: &Object, original: &Object) -> bool {
1168 let data_equal = match (&object.data, &original.data) {
1169 (Data::Move(a), Data::Move(b)) => a.contents_and_type_equal(b),
1170 (Data::Package(a), Data::Package(b)) => a == b,
1173 _ => false,
1174 };
1175
1176 let owner_equal = match (&object.owner, &original.owner) {
1177 (Owner::Shared { .. }, Owner::Shared { .. }) => true,
1181 (
1182 Owner::ConsensusAddressOwner { owner: a, .. },
1183 Owner::ConsensusAddressOwner { owner: b, .. },
1184 ) => a == b,
1185 (Owner::AddressOwner(a), Owner::AddressOwner(b)) => a == b,
1186 (Owner::Immutable, Owner::Immutable) => true,
1187 (Owner::ObjectOwner(a), Owner::ObjectOwner(b)) => a == b,
1188
1189 (Owner::AddressOwner(_), _)
1192 | (Owner::Immutable, _)
1193 | (Owner::ObjectOwner(_), _)
1194 | (Owner::Shared { .. }, _)
1195 | (Owner::ConsensusAddressOwner { .. }, _) => false,
1196 };
1197
1198 !data_equal || !owner_equal
1199}
1200
1201impl Storage for TemporaryStore<'_> {
1202 fn reset(&mut self) {
1203 self.drop_writes();
1204 }
1205
1206 fn read_object(&self, id: &ObjectID) -> Option<&Object> {
1207 TemporaryStore::read_object(self, id)
1208 }
1209
1210 fn record_execution_results(
1212 &mut self,
1213 results: ExecutionResults,
1214 ) -> Result<(), ExecutionError> {
1215 let ExecutionResults::V2(mut results) = results else {
1216 panic!("ExecutionResults::V2 expected in sui-execution v1 and above");
1217 };
1218
1219 let mut to_remove = Vec::new();
1221 for (id, original) in &self.non_exclusive_input_original_versions {
1222 if results
1224 .written_objects
1225 .get(id)
1226 .map(|obj| was_object_mutated(obj, original))
1227 .unwrap_or(true)
1228 {
1229 return Err(ExecutionError::new_with_source(
1230 ExecutionErrorKind::NonExclusiveWriteInputObjectModified { id: *id },
1231 "Non-exclusive write input object has been modified or deleted",
1232 ));
1233 }
1234 to_remove.push(*id);
1235 }
1236
1237 for id in to_remove {
1238 results.written_objects.remove(&id);
1239 results.modified_objects.remove(&id);
1240 }
1241
1242 self.execution_results.merge_results(results);
1245
1246 Ok(())
1247 }
1248
1249 fn save_loaded_runtime_objects(
1250 &mut self,
1251 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
1252 ) {
1253 TemporaryStore::save_loaded_runtime_objects(self, loaded_runtime_objects)
1254 }
1255
1256 fn save_wrapped_object_containers(
1257 &mut self,
1258 wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
1259 ) {
1260 TemporaryStore::save_wrapped_object_containers(self, wrapped_object_containers)
1261 }
1262
1263 fn check_coin_deny_list(
1264 &self,
1265 receiving_funds_type_and_owners: BTreeMap<TypeTag, BTreeSet<SuiAddress>>,
1266 ) -> DenyListResult {
1267 let result = check_coin_deny_list_v2_during_execution(
1268 receiving_funds_type_and_owners,
1269 self.cur_epoch,
1270 self.store.as_object_store(),
1271 );
1272 if result.num_non_gas_coin_owners > 0
1275 && !self.input_objects.contains_key(&SUI_DENY_LIST_OBJECT_ID)
1276 {
1277 self.loaded_per_epoch_config_objects
1278 .write()
1279 .insert(SUI_DENY_LIST_OBJECT_ID);
1280 }
1281 result
1282 }
1283
1284 fn record_generated_object_ids(&mut self, generated_ids: BTreeSet<ObjectID>) {
1285 TemporaryStore::save_generated_object_ids(self, generated_ids)
1286 }
1287}
1288
1289impl BackingPackageStore for TemporaryStore<'_> {
1290 fn get_package_object(&self, package_id: &ObjectID) -> SuiResult<Option<PackageObject>> {
1291 if let Some(obj) = self.execution_results.written_objects.get(package_id) {
1298 Ok(Some(PackageObject::new(obj.clone())))
1299 } else {
1300 self.store.get_package_object(package_id).inspect(|obj| {
1301 if let Some(v) = obj
1303 && !self
1304 .runtime_packages_loaded_from_db
1305 .read()
1306 .contains_key(package_id)
1307 {
1308 self.runtime_packages_loaded_from_db
1313 .write()
1314 .insert(*package_id, v.clone());
1315 }
1316 })
1317 }
1318 }
1319}
1320
1321impl ParentSync for TemporaryStore<'_> {
1322 fn get_latest_parent_entry_ref_deprecated(&self, _object_id: ObjectID) -> Option<ObjectRef> {
1323 unreachable!("Never called in newer protocol versions")
1324 }
1325}