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::LimitsMetrics,
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<LimitsMetrics>,
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 };
194 match object.data {
195 Data::Package(_) => {
196 return Err(PartialVMError::new(StatusCode::STORAGE_ERROR).with_message(
197 format!(
198 "Mismatched object type for {child}. \
199 Expected a Move object but found a Move package"
200 ),
201 ))
202 }
203 Data::Move(_) => Some(object),
204 }
205 } else {
206 None
207 };
208
209 if let LimitThresholdCrossed::Hard(_, lim) = check_limit_by_meter!(
210 self.is_metered,
211 cached_objects_count,
212 self.local_config.object_runtime_max_num_cached_objects,
213 self.local_config
214 .object_runtime_max_num_cached_objects_system_tx,
215 self.metrics.excessive_object_runtime_cached_objects
216 ) {
217 return Err(PartialVMError::new(StatusCode::MEMORY_LIMIT_EXCEEDED)
218 .with_message(format!(
219 "Object runtime cached objects limit ({} entries) reached",
220 lim
221 ))
222 .with_sub_status(
223 VMMemoryLimitExceededSubStatusCode::OBJECT_RUNTIME_CACHE_LIMIT_EXCEEDED
224 as u64,
225 ));
226 };
227
228 e.insert(obj_opt);
229 }
230 Ok(self
231 .cached_objects
232 .get(&child)
233 .unwrap()
234 .as_ref()
235 .map(|obj| {
236 obj.data
237 .try_as_move()
238 .unwrap()
240 }))
241 }
242
243 fn fetch_object_impl(
244 &mut self,
245 parent: ObjectID,
246 child: ObjectID,
247 child_ty: &Type,
248 child_ty_layout: &R::MoveTypeLayout,
249 child_ty_fully_annotated_layout: &A::MoveTypeLayout,
250 child_move_type: MoveObjectType,
251 ) -> PartialVMResult<ObjectResult<(Type, MoveObjectType, GlobalValue)>> {
252 let obj = match self.get_or_fetch_object_from_store(parent, child)? {
253 None => {
254 return Ok(ObjectResult::Loaded((
255 child_ty.clone(),
256 child_move_type,
257 GlobalValue::none(),
258 )))
259 }
260 Some(obj) => obj,
261 };
262 if obj.type_() != &child_move_type {
264 return Ok(ObjectResult::MismatchedType);
265 }
266 let obj_contents = obj.contents();
268 let v = match Value::simple_deserialize(obj_contents, child_ty_layout) {
269 Some(v) => v,
270 None => return Err(
271 PartialVMError::new(StatusCode::FAILED_TO_DESERIALIZE_RESOURCE).with_message(
272 format!("Failed to deserialize object {child} with type {child_move_type}",),
273 ),
274 ),
275 };
276 let global_value =
277 match GlobalValue::cached(v) {
278 Ok(gv) => gv,
279 Err(e) => {
280 return Err(PartialVMError::new(StatusCode::STORAGE_ERROR).with_message(
281 format!("Object {child} did not deserialize to a struct Value. Error: {e}"),
282 ))
283 }
284 };
285 let contained_uids =
287 get_all_uids(child_ty_fully_annotated_layout, obj_contents).map_err(|e| {
288 PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
289 .with_message(format!("Failed to find UIDs. ERROR: {e}"))
290 })?;
291 let parents_root_version = self.root_version.get(&parent).copied();
292 if let Some(v) = parents_root_version {
293 debug_assert!(contained_uids.contains(&child));
294 for id in contained_uids {
295 self.root_version.insert(id, v);
296 }
297 }
298 Ok(ObjectResult::Loaded((
299 child_ty.clone(),
300 child_move_type,
301 global_value,
302 )))
303 }
304}
305
306fn deserialize_move_object(
307 obj: &MoveObject,
308 child_ty: &Type,
309 child_ty_layout: &R::MoveTypeLayout,
310 child_move_type: MoveObjectType,
311) -> PartialVMResult<ObjectResult<(Type, MoveObjectType, Value)>> {
312 let child_id = obj.id();
313 if obj.type_() != &child_move_type {
315 return Ok(ObjectResult::MismatchedType);
316 }
317 let value = match Value::simple_deserialize(obj.contents(), child_ty_layout) {
318 Some(v) => v,
319 None => {
320 return Err(
321 PartialVMError::new(StatusCode::FAILED_TO_DESERIALIZE_RESOURCE).with_message(
322 format!("Failed to deserialize object {child_id} with type {child_move_type}",),
323 ),
324 )
325 }
326 };
327 Ok(ObjectResult::Loaded((
328 child_ty.clone(),
329 child_move_type,
330 value,
331 )))
332}
333
334impl<'a> ChildObjectStore<'a> {
335 pub(super) fn new(
336 resolver: &'a dyn ChildObjectResolver,
337 root_version: BTreeMap<ObjectID, SequenceNumber>,
338 is_metered: bool,
339 local_config: LocalProtocolConfig,
340 metrics: Arc<LimitsMetrics>,
341 current_epoch_id: EpochId,
342 ) -> Self {
343 Self {
344 inner: Inner {
345 resolver,
346 root_version,
347 cached_objects: BTreeMap::new(),
348 is_metered,
349 local_config,
350 metrics,
351 current_epoch_id,
352 },
353 store: BTreeMap::new(),
354 is_metered,
355 }
356 }
357
358 pub(super) fn receive_object(
359 &mut self,
360 parent: ObjectID,
361 child: ObjectID,
362 child_version: SequenceNumber,
363 child_ty: &Type,
364 child_layout: &R::MoveTypeLayout,
365 child_fully_annotated_layout: &A::MoveTypeLayout,
366 child_move_type: MoveObjectType,
367 ) -> PartialVMResult<LoadedWithMetadataResult<ObjectResult<Value>>> {
368 let Some((obj, obj_meta)) =
369 self.inner
370 .receive_object_from_store(parent, child, child_version)?
371 else {
372 return Ok(None);
373 };
374
375 Ok(Some(
376 match deserialize_move_object(&obj, child_ty, child_layout, child_move_type)? {
377 ObjectResult::MismatchedType => (ObjectResult::MismatchedType, obj_meta),
378 ObjectResult::Loaded((_, _, v)) => {
379 let contained_uids = get_all_uids(child_fully_annotated_layout, obj.contents())
383 .map_err(|e| {
384 PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
385 .with_message(format!(
386 "Failed to find UIDs for receiving object. ERROR: {e}"
387 ))
388 })?;
389 for id in contained_uids {
390 self.inner.root_version.insert(id, child_version);
391 }
392 (ObjectResult::Loaded(v), obj_meta)
393 }
394 },
395 ))
396 }
397
398 pub(super) fn object_exists(
399 &mut self,
400 parent: ObjectID,
401 child: ObjectID,
402 ) -> PartialVMResult<bool> {
403 if let Some(child_object) = self.store.get(&child) {
404 return child_object.value.exists();
405 }
406 Ok(self
407 .inner
408 .get_or_fetch_object_from_store(parent, child)?
409 .is_some())
410 }
411
412 pub(super) fn object_exists_and_has_type(
413 &mut self,
414 parent: ObjectID,
415 child: ObjectID,
416 child_move_type: &MoveObjectType,
417 ) -> PartialVMResult<bool> {
418 if let Some(child_object) = self.store.get(&child) {
419 return Ok(child_object.value.exists()? && &child_object.move_type == child_move_type);
421 }
422 Ok(self
423 .inner
424 .get_or_fetch_object_from_store(parent, child)?
425 .map(|move_obj| move_obj.type_() == child_move_type)
426 .unwrap_or(false))
427 }
428
429 pub(super) fn get_or_fetch_object(
430 &mut self,
431 parent: ObjectID,
432 child: ObjectID,
433 child_ty: &Type,
434 child_layout: &R::MoveTypeLayout,
435 child_fully_annotated_layout: &A::MoveTypeLayout,
436 child_move_type: MoveObjectType,
437 ) -> PartialVMResult<ObjectResult<&mut ChildObject>> {
438 let store_entries_count = self.store.len() as u64;
439 let child_object = match self.store.entry(child) {
440 btree_map::Entry::Vacant(e) => {
441 let (ty, move_type, value) = match self.inner.fetch_object_impl(
442 parent,
443 child,
444 child_ty,
445 child_layout,
446 child_fully_annotated_layout,
447 child_move_type,
448 )? {
449 ObjectResult::MismatchedType => return Ok(ObjectResult::MismatchedType),
450 ObjectResult::Loaded(res) => res,
451 };
452
453 if let LimitThresholdCrossed::Hard(_, lim) = check_limit_by_meter!(
454 self.is_metered,
455 store_entries_count,
456 self.inner.local_config.object_runtime_max_num_store_entries,
457 self.inner
458 .local_config
459 .object_runtime_max_num_store_entries_system_tx,
460 self.inner.metrics.excessive_object_runtime_store_entries
461 ) {
462 return Err(PartialVMError::new(StatusCode::MEMORY_LIMIT_EXCEEDED)
463 .with_message(format!(
464 "Object runtime store limit ({} entries) reached",
465 lim
466 ))
467 .with_sub_status(
468 VMMemoryLimitExceededSubStatusCode::OBJECT_RUNTIME_STORE_LIMIT_EXCEEDED
469 as u64,
470 ));
471 };
472
473 e.insert(ChildObject {
474 owner: parent,
475 ty,
476 move_type,
477 value,
478 })
479 }
480 btree_map::Entry::Occupied(e) => {
481 let child_object = e.into_mut();
482 if child_object.move_type != child_move_type {
483 return Ok(ObjectResult::MismatchedType);
484 }
485 child_object
486 }
487 };
488 Ok(ObjectResult::Loaded(child_object))
489 }
490
491 pub(super) fn add_object(
492 &mut self,
493 parent: ObjectID,
494 child: ObjectID,
495 child_ty: &Type,
496 child_move_type: MoveObjectType,
497 child_value: Value,
498 ) -> PartialVMResult<()> {
499 if let LimitThresholdCrossed::Hard(_, lim) = check_limit_by_meter!(
500 self.is_metered,
501 self.store.len(),
502 self.inner.local_config.object_runtime_max_num_store_entries,
503 self.inner
504 .local_config
505 .object_runtime_max_num_store_entries_system_tx,
506 self.inner.metrics.excessive_object_runtime_store_entries
507 ) {
508 return Err(PartialVMError::new(StatusCode::MEMORY_LIMIT_EXCEEDED)
509 .with_message(format!(
510 "Object runtime store limit ({} entries) reached",
511 lim
512 ))
513 .with_sub_status(
514 VMMemoryLimitExceededSubStatusCode::OBJECT_RUNTIME_STORE_LIMIT_EXCEEDED as u64,
515 ));
516 };
517
518 let mut value = if let Some(ChildObject { ty, value, .. }) = self.store.remove(&child) {
519 if value.exists()? {
520 return Err(
521 PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
522 .with_message(
523 "Duplicate addition of a child object. \
524 The previous value cannot be dropped. Indicates possible duplication \
525 of objects as an object was fetched more than once from two different \
526 parents, yet was not removed from one first"
527 .to_string(),
528 ),
529 );
530 }
531 if self.inner.local_config.loaded_child_object_format {
532 if !self.inner.local_config.loaded_child_object_format_type && child_ty != &ty {
534 let msg = format!("Type changed for child {child} when setting the value back");
535 return Err(
536 PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
537 .with_message(msg),
538 );
539 }
540 value
541 } else {
542 GlobalValue::none()
543 }
544 } else {
545 GlobalValue::none()
546 };
547 if let Err((e, _)) = value.move_to(child_value) {
548 return Err(
549 PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR).with_message(
550 format!("Unable to set value for child {child}, with error {e}",),
551 ),
552 );
553 }
554 let child_object = ChildObject {
555 owner: parent,
556 ty: child_ty.clone(),
557 move_type: child_move_type,
558 value,
559 };
560 self.store.insert(child, child_object);
561 Ok(())
562 }
563
564 pub(super) fn cached_objects(&self) -> &BTreeMap<ObjectID, Option<Object>> {
565 &self.inner.cached_objects
566 }
567
568 pub(super) fn take_effects(&mut self) -> BTreeMap<ObjectID, ChildObjectEffect> {
570 std::mem::take(&mut self.store)
571 .into_iter()
572 .filter_map(|(id, child_object)| {
573 let ChildObject {
574 owner,
575 ty,
576 move_type: _,
577 value,
578 } = child_object;
579 let effect = value.into_effect()?;
580 let child_effect = ChildObjectEffect { owner, ty, effect };
581 Some((id, child_effect))
582 })
583 .collect()
584 }
585
586 pub(super) fn all_active_objects(&self) -> impl Iterator<Item = (&ObjectID, &Type, Value)> {
587 self.store.iter().filter_map(|(id, child_object)| {
588 let child_exists = child_object.value.exists().unwrap();
589 if !child_exists {
590 None
591 } else {
592 let copied_child_value = child_object
593 .value
594 .borrow_global()
595 .unwrap()
596 .value_as::<StructRef>()
597 .unwrap()
598 .read_ref()
599 .unwrap();
600 Some((id, &child_object.ty, copied_child_value))
601 }
602 })
603 }
604}