1use crate::gas_charger::GasCharger;
5use parking_lot::RwLock;
6use std::collections::{BTreeMap, BTreeSet, HashSet};
7use sui_protocol_config::ProtocolConfig;
8use sui_types::base_types::VersionDigest;
9use sui_types::committee::EpochId;
10use sui_types::digests::ObjectDigest;
11use sui_types::effects::{TransactionEffects, TransactionEffectsV2, TransactionEvents};
12use sui_types::execution::{
13 DynamicallyLoadedObjectMetadata, ExecutionResults, ExecutionResultsV2, SharedInput,
14};
15use sui_types::execution_status::ExecutionStatus;
16use sui_types::inner_temporary_store::InnerTemporaryStore;
17use sui_types::layout_resolver::LayoutResolver;
18use sui_types::storage::{BackingStore, DenyListResult, PackageObject};
19use sui_types::sui_system_state::{get_sui_system_state_wrapper, AdvanceEpochParams};
20use sui_types::{
21 base_types::{ObjectID, ObjectRef, SequenceNumber, SuiAddress, TransactionDigest},
22 effects::EffectsObjectChange,
23 error::{ExecutionError, SuiResult},
24 gas::GasCostSummary,
25 object::Object,
26 object::Owner,
27 storage::{BackingPackageStore, ChildObjectResolver, ParentSync, Storage},
28 transaction::InputObjects,
29 TypeTag,
30};
31use sui_types::{is_system_package, SUI_SYSTEM_STATE_OBJECT_ID};
32
33pub struct TemporaryStore<'backing> {
34 store: &'backing dyn BackingStore,
40 tx_digest: TransactionDigest,
41 input_objects: BTreeMap<ObjectID, Object>,
42 deleted_consensus_objects: BTreeMap<ObjectID, SequenceNumber>,
43 lamport_timestamp: SequenceNumber,
45 mutable_input_refs: BTreeMap<ObjectID, (VersionDigest, Owner)>, execution_results: ExecutionResultsV2,
47 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
49 protocol_config: ProtocolConfig,
50
51 runtime_packages_loaded_from_db: RwLock<BTreeMap<ObjectID, PackageObject>>,
54
55 receiving_objects: Vec<ObjectRef>,
58}
59
60impl<'backing> TemporaryStore<'backing> {
61 pub fn new(
64 store: &'backing dyn BackingStore,
65 input_objects: InputObjects,
66 receiving_objects: Vec<ObjectRef>,
67 tx_digest: TransactionDigest,
68 protocol_config: &ProtocolConfig,
69 ) -> Self {
70 let mutable_input_refs = input_objects.exclusive_mutable_inputs();
71 let lamport_timestamp = input_objects.lamport_timestamp(&receiving_objects);
72 let deleted_consensus_objects = input_objects.consensus_stream_ended_objects();
73 let objects = input_objects.into_object_map();
74 Self {
75 store,
76 tx_digest,
77 input_objects: objects,
78 deleted_consensus_objects,
79 lamport_timestamp,
80 mutable_input_refs,
81 execution_results: ExecutionResultsV2::default(),
82 protocol_config: protocol_config.clone(),
83 loaded_runtime_objects: BTreeMap::new(),
84 runtime_packages_loaded_from_db: RwLock::new(BTreeMap::new()),
85 receiving_objects,
86 }
87 }
88
89 pub fn objects(&self) -> &BTreeMap<ObjectID, Object> {
91 &self.input_objects
92 }
93
94 pub fn update_object_version_and_prev_tx(&mut self) {
95 self.execution_results.update_version_and_previous_tx(
96 self.lamport_timestamp,
97 self.tx_digest,
98 &self.input_objects,
99 false,
100 );
101
102 #[cfg(debug_assertions)]
103 {
104 self.check_invariants();
105 }
106 }
107
108 pub fn into_inner(self) -> InnerTemporaryStore {
110 let results = self.execution_results;
111 InnerTemporaryStore {
112 input_objects: self.input_objects,
113 stream_ended_consensus_objects: self.deleted_consensus_objects,
114 mutable_inputs: self.mutable_input_refs,
115 written: results.written_objects,
116 events: TransactionEvents {
117 data: results.user_events,
118 },
119 accumulator_events: vec![],
121 loaded_runtime_objects: self.loaded_runtime_objects,
122 runtime_packages_loaded_from_db: self.runtime_packages_loaded_from_db.into_inner(),
123 lamport_version: self.lamport_timestamp,
124 binary_config: self.protocol_config.binary_config(None),
125 accumulator_running_max_withdraws: BTreeMap::new(),
126 }
127 }
128
129 pub(crate) fn ensure_active_inputs_mutated(&mut self) {
133 let mut to_be_updated = vec![];
134 for id in self.mutable_input_refs.keys() {
135 if !self.execution_results.modified_objects.contains(id) {
136 to_be_updated.push(self.input_objects[id].clone());
140 }
141 }
142 for object in to_be_updated {
143 self.mutate_input_object(object.clone());
145 }
146 }
147
148 fn get_object_changes(&self) -> BTreeMap<ObjectID, EffectsObjectChange> {
149 let results = &self.execution_results;
150 let all_ids = results
151 .created_object_ids
152 .iter()
153 .chain(&results.deleted_object_ids)
154 .chain(&results.modified_objects)
155 .chain(results.written_objects.keys())
156 .collect::<BTreeSet<_>>();
157 all_ids
158 .into_iter()
159 .map(|id| {
160 (
161 *id,
162 EffectsObjectChange::new(
163 self.get_object_modified_at(id)
164 .map(|metadata| ((metadata.version, metadata.digest), metadata.owner)),
165 results.written_objects.get(id),
166 results.created_object_ids.contains(id),
167 results.deleted_object_ids.contains(id),
168 ),
169 )
170 })
171 .collect()
172 }
173
174 pub fn into_effects(
175 mut self,
176 shared_object_refs: Vec<SharedInput>,
177 transaction_digest: &TransactionDigest,
178 mut transaction_dependencies: BTreeSet<TransactionDigest>,
179 gas_cost_summary: GasCostSummary,
180 status: ExecutionStatus,
181 gas_charger: &mut GasCharger,
182 epoch: EpochId,
183 ) -> (InnerTemporaryStore, TransactionEffects) {
184 self.update_object_version_and_prev_tx();
185
186 for (id, expected_version, expected_digest) in &self.receiving_objects {
189 if let Some(obj_meta) = self.loaded_runtime_objects.get(id) {
193 let loaded_via_receive = obj_meta.version == *expected_version
197 && obj_meta.digest == *expected_digest
198 && obj_meta.owner.is_address_owned();
199 if loaded_via_receive {
200 transaction_dependencies.insert(obj_meta.previous_transaction);
201 }
202 }
203 }
204
205 if self.protocol_config.enable_effects_v2() {
206 self.into_effects_v2(
207 shared_object_refs,
208 transaction_digest,
209 transaction_dependencies,
210 gas_cost_summary,
211 status,
212 gas_charger,
213 epoch,
214 )
215 } else {
216 let shared_object_refs = shared_object_refs
217 .into_iter()
218 .map(|shared_input| match shared_input {
219 SharedInput::Existing(oref) => oref,
220 SharedInput::ConsensusStreamEnded(_) => {
221 unreachable!("Shared object deletion not supported in effects v1")
222 }
223 SharedInput::Cancelled(_) => {
224 unreachable!("Per object congestion control not supported in effects v1.")
225 }
226 })
227 .collect();
228 self.into_effects_v1(
229 shared_object_refs,
230 transaction_digest,
231 transaction_dependencies,
232 gas_cost_summary,
233 status,
234 gas_charger,
235 epoch,
236 )
237 }
238 }
239
240 fn into_effects_v1(
241 self,
242 shared_object_refs: Vec<ObjectRef>,
243 transaction_digest: &TransactionDigest,
244 transaction_dependencies: BTreeSet<TransactionDigest>,
245 gas_cost_summary: GasCostSummary,
246 status: ExecutionStatus,
247 gas_charger: &mut GasCharger,
248 epoch: EpochId,
249 ) -> (InnerTemporaryStore, TransactionEffects) {
250 let updated_gas_object_info = if let Some(coin_id) = gas_charger.gas_coin() {
251 let object = &self.execution_results.written_objects[&coin_id];
252 (object.compute_object_reference(), object.owner.clone())
253 } else {
254 (
255 (ObjectID::ZERO, SequenceNumber::default(), ObjectDigest::MIN),
256 Owner::AddressOwner(SuiAddress::default()),
257 )
258 };
259 let lampot_version = self.lamport_timestamp;
260
261 let mut created = vec![];
262 let mut mutated = vec![];
263 let mut unwrapped = vec![];
264 let mut deleted = vec![];
265 let mut unwrapped_then_deleted = vec![];
266 let mut wrapped = vec![];
267 let mut modified_at_versions = vec![];
270 let mut deleted_at_versions = vec![];
271 self.execution_results
272 .written_objects
273 .iter()
274 .for_each(|(id, object)| {
275 let object_ref = object.compute_object_reference();
276 let owner = object.owner.clone();
277 if let Some(old_object_meta) = self.get_object_modified_at(id) {
278 modified_at_versions.push((*id, old_object_meta.version));
279 mutated.push((object_ref, owner));
280 } else if self.execution_results.created_object_ids.contains(id) {
281 created.push((object_ref, owner));
282 } else {
283 unwrapped.push((object_ref, owner));
284 }
285 });
286 self.execution_results
287 .modified_objects
288 .iter()
289 .filter(|id| !self.execution_results.written_objects.contains_key(id))
290 .for_each(|id| {
291 let old_object_meta = self.get_object_modified_at(id).unwrap();
292 deleted_at_versions.push((*id, old_object_meta.version));
293 if self.execution_results.deleted_object_ids.contains(id) {
294 deleted.push((*id, lampot_version, ObjectDigest::OBJECT_DIGEST_DELETED));
295 } else {
296 wrapped.push((*id, lampot_version, ObjectDigest::OBJECT_DIGEST_WRAPPED));
297 }
298 });
299 self.execution_results
300 .deleted_object_ids
301 .iter()
302 .filter(|id| !self.execution_results.modified_objects.contains(id))
303 .for_each(|id| {
304 unwrapped_then_deleted.push((
305 *id,
306 lampot_version,
307 ObjectDigest::OBJECT_DIGEST_DELETED,
308 ));
309 });
310 modified_at_versions.extend(deleted_at_versions);
311
312 let inner = self.into_inner();
313 let effects = TransactionEffects::new_from_execution_v1(
314 status,
315 epoch,
316 gas_cost_summary,
317 modified_at_versions,
318 shared_object_refs,
319 *transaction_digest,
320 created,
321 mutated,
322 unwrapped,
323 deleted,
324 unwrapped_then_deleted,
325 wrapped,
326 updated_gas_object_info,
327 if inner.events.data.is_empty() {
328 None
329 } else {
330 Some(inner.events.digest())
331 },
332 transaction_dependencies.into_iter().collect(),
333 );
334 (inner, effects)
335 }
336
337 fn into_effects_v2(
338 self,
339 shared_object_refs: Vec<SharedInput>,
340 transaction_digest: &TransactionDigest,
341 transaction_dependencies: BTreeSet<TransactionDigest>,
342 gas_cost_summary: GasCostSummary,
343 status: ExecutionStatus,
344 gas_charger: &mut GasCharger,
345 epoch: EpochId,
346 ) -> (InnerTemporaryStore, TransactionEffects) {
347 let gas_coin = gas_charger.gas_coin();
352
353 let object_changes = self.get_object_changes();
354
355 let lamport_version = self.lamport_timestamp;
356 let unchanged_consensus_objects = TransactionEffectsV2::compute_unchanged_consensus_objects(
357 shared_object_refs,
358 BTreeSet::new(),
359 &object_changes,
360 );
361 let inner = self.into_inner();
362
363 let effects = TransactionEffects::new_from_execution_v2(
364 status,
365 epoch,
366 gas_cost_summary,
367 unchanged_consensus_objects,
368 *transaction_digest,
369 lamport_version,
370 object_changes,
371 gas_coin,
372 if inner.events.data.is_empty() {
373 None
374 } else {
375 Some(inner.events.digest())
376 },
377 transaction_dependencies.into_iter().collect(),
378 );
379
380 (inner, effects)
381 }
382
383 #[cfg(debug_assertions)]
385 fn check_invariants(&self) {
386 debug_assert!(
388 {
389 self.execution_results
390 .written_objects
391 .keys()
392 .all(|id| !self.execution_results.deleted_object_ids.contains(id))
393 },
394 "Object both written and deleted."
395 );
396
397 debug_assert!(
399 {
400 self.mutable_input_refs
401 .keys()
402 .all(|id| self.execution_results.modified_objects.contains(id))
403 },
404 "Mutable input not modified."
405 );
406
407 debug_assert!(
408 {
409 self.execution_results
410 .written_objects
411 .values()
412 .all(|obj| obj.previous_transaction == self.tx_digest)
413 },
414 "Object previous transaction not properly set",
415 );
416 }
417
418 pub fn mutate_input_object(&mut self, object: Object) {
420 let id = object.id();
421 self.execution_results.modified_objects.insert(id);
422 self.execution_results.written_objects.insert(id, object);
423 }
424
425 pub fn mutate_child_object(&mut self, old_object: Object, new_object: Object) {
429 let id = new_object.id();
430 let old_ref = old_object.compute_object_reference();
431 debug_assert_eq!(old_ref.0, id);
432 self.loaded_runtime_objects.insert(
433 id,
434 DynamicallyLoadedObjectMetadata {
435 version: old_ref.1,
436 digest: old_ref.2,
437 owner: old_object.owner.clone(),
438 storage_rebate: old_object.storage_rebate,
439 previous_transaction: old_object.previous_transaction,
440 },
441 );
442 self.execution_results.modified_objects.insert(id);
443 self.execution_results
444 .written_objects
445 .insert(id, new_object);
446 }
447
448 pub fn upgrade_system_package(&mut self, package: Object) {
454 let id = package.id();
455 assert!(package.is_package() && is_system_package(id));
456 self.execution_results.modified_objects.insert(id);
457 self.execution_results.written_objects.insert(id, package);
458 }
459
460 pub fn create_object(&mut self, object: Object) {
462 debug_assert!(
467 object.is_immutable() || object.version() == SequenceNumber::MIN,
468 "Created mutable objects should not have a version set",
469 );
470 let id = object.id();
471 self.execution_results.created_object_ids.insert(id);
472 self.execution_results.written_objects.insert(id, object);
473 }
474
475 pub fn delete_input_object(&mut self, id: &ObjectID) {
477 debug_assert!(!self.execution_results.written_objects.contains_key(id));
479 self.execution_results.modified_objects.insert(*id);
480 self.execution_results.deleted_object_ids.insert(*id);
481 }
482
483 pub fn drop_writes(&mut self) {
484 self.execution_results.drop_writes();
485 }
486
487 pub fn read_object(&self, id: &ObjectID) -> Option<&Object> {
488 debug_assert!(!self.execution_results.deleted_object_ids.contains(id));
490 self.execution_results
491 .written_objects
492 .get(id)
493 .or_else(|| self.input_objects.get(id))
494 }
495
496 pub fn save_loaded_runtime_objects(
497 &mut self,
498 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
499 ) {
500 #[cfg(debug_assertions)]
501 {
502 for (id, v1) in &loaded_runtime_objects {
503 if let Some(v2) = self.loaded_runtime_objects.get(id) {
504 assert_eq!(v1, v2);
505 }
506 }
507 for (id, v1) in &self.loaded_runtime_objects {
508 if let Some(v2) = loaded_runtime_objects.get(id) {
509 assert_eq!(v1, v2);
510 }
511 }
512 }
513 self.loaded_runtime_objects.extend(loaded_runtime_objects);
516 }
517
518 pub fn estimate_effects_size_upperbound(&self) -> usize {
519 if self.protocol_config.enable_effects_v2() {
520 TransactionEffects::estimate_effects_size_upperbound_v2(
521 self.execution_results.written_objects.len(),
522 self.execution_results.modified_objects.len(),
523 self.input_objects.len(),
524 )
525 } else {
526 let num_deletes = self.execution_results.deleted_object_ids.len()
527 + self
528 .execution_results
529 .modified_objects
530 .iter()
531 .filter(|id| {
532 !self.execution_results.written_objects.contains_key(id)
534 && !self.execution_results.deleted_object_ids.contains(id)
535 })
536 .count();
537 TransactionEffects::estimate_effects_size_upperbound_v1(
539 self.execution_results.written_objects.len(),
540 self.mutable_input_refs.len(),
541 num_deletes,
542 self.input_objects.len(),
543 )
544 }
545 }
546
547 pub fn written_objects_size(&self) -> usize {
548 self.execution_results
549 .written_objects
550 .values()
551 .fold(0, |sum, obj| sum + obj.object_size_for_gas_metering())
552 }
553
554 pub fn conserve_unmetered_storage_rebate(&mut self, unmetered_storage_rebate: u64) {
559 if unmetered_storage_rebate == 0 {
560 return;
564 }
565 tracing::debug!(
566 "Amount of unmetered storage rebate from system tx: {:?}",
567 unmetered_storage_rebate
568 );
569 let mut system_state_wrapper = self
570 .read_object(&SUI_SYSTEM_STATE_OBJECT_ID)
571 .expect("0x5 object must be muated in system tx with unmetered storage rebate")
572 .clone();
573 assert_eq!(system_state_wrapper.storage_rebate, 0);
576 system_state_wrapper.storage_rebate = unmetered_storage_rebate;
577 self.mutate_input_object(system_state_wrapper);
578 }
579
580 fn get_object_modified_at(
586 &self,
587 object_id: &ObjectID,
588 ) -> Option<DynamicallyLoadedObjectMetadata> {
589 if self.execution_results.modified_objects.contains(object_id) {
590 Some(
591 self.mutable_input_refs
592 .get(object_id)
593 .map(
594 |((version, digest), owner)| DynamicallyLoadedObjectMetadata {
595 version: *version,
596 digest: *digest,
597 owner: owner.clone(),
598 storage_rebate: self.input_objects[object_id].storage_rebate,
600 previous_transaction: self.input_objects[object_id]
601 .previous_transaction,
602 },
603 )
604 .or_else(|| self.loaded_runtime_objects.get(object_id).cloned())
605 .unwrap_or_else(|| {
606 debug_assert!(is_system_package(*object_id));
607 let obj = self.store.get_object(object_id).unwrap();
608 DynamicallyLoadedObjectMetadata {
609 version: obj.version(),
610 digest: obj.digest(),
611 owner: obj.owner.clone(),
612 storage_rebate: obj.storage_rebate,
613 previous_transaction: obj.previous_transaction,
614 }
615 }),
616 )
617 } else {
618 None
619 }
620 }
621}
622
623impl TemporaryStore<'_> {
624 fn get_objects_to_authenticate(
626 &self,
627 sender: &SuiAddress,
628 gas_charger: &mut GasCharger,
629 is_epoch_change: bool,
630 ) -> SuiResult<(Vec<ObjectID>, HashSet<ObjectID>)> {
631 let gas_objs: HashSet<&ObjectID> = gas_charger.gas_coins().iter().map(|g| &g.0).collect();
632 let mut objs_to_authenticate = Vec::new();
633 let mut authenticated_objs = HashSet::new();
634 for (id, obj) in &self.input_objects {
635 if gas_objs.contains(id) {
636 continue;
641 }
642 match &obj.owner {
643 Owner::AddressOwner(a) => {
644 assert!(sender == a, "Input object not owned by sender");
645 authenticated_objs.insert(*id);
646 }
647 Owner::Shared { .. } => {
648 authenticated_objs.insert(*id);
649 }
650 Owner::Immutable => {
651 }
662 Owner::ObjectOwner(_parent) => {
663 unreachable!("Input objects must be address owned, shared, or immutable")
664 }
665 Owner::ConsensusAddressOwner { .. } => {
666 unimplemented!(
667 "ConsensusAddressOwner does not exist for this execution version"
668 )
669 }
670 Owner::Party { .. } => {
671 unimplemented!("Party does not exist for this execution version")
672 }
673 }
674 }
675
676 for id in &self.execution_results.modified_objects {
677 if authenticated_objs.contains(id) || gas_objs.contains(id) {
678 continue;
679 }
680 let old_obj = self.store.get_object(id).unwrap_or_else(|| {
681 panic!("Modified object must exist in the store: ID = {:?}", id)
682 });
683 match &old_obj.owner {
684 Owner::ObjectOwner(_) | Owner::AddressOwner(_) => {
687 objs_to_authenticate.push(*id);
688 }
689 Owner::Shared { .. } => {
690 unreachable!("Should already be in authenticated_objs")
691 }
692 Owner::Immutable => {
693 assert!(is_epoch_change, "Immutable objects cannot be written, except for Sui Framework/Move stdlib upgrades at epoch change boundaries");
694 assert!(
697 is_system_package(*id),
698 "Only system packages can be upgraded"
699 );
700 }
701 Owner::ConsensusAddressOwner { .. } => {
702 unimplemented!(
703 "ConsensusAddressOwner does not exist for this execution version"
704 )
705 }
706 Owner::Party { .. } => {
707 unimplemented!("Party does not exist for this execution version")
708 }
709 }
710 }
711 Ok((objs_to_authenticate, authenticated_objs))
712 }
713
714 pub fn check_ownership_invariants(
716 &self,
717 sender: &SuiAddress,
718 gas_charger: &mut GasCharger,
719 is_epoch_change: bool,
720 ) -> SuiResult<()> {
721 let (mut objects_to_authenticate, mut authenticated_objects) =
722 self.get_objects_to_authenticate(sender, gas_charger, is_epoch_change)?;
723
724 let mut covered = BTreeMap::new();
726 while let Some(to_authenticate) = objects_to_authenticate.pop() {
727 let Some(old_obj) = self.store.get_object(&to_authenticate) else {
728 continue;
731 };
732 let parent = match &old_obj.owner {
733 Owner::ObjectOwner(parent) | Owner::AddressOwner(parent) => ObjectID::from(*parent),
734 owner => panic!(
735 "Unauthenticated root at {to_authenticate:?} with owner {owner:?}\n\
736 Potentially covering objects in: {covered:#?}",
737 ),
738 };
739
740 if authenticated_objects.contains(&parent) {
741 authenticated_objects.insert(to_authenticate);
742 } else if !covered.contains_key(&parent) {
743 objects_to_authenticate.push(parent);
744 }
745
746 covered.insert(to_authenticate, parent);
747 }
748 Ok(())
749 }
750}
751
752impl TemporaryStore<'_> {
753 pub(crate) fn collect_storage_and_rebate(&mut self, gas_charger: &mut GasCharger) {
760 let old_storage_rebates: Vec<_> = self
762 .execution_results
763 .written_objects
764 .keys()
765 .map(|object_id| {
766 self.get_object_modified_at(object_id)
767 .map(|metadata| metadata.storage_rebate)
768 .unwrap_or_default()
769 })
770 .collect();
771 for (object, old_storage_rebate) in self
772 .execution_results
773 .written_objects
774 .values_mut()
775 .zip(old_storage_rebates)
776 {
777 let new_object_size = object.object_size_for_gas_metering();
779 let new_storage_rebate = gas_charger.track_storage_mutation(
781 object.id(),
782 new_object_size,
783 old_storage_rebate,
784 );
785 object.storage_rebate = new_storage_rebate;
786 }
787
788 self.collect_rebate(gas_charger);
789 }
790
791 pub(crate) fn collect_rebate(&self, gas_charger: &mut GasCharger) {
792 for object_id in &self.execution_results.modified_objects {
793 if self
794 .execution_results
795 .written_objects
796 .contains_key(object_id)
797 {
798 continue;
799 }
800 let storage_rebate = self
802 .get_object_modified_at(object_id)
803 .unwrap()
805 .storage_rebate;
806 gas_charger.track_storage_mutation(*object_id, 0, storage_rebate);
807 }
808 }
809
810 pub fn check_execution_results_consistency(&self) -> Result<(), ExecutionError> {
811 assert_invariant!(
812 self.execution_results
813 .created_object_ids
814 .iter()
815 .all(|id| !self.execution_results.deleted_object_ids.contains(id)
816 && !self.execution_results.modified_objects.contains(id)),
817 "Created object IDs cannot also be deleted or modified"
818 );
819 assert_invariant!(
820 self.execution_results.modified_objects.iter().all(|id| {
821 self.mutable_input_refs.contains_key(id)
822 || self.loaded_runtime_objects.contains_key(id)
823 || is_system_package(*id)
824 }),
825 "A modified object must be either a mutable input, a loaded child object, or a system package"
826 );
827 Ok(())
828 }
829}
830impl TemporaryStore<'_> {
835 pub fn advance_epoch_safe_mode(
836 &mut self,
837 params: &AdvanceEpochParams,
838 protocol_config: &ProtocolConfig,
839 ) {
840 let wrapper = get_sui_system_state_wrapper(self.store.as_object_store())
841 .expect("System state wrapper object must exist");
842 let (old_object, new_object) =
843 wrapper.advance_epoch_safe_mode(params, self.store.as_object_store(), protocol_config);
844 self.mutate_child_object(old_object, new_object);
845 }
846}
847
848type ModifiedObjectInfo<'a> = (
849 ObjectID,
850 Option<DynamicallyLoadedObjectMetadata>,
852 Option<&'a Object>,
853);
854
855impl TemporaryStore<'_> {
856 fn get_input_sui(
857 &self,
858 id: &ObjectID,
859 expected_version: SequenceNumber,
860 layout_resolver: &mut impl LayoutResolver,
861 ) -> Result<u64, ExecutionError> {
862 if let Some(obj) = self.input_objects.get(id) {
863 if obj.version() != expected_version {
865 invariant_violation!(
866 "Version mismatching when resolving input object to check conservation--\
867 expected {}, got {}",
868 expected_version,
869 obj.version(),
870 );
871 }
872 obj.get_total_sui(layout_resolver).map_err(|e| {
873 make_invariant_violation!(
874 "Failed looking up input SUI in SUI conservation checking for input with \
875 type {:?}: {e:#?}",
876 obj.struct_tag(),
877 )
878 })
879 } else {
880 let Some(obj) = self.store.get_object_by_key(id, expected_version) else {
882 invariant_violation!(
883 "Failed looking up dynamic field {id} in SUI conservation checking"
884 );
885 };
886 obj.get_total_sui(layout_resolver).map_err(|e| {
887 make_invariant_violation!(
888 "Failed looking up input SUI in SUI conservation checking for type \
889 {:?}: {e:#?}",
890 obj.struct_tag(),
891 )
892 })
893 }
894 }
895
896 fn get_modified_objects(&self) -> Vec<ModifiedObjectInfo<'_>> {
901 self.execution_results
902 .modified_objects
903 .iter()
904 .map(|id| {
905 let metadata = self.get_object_modified_at(id);
906 let output = self.execution_results.written_objects.get(id);
907 (*id, metadata, output)
908 })
909 .chain(
910 self.execution_results
911 .written_objects
912 .iter()
913 .filter_map(|(id, object)| {
914 if self.execution_results.modified_objects.contains(id) {
915 None
916 } else {
917 Some((*id, None, Some(object)))
918 }
919 }),
920 )
921 .collect()
922 }
923
924 pub fn check_sui_conserved(
938 &self,
939 simple_conservation_checks: bool,
940 gas_summary: &GasCostSummary,
941 ) -> Result<(), ExecutionError> {
942 if !simple_conservation_checks {
943 return Ok(());
944 }
945 let mut total_input_rebate = 0;
947 let mut total_output_rebate = 0;
949 for (_, input, output) in self.get_modified_objects() {
950 if let Some(input) = input {
951 total_input_rebate += input.storage_rebate;
952 }
953 if let Some(object) = output {
954 total_output_rebate += object.storage_rebate;
955 }
956 }
957
958 if gas_summary.storage_cost == 0 {
959 if total_input_rebate
971 != total_output_rebate
972 + gas_summary.storage_rebate
973 + gas_summary.non_refundable_storage_fee
974 {
975 return Err(ExecutionError::invariant_violation(format!(
976 "SUI conservation failed -- no storage charges in gas summary \
977 and total storage input rebate {} not equal \
978 to total storage output rebate {}",
979 total_input_rebate, total_output_rebate,
980 )));
981 }
982 } else {
983 if total_input_rebate
986 != gas_summary.storage_rebate + gas_summary.non_refundable_storage_fee
987 {
988 return Err(ExecutionError::invariant_violation(format!(
989 "SUI conservation failed -- {} SUI in storage rebate field of input objects, \
990 {} SUI in tx storage rebate or tx non-refundable storage rebate",
991 total_input_rebate, gas_summary.non_refundable_storage_fee,
992 )));
993 }
994
995 if gas_summary.storage_cost != total_output_rebate {
998 return Err(ExecutionError::invariant_violation(format!(
999 "SUI conservation failed -- {} SUI charged for storage, \
1000 {} SUI in storage rebate field of output objects",
1001 gas_summary.storage_cost, total_output_rebate
1002 )));
1003 }
1004 }
1005 Ok(())
1006 }
1007
1008 pub fn check_sui_conserved_expensive(
1021 &self,
1022 gas_summary: &GasCostSummary,
1023 advance_epoch_gas_summary: Option<(u64, u64)>,
1024 layout_resolver: &mut impl LayoutResolver,
1025 ) -> Result<(), ExecutionError> {
1026 let mut total_input_sui = 0;
1028 let mut total_output_sui = 0;
1030 for (id, input, output) in self.get_modified_objects() {
1031 if let Some(input) = input {
1032 total_input_sui += self.get_input_sui(&id, input.version, layout_resolver)?;
1033 }
1034 if let Some(object) = output {
1035 total_output_sui += object.get_total_sui(layout_resolver).map_err(|e| {
1036 make_invariant_violation!(
1037 "Failed looking up output SUI in SUI conservation checking for \
1038 mutated type {:?}: {e:#?}",
1039 object.struct_tag(),
1040 )
1041 })?;
1042 }
1043 }
1044 total_output_sui += gas_summary.computation_cost + gas_summary.non_refundable_storage_fee;
1049 if let Some((epoch_fees, epoch_rebates)) = advance_epoch_gas_summary {
1050 total_input_sui += epoch_fees;
1051 total_output_sui += epoch_rebates;
1052 }
1053 if total_input_sui != total_output_sui {
1054 return Err(ExecutionError::invariant_violation(format!(
1055 "SUI conservation failed: input={}, output={}, \
1056 this transaction either mints or burns SUI",
1057 total_input_sui, total_output_sui,
1058 )));
1059 }
1060 Ok(())
1061 }
1062}
1063
1064impl ChildObjectResolver for TemporaryStore<'_> {
1065 fn read_child_object(
1066 &self,
1067 parent: &ObjectID,
1068 child: &ObjectID,
1069 child_version_upper_bound: SequenceNumber,
1070 ) -> SuiResult<Option<Object>> {
1071 let obj_opt = self.execution_results.written_objects.get(child);
1072 if obj_opt.is_some() {
1073 Ok(obj_opt.cloned())
1074 } else {
1075 self.store
1076 .read_child_object(parent, child, child_version_upper_bound)
1077 }
1078 }
1079
1080 fn get_object_received_at_version(
1081 &self,
1082 owner: &ObjectID,
1083 receiving_object_id: &ObjectID,
1084 receive_object_at_version: SequenceNumber,
1085 epoch_id: EpochId,
1086 ) -> SuiResult<Option<Object>> {
1087 debug_assert!(!self
1090 .execution_results
1091 .written_objects
1092 .contains_key(receiving_object_id));
1093 debug_assert!(!self
1094 .execution_results
1095 .deleted_object_ids
1096 .contains(receiving_object_id));
1097 self.store.get_object_received_at_version(
1098 owner,
1099 receiving_object_id,
1100 receive_object_at_version,
1101 epoch_id,
1102 )
1103 }
1104}
1105
1106impl Storage for TemporaryStore<'_> {
1107 fn reset(&mut self) {
1108 self.drop_writes();
1109 }
1110
1111 fn read_object(&self, id: &ObjectID) -> Option<&Object> {
1112 TemporaryStore::read_object(self, id)
1113 }
1114
1115 fn record_execution_results(
1117 &mut self,
1118 results: ExecutionResults,
1119 ) -> Result<(), ExecutionError> {
1120 let ExecutionResults::V2(results) = results else {
1121 panic!("ExecutionResults::V2 expected in sui-execution v1 and above");
1122 };
1123 self.execution_results
1126 .merge_results(
1127 results, false, true,
1128 )
1129 .unwrap();
1130 Ok(())
1131 }
1132
1133 fn save_loaded_runtime_objects(
1134 &mut self,
1135 loaded_runtime_objects: BTreeMap<ObjectID, DynamicallyLoadedObjectMetadata>,
1136 ) {
1137 TemporaryStore::save_loaded_runtime_objects(self, loaded_runtime_objects)
1138 }
1139
1140 fn save_wrapped_object_containers(
1141 &mut self,
1142 _wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
1143 ) {
1144 unreachable!("Unused in v1")
1145 }
1146
1147 fn check_coin_deny_list(
1148 &self,
1149 _receiving_funds_type_and_owners: BTreeMap<TypeTag, BTreeSet<SuiAddress>>,
1150 ) -> DenyListResult {
1151 unreachable!("Coin denylist v2 is not supported in sui-execution v1");
1152 }
1153
1154 fn record_generated_object_ids(&mut self, _generated_ids: BTreeSet<ObjectID>) {
1155 unreachable!(
1156 "Generated object IDs are not recorded in ExecutionResults in sui-execution v1"
1157 );
1158 }
1159}
1160
1161impl BackingPackageStore for TemporaryStore<'_> {
1162 fn get_package_object(&self, package_id: &ObjectID) -> SuiResult<Option<PackageObject>> {
1163 if let Some(obj) = self.read_object(package_id) {
1170 Ok(Some(PackageObject::new(obj.clone())))
1171 } else {
1172 self.store.get_package_object(package_id).inspect(|obj| {
1173 if let Some(v) = obj {
1175 if !self
1176 .runtime_packages_loaded_from_db
1177 .read()
1178 .contains_key(package_id)
1179 {
1180 self.runtime_packages_loaded_from_db
1183 .write()
1184 .insert(*package_id, v.clone());
1185 }
1186 }
1187 })
1188 }
1189 }
1190}
1191
1192impl ParentSync for TemporaryStore<'_> {
1193 fn get_latest_parent_entry_ref_deprecated(&self, _object_id: ObjectID) -> Option<ObjectRef> {
1194 unreachable!("Never called in newer protocol versions")
1195 }
1196}