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