1use crate::object_runtime::{get_all_uids, LocalProtocolConfig};
5use move_binary_format::errors::{PartialVMError, PartialVMResult};
6use move_core_types::{annotated_value as A, runtime_value as R, vm_status::StatusCode};
7use move_vm_types::{
8 effects::Op,
9 loaded_data::runtime_types::Type,
10 values::{GlobalValue, StructRef, Value},
11};
12use std::{
13 collections::{btree_map, BTreeMap},
14 sync::Arc,
15};
16use sui_protocol_config::{check_limit_by_meter, LimitThresholdCrossed};
17use sui_types::{
18 base_types::{MoveObjectType, ObjectID, SequenceNumber},
19 committee::EpochId,
20 error::VMMemoryLimitExceededSubStatusCode,
21 execution::DynamicallyLoadedObjectMetadata,
22 metrics::ExecutionMetrics,
23 object::{Data, MoveObject, Object, Owner},
24 storage::ChildObjectResolver,
25};
26
27pub(super) struct ChildObject {
28 pub(super) owner: ObjectID,
29 pub(super) ty: Type,
30 pub(super) move_type: MoveObjectType,
31 pub(super) value: GlobalValue,
32}
33
34#[derive(Debug)]
35pub(crate) struct ChildObjectEffect {
36 pub(super) owner: ObjectID,
37 pub(super) ty: Type,
38 pub(super) effect: Op<Value>,
39}
40
41struct Inner<'a> {
42 resolver: &'a dyn ChildObjectResolver,
44 root_version: BTreeMap<ObjectID, SequenceNumber>,
48 cached_objects: BTreeMap<ObjectID, Option<Object>>,
51 is_metered: bool,
53 local_config: LocalProtocolConfig,
55 metrics: Arc<ExecutionMetrics>,
57 current_epoch_id: EpochId,
59}
60
61pub(super) struct ChildObjectStore<'a> {
64 inner: Inner<'a>,
68 store: BTreeMap<ObjectID, ChildObject>,
71 is_metered: bool,
73}
74
75pub(crate) enum ObjectResult<V> {
76 MismatchedType,
78 Loaded(V),
79}
80
81type LoadedWithMetadataResult<V> = Option<(V, DynamicallyLoadedObjectMetadata)>;
82
83impl Inner<'_> {
84 fn receive_object_from_store(
85 &self,
86 owner: ObjectID,
87 child: ObjectID,
88 version: SequenceNumber,
89 ) -> PartialVMResult<LoadedWithMetadataResult<MoveObject>> {
90 let child_opt = self
91 .resolver
92 .get_object_received_at_version(&owner, &child, version, self.current_epoch_id)
93 .map_err(|msg| {
94 PartialVMError::new(StatusCode::STORAGE_ERROR).with_message(format!("{msg}"))
95 })?;
96 let obj_opt = if let Some(object) = child_opt {
97 if object.owner != Owner::AddressOwner(owner.into()) {
101 return Err(
102 PartialVMError::new(StatusCode::STORAGE_ERROR).with_message(format!(
103 "Bad owner for {child}. \
104 Expected owner {owner} but found owner {}",
105 object.owner
106 )),
107 );
108 }
109 let loaded_metadata = DynamicallyLoadedObjectMetadata {
110 version,
111 digest: object.digest(),
112 storage_rebate: object.storage_rebate,
113 owner: object.owner.clone(),
114 previous_transaction: object.previous_transaction,
115 };
116
117 if object.version() != version {
122 return Err(
123 PartialVMError::new(StatusCode::STORAGE_ERROR).with_message(format!(
124 "Bad version for {child}. \
125 Expected version {version} but found version {}",
126 object.version()
127 )),
128 );
129 }
130 match object.into_inner().data {
131 Data::Package(_) => {
132 return Err(PartialVMError::new(StatusCode::STORAGE_ERROR).with_message(
133 format!(
134 "Mismatched object type for {child}. \
135 Expected a Move object but found a Move package"
136 ),
137 ))
138 }
139 Data::Move(mo @ MoveObject { .. }) => Some((mo, loaded_metadata)),
140 }
141 } else {
142 None
143 };
144 Ok(obj_opt)
145 }
146
147 fn get_or_fetch_object_from_store(
148 &mut self,
149 parent: ObjectID,
150 child: ObjectID,
151 ) -> PartialVMResult<Option<&MoveObject>> {
152 let cached_objects_count = self.cached_objects.len() as u64;
153 let parents_root_version = self.root_version.get(&parent).copied();
154 let had_parent_root_version = parents_root_version.is_some();
155 let parents_root_version = parents_root_version.unwrap_or(SequenceNumber::new());
158 if let btree_map::Entry::Vacant(e) = self.cached_objects.entry(child) {
159 let child_opt = self
160 .resolver
161 .read_child_object(&parent, &child, parents_root_version)
162 .map_err(|msg| {
163 PartialVMError::new(StatusCode::STORAGE_ERROR).with_message(format!("{msg}"))
164 })?;
165 let obj_opt = if let Some(object) = child_opt {
166 if !had_parent_root_version {
169 return Err(PartialVMError::new(StatusCode::STORAGE_ERROR).with_message(
170 format!("A new parent {parent} should not have a child object {child}."),
171 ));
172 }
173 match &object.owner {
176 Owner::ObjectOwner(id) => {
177 if ObjectID::from(*id) != parent {
178 return Err(PartialVMError::new(StatusCode::STORAGE_ERROR).with_message(
179 format!("Bad owner for {child}. \
180 Expected owner {parent} but found owner {id}")
181 ))
182 }
183 }
184 Owner::AddressOwner(_) | Owner::Immutable | Owner::Shared { .. } => {
185 return Err(PartialVMError::new(StatusCode::STORAGE_ERROR).with_message(
186 format!("Bad owner for {child}. \
187 Expected an id owner {parent} but found an address, immutable, or shared owner")
188 ))
189 }
190 Owner::ConsensusAddressOwner { .. } => {
191 unimplemented!("ConsensusAddressOwner does not exist for this execution version")
192 }
193 Owner::Party { .. } => {
194 unimplemented!("Party does not exist for this execution version")
195 }
196 };
197 match object.data {
198 Data::Package(_) => {
199 return Err(PartialVMError::new(StatusCode::STORAGE_ERROR).with_message(
200 format!(
201 "Mismatched object type for {child}. \
202 Expected a Move object but found a Move package"
203 ),
204 ))
205 }
206 Data::Move(_) => Some(object),
207 }
208 } else {
209 None
210 };
211
212 if let LimitThresholdCrossed::Hard(_, lim) = check_limit_by_meter!(
213 self.is_metered,
214 cached_objects_count,
215 self.local_config.object_runtime_max_num_cached_objects,
216 self.local_config
217 .object_runtime_max_num_cached_objects_system_tx,
218 self.metrics
219 .limits_metrics
220 .excessive_object_runtime_cached_objects
221 ) {
222 return Err(PartialVMError::new(StatusCode::MEMORY_LIMIT_EXCEEDED)
223 .with_message(format!(
224 "Object runtime cached objects limit ({} entries) reached",
225 lim
226 ))
227 .with_sub_status(
228 VMMemoryLimitExceededSubStatusCode::OBJECT_RUNTIME_CACHE_LIMIT_EXCEEDED
229 as u64,
230 ));
231 };
232
233 e.insert(obj_opt);
234 }
235 Ok(self
236 .cached_objects
237 .get(&child)
238 .unwrap()
239 .as_ref()
240 .map(|obj| {
241 obj.data
242 .try_as_move()
243 .unwrap()
245 }))
246 }
247
248 fn fetch_object_impl(
249 &mut self,
250 parent: ObjectID,
251 child: ObjectID,
252 child_ty: &Type,
253 child_ty_layout: &R::MoveTypeLayout,
254 child_ty_fully_annotated_layout: &A::MoveTypeLayout,
255 child_move_type: MoveObjectType,
256 ) -> PartialVMResult<ObjectResult<(Type, MoveObjectType, GlobalValue)>> {
257 let obj = match self.get_or_fetch_object_from_store(parent, child)? {
258 None => {
259 return Ok(ObjectResult::Loaded((
260 child_ty.clone(),
261 child_move_type,
262 GlobalValue::none(),
263 )))
264 }
265 Some(obj) => obj,
266 };
267 if obj.type_() != &child_move_type {
269 return Ok(ObjectResult::MismatchedType);
270 }
271 let obj_contents = obj.contents();
273 let v = match Value::simple_deserialize(obj_contents, child_ty_layout) {
274 Some(v) => v,
275 None => return Err(
276 PartialVMError::new(StatusCode::FAILED_TO_DESERIALIZE_RESOURCE).with_message(
277 format!("Failed to deserialize object {child} with type {child_move_type}",),
278 ),
279 ),
280 };
281 let global_value =
282 match GlobalValue::cached(v) {
283 Ok(gv) => gv,
284 Err(e) => {
285 return Err(PartialVMError::new(StatusCode::STORAGE_ERROR).with_message(
286 format!("Object {child} did not deserialize to a struct Value. Error: {e}"),
287 ))
288 }
289 };
290 let contained_uids =
292 get_all_uids(child_ty_fully_annotated_layout, obj_contents).map_err(|e| {
293 PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
294 .with_message(format!("Failed to find UIDs. ERROR: {e}"))
295 })?;
296 let parents_root_version = self.root_version.get(&parent).copied();
297 if let Some(v) = parents_root_version {
298 debug_assert!(contained_uids.contains(&child));
299 for id in contained_uids {
300 self.root_version.insert(id, v);
301 }
302 }
303 Ok(ObjectResult::Loaded((
304 child_ty.clone(),
305 child_move_type,
306 global_value,
307 )))
308 }
309}
310
311fn deserialize_move_object(
312 obj: &MoveObject,
313 child_ty: &Type,
314 child_ty_layout: &R::MoveTypeLayout,
315 child_move_type: MoveObjectType,
316) -> PartialVMResult<ObjectResult<(Type, MoveObjectType, Value)>> {
317 let child_id = obj.id();
318 if obj.type_() != &child_move_type {
320 return Ok(ObjectResult::MismatchedType);
321 }
322 let value = match Value::simple_deserialize(obj.contents(), child_ty_layout) {
323 Some(v) => v,
324 None => {
325 return Err(
326 PartialVMError::new(StatusCode::FAILED_TO_DESERIALIZE_RESOURCE).with_message(
327 format!("Failed to deserialize object {child_id} with type {child_move_type}",),
328 ),
329 )
330 }
331 };
332 Ok(ObjectResult::Loaded((
333 child_ty.clone(),
334 child_move_type,
335 value,
336 )))
337}
338
339impl<'a> ChildObjectStore<'a> {
340 pub(super) fn new(
341 resolver: &'a dyn ChildObjectResolver,
342 root_version: BTreeMap<ObjectID, SequenceNumber>,
343 is_metered: bool,
344 local_config: LocalProtocolConfig,
345 metrics: Arc<ExecutionMetrics>,
346 current_epoch_id: EpochId,
347 ) -> Self {
348 Self {
349 inner: Inner {
350 resolver,
351 root_version,
352 cached_objects: BTreeMap::new(),
353 is_metered,
354 local_config,
355 metrics,
356 current_epoch_id,
357 },
358 store: BTreeMap::new(),
359 is_metered,
360 }
361 }
362
363 pub(super) fn receive_object(
364 &mut self,
365 parent: ObjectID,
366 child: ObjectID,
367 child_version: SequenceNumber,
368 child_ty: &Type,
369 child_layout: &R::MoveTypeLayout,
370 child_fully_annotated_layout: &A::MoveTypeLayout,
371 child_move_type: MoveObjectType,
372 ) -> PartialVMResult<LoadedWithMetadataResult<ObjectResult<Value>>> {
373 let Some((obj, obj_meta)) =
374 self.inner
375 .receive_object_from_store(parent, child, child_version)?
376 else {
377 return Ok(None);
378 };
379
380 Ok(Some(
381 match deserialize_move_object(&obj, child_ty, child_layout, child_move_type)? {
382 ObjectResult::MismatchedType => (ObjectResult::MismatchedType, obj_meta),
383 ObjectResult::Loaded((_, _, v)) => {
384 let contained_uids = get_all_uids(child_fully_annotated_layout, obj.contents())
388 .map_err(|e| {
389 PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
390 .with_message(format!(
391 "Failed to find UIDs for receiving object. ERROR: {e}"
392 ))
393 })?;
394 for id in contained_uids {
395 self.inner.root_version.insert(id, child_version);
396 }
397 (ObjectResult::Loaded(v), obj_meta)
398 }
399 },
400 ))
401 }
402
403 pub(super) fn object_exists(
404 &mut self,
405 parent: ObjectID,
406 child: ObjectID,
407 ) -> PartialVMResult<bool> {
408 if let Some(child_object) = self.store.get(&child) {
409 return child_object.value.exists();
410 }
411 Ok(self
412 .inner
413 .get_or_fetch_object_from_store(parent, child)?
414 .is_some())
415 }
416
417 pub(super) fn object_exists_and_has_type(
418 &mut self,
419 parent: ObjectID,
420 child: ObjectID,
421 child_move_type: &MoveObjectType,
422 ) -> PartialVMResult<bool> {
423 if let Some(child_object) = self.store.get(&child) {
424 return Ok(child_object.value.exists()? && &child_object.move_type == child_move_type);
426 }
427 Ok(self
428 .inner
429 .get_or_fetch_object_from_store(parent, child)?
430 .map(|move_obj| move_obj.type_() == child_move_type)
431 .unwrap_or(false))
432 }
433
434 pub(super) fn get_or_fetch_object(
435 &mut self,
436 parent: ObjectID,
437 child: ObjectID,
438 child_ty: &Type,
439 child_layout: &R::MoveTypeLayout,
440 child_fully_annotated_layout: &A::MoveTypeLayout,
441 child_move_type: MoveObjectType,
442 ) -> PartialVMResult<ObjectResult<&mut ChildObject>> {
443 let store_entries_count = self.store.len() as u64;
444 let child_object = match self.store.entry(child) {
445 btree_map::Entry::Vacant(e) => {
446 let (ty, move_type, value) = match self.inner.fetch_object_impl(
447 parent,
448 child,
449 child_ty,
450 child_layout,
451 child_fully_annotated_layout,
452 child_move_type,
453 )? {
454 ObjectResult::MismatchedType => return Ok(ObjectResult::MismatchedType),
455 ObjectResult::Loaded(res) => res,
456 };
457
458 if let LimitThresholdCrossed::Hard(_, lim) = check_limit_by_meter!(
459 self.is_metered,
460 store_entries_count,
461 self.inner.local_config.object_runtime_max_num_store_entries,
462 self.inner
463 .local_config
464 .object_runtime_max_num_store_entries_system_tx,
465 self.inner
466 .metrics
467 .limits_metrics
468 .excessive_object_runtime_store_entries
469 ) {
470 return Err(PartialVMError::new(StatusCode::MEMORY_LIMIT_EXCEEDED)
471 .with_message(format!(
472 "Object runtime store limit ({} entries) reached",
473 lim
474 ))
475 .with_sub_status(
476 VMMemoryLimitExceededSubStatusCode::OBJECT_RUNTIME_STORE_LIMIT_EXCEEDED
477 as u64,
478 ));
479 };
480
481 e.insert(ChildObject {
482 owner: parent,
483 ty,
484 move_type,
485 value,
486 })
487 }
488 btree_map::Entry::Occupied(e) => {
489 let child_object = e.into_mut();
490 if child_object.move_type != child_move_type {
491 return Ok(ObjectResult::MismatchedType);
492 }
493 child_object
494 }
495 };
496 Ok(ObjectResult::Loaded(child_object))
497 }
498
499 pub(super) fn add_object(
500 &mut self,
501 parent: ObjectID,
502 child: ObjectID,
503 child_ty: &Type,
504 child_move_type: MoveObjectType,
505 child_value: Value,
506 ) -> PartialVMResult<()> {
507 if let LimitThresholdCrossed::Hard(_, lim) = check_limit_by_meter!(
508 self.is_metered,
509 self.store.len(),
510 self.inner.local_config.object_runtime_max_num_store_entries,
511 self.inner
512 .local_config
513 .object_runtime_max_num_store_entries_system_tx,
514 self.inner
515 .metrics
516 .limits_metrics
517 .excessive_object_runtime_store_entries
518 ) {
519 return Err(PartialVMError::new(StatusCode::MEMORY_LIMIT_EXCEEDED)
520 .with_message(format!(
521 "Object runtime store limit ({} entries) reached",
522 lim
523 ))
524 .with_sub_status(
525 VMMemoryLimitExceededSubStatusCode::OBJECT_RUNTIME_STORE_LIMIT_EXCEEDED as u64,
526 ));
527 };
528
529 let mut value = if let Some(ChildObject { ty, value, .. }) = self.store.remove(&child) {
530 if value.exists()? {
531 return Err(
532 PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
533 .with_message(
534 "Duplicate addition of a child object. \
535 The previous value cannot be dropped. Indicates possible duplication \
536 of objects as an object was fetched more than once from two different \
537 parents, yet was not removed from one first"
538 .to_string(),
539 ),
540 );
541 }
542 if self.inner.local_config.loaded_child_object_format {
543 if !self.inner.local_config.loaded_child_object_format_type && child_ty != &ty {
545 let msg = format!("Type changed for child {child} when setting the value back");
546 return Err(
547 PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
548 .with_message(msg),
549 );
550 }
551 value
552 } else {
553 GlobalValue::none()
554 }
555 } else {
556 GlobalValue::none()
557 };
558 if let Err((e, _)) = value.move_to(child_value) {
559 return Err(
560 PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR).with_message(
561 format!("Unable to set value for child {child}, with error {e}",),
562 ),
563 );
564 }
565 let child_object = ChildObject {
566 owner: parent,
567 ty: child_ty.clone(),
568 move_type: child_move_type,
569 value,
570 };
571 self.store.insert(child, child_object);
572 Ok(())
573 }
574
575 pub(super) fn cached_objects(&self) -> &BTreeMap<ObjectID, Option<Object>> {
576 &self.inner.cached_objects
577 }
578
579 pub(super) fn take_effects(&mut self) -> BTreeMap<ObjectID, ChildObjectEffect> {
581 std::mem::take(&mut self.store)
582 .into_iter()
583 .filter_map(|(id, child_object)| {
584 let ChildObject {
585 owner,
586 ty,
587 move_type: _,
588 value,
589 } = child_object;
590 let effect = value.into_effect()?;
591 let child_effect = ChildObjectEffect { owner, ty, effect };
592 Some((id, child_effect))
593 })
594 .collect()
595 }
596
597 pub(super) fn all_active_objects(&self) -> impl Iterator<Item = (&ObjectID, &Type, Value)> {
598 self.store.iter().filter_map(|(id, child_object)| {
599 let child_exists = child_object.value.exists().unwrap();
600 if !child_exists {
601 None
602 } else {
603 let copied_child_value = child_object
604 .value
605 .borrow_global()
606 .unwrap()
607 .value_as::<StructRef>()
608 .unwrap()
609 .read_ref()
610 .unwrap();
611 Some((id, &child_object.ty, copied_child_value))
612 }
613 })
614 }
615}