1use std::{
6 collections::{BTreeMap, btree_map},
7 sync::Arc,
8};
9
10use iota_protocol_config::{LimitThresholdCrossed, ProtocolConfig, check_limit_by_meter};
11use iota_types::{
12 base_types::{MoveObjectType, ObjectID, SequenceNumber},
13 committee::EpochId,
14 error::VMMemoryLimitExceededSubStatusCode,
15 execution::DynamicallyLoadedObjectMetadata,
16 metrics::LimitsMetrics,
17 object::{Data, MoveObject, Object, Owner},
18 storage::ChildObjectResolver,
19};
20use move_binary_format::errors::{PartialVMError, PartialVMResult};
21use move_core_types::{
22 annotated_value as A, effects::Op, runtime_value as R, vm_status::StatusCode,
23};
24use move_vm_types::{
25 loaded_data::runtime_types::Type,
26 values::{GlobalValue, StructRef, Value},
27};
28
29use crate::object_runtime::get_all_uids;
30
31pub(super) struct ChildObject {
32 pub(super) owner: ObjectID,
33 pub(super) ty: Type,
34 pub(super) move_type: MoveObjectType,
35 pub(super) value: GlobalValue,
36}
37
38pub(crate) struct ActiveChildObject<'a> {
39 pub(crate) id: &'a ObjectID,
40 pub(crate) owner: &'a ObjectID,
41 pub(crate) ty: &'a Type,
42 pub(crate) move_type: &'a MoveObjectType,
43 pub(crate) copied_value: Option<Value>,
44}
45
46#[derive(Debug)]
47struct ConfigSetting {
48 config: ObjectID,
49 ty: MoveObjectType,
50 value: Value,
51}
52
53#[derive(Debug)]
54pub(crate) struct ChildObjectEffect {
55 pub(super) owner: ObjectID,
56 pub(super) ty: Type,
57 pub(super) effect: Op<Value>,
58}
59
60struct Inner<'a> {
61 resolver: &'a dyn ChildObjectResolver,
63 root_version: BTreeMap<ObjectID, SequenceNumber>,
67 wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
70 cached_objects: BTreeMap<ObjectID, Option<Object>>,
73 is_metered: bool,
75 protocol_config: &'a ProtocolConfig,
77 metrics: Arc<LimitsMetrics>,
79 current_epoch_id: EpochId,
81}
82
83pub(super) struct ChildObjectStore<'a> {
86 inner: Inner<'a>,
90 store: BTreeMap<ObjectID, ChildObject>,
93 config_setting_cache: BTreeMap<ObjectID, ConfigSetting>,
94 is_metered: bool,
96}
97
98pub(crate) enum ObjectResult<V> {
99 MismatchedType,
101 Loaded(V),
102}
103
104type LoadedWithMetadataResult<V> = Option<(V, DynamicallyLoadedObjectMetadata)>;
105
106macro_rules! fetch_child_object_unbounded {
107 ($inner:ident, $parent:ident, $child:ident, $parents_root_version:expr, $had_parent_root_version:expr) => {{
108 let child_opt = $inner
109 .resolver
110 .read_child_object(&$parent, &$child, $parents_root_version)
111 .map_err(|msg| {
112 PartialVMError::new(StatusCode::STORAGE_ERROR).with_message(format!("{msg}"))
113 })?;
114 if let Some(object) = child_opt {
115 if !$had_parent_root_version {
118 return Err(
119 PartialVMError::new(StatusCode::STORAGE_ERROR).with_message(format!(
120 "A new parent {} should not have a child object {}.",
121 $parent, $child
122 )),
123 );
124 }
125 match &object.owner {
128 Owner::ObjectOwner(id) => {
129 if ObjectID::from(*id) != $parent {
130 return Err(PartialVMError::new(StatusCode::STORAGE_ERROR).with_message(
131 format!(
132 "Bad owner for {}. Expected owner {} but found owner {}",
133 $child, $parent, id
134 ),
135 ));
136 }
137 }
138 Owner::AddressOwner(_) | Owner::Immutable | Owner::Shared { .. } => {
139 return Err(PartialVMError::new(StatusCode::STORAGE_ERROR).with_message(
140 format!(
141 "Bad owner for {}. \
142 Expected an id owner {} but found an address, \
143 immutable, or shared owner",
144 $child, $parent
145 ),
146 ));
147 }
148 };
149 match object.data {
150 Data::Package(_) => {
151 return Err(PartialVMError::new(StatusCode::STORAGE_ERROR).with_message(
152 format!(
153 "Mismatched object type for {}. \
154 Expected a Move object but found a Move package",
155 $child
156 ),
157 ));
158 }
159 Data::Move(_) => Some(object),
160 }
161 } else {
162 None
163 }
164 }};
165}
166
167impl Inner<'_> {
168 fn receive_object_from_store(
169 &self,
170 owner: ObjectID,
171 child: ObjectID,
172 version: SequenceNumber,
173 ) -> PartialVMResult<LoadedWithMetadataResult<MoveObject>> {
174 let child_opt = self
175 .resolver
176 .get_object_received_at_version(&owner, &child, version, self.current_epoch_id)
177 .map_err(|msg| {
178 PartialVMError::new(StatusCode::STORAGE_ERROR).with_message(format!("{msg}"))
179 })?;
180 let obj_opt = if let Some(object) = child_opt {
181 if object.owner != Owner::AddressOwner(owner.into()) {
186 return Err(
187 PartialVMError::new(StatusCode::STORAGE_ERROR).with_message(format!(
188 "Bad owner for {child}. \
189 Expected owner {owner} but found owner {}",
190 object.owner
191 )),
192 );
193 }
194 let loaded_metadata = DynamicallyLoadedObjectMetadata {
195 version,
196 digest: object.digest(),
197 storage_rebate: object.storage_rebate,
198 owner: object.owner,
199 previous_transaction: object.previous_transaction,
200 };
201
202 if object.version() != version {
207 return Err(
208 PartialVMError::new(StatusCode::STORAGE_ERROR).with_message(format!(
209 "Bad version for {child}. \
210 Expected version {version} but found version {}",
211 object.version()
212 )),
213 );
214 }
215 match object.into_inner().data {
216 Data::Package(_) => {
217 return Err(PartialVMError::new(StatusCode::STORAGE_ERROR).with_message(
218 format!(
219 "Mismatched object type for {child}. \
220 Expected a Move object but found a Move package"
221 ),
222 ));
223 }
224 Data::Move(mo @ MoveObject { .. }) => Some((mo, loaded_metadata)),
225 }
226 } else {
227 None
228 };
229 Ok(obj_opt)
230 }
231
232 fn get_or_fetch_object_from_store(
233 &mut self,
234 parent: ObjectID,
235 child: ObjectID,
236 ) -> PartialVMResult<Option<&MoveObject>> {
237 let cached_objects_count = self.cached_objects.len() as u64;
238 let parents_root_version = self.root_version.get(&parent).copied();
239 let had_parent_root_version = parents_root_version.is_some();
240 let parents_root_version = parents_root_version.unwrap_or(SequenceNumber::new());
243 if let btree_map::Entry::Vacant(e) = self.cached_objects.entry(child) {
244 let obj_opt = fetch_child_object_unbounded!(
245 self,
246 parent,
247 child,
248 parents_root_version,
249 had_parent_root_version
250 );
251
252 if let LimitThresholdCrossed::Hard(_, lim) = check_limit_by_meter!(
253 self.is_metered,
254 cached_objects_count,
255 self.protocol_config.object_runtime_max_num_cached_objects(),
256 self.protocol_config
257 .object_runtime_max_num_cached_objects_system_tx(),
258 self.metrics.excessive_object_runtime_cached_objects
259 ) {
260 return Err(PartialVMError::new(StatusCode::MEMORY_LIMIT_EXCEEDED)
261 .with_message(format!(
262 "Object runtime cached objects limit ({} entries) reached",
263 lim
264 ))
265 .with_sub_status(
266 VMMemoryLimitExceededSubStatusCode::OBJECT_RUNTIME_CACHE_LIMIT_EXCEEDED
267 as u64,
268 ));
269 };
270
271 e.insert(obj_opt);
272 }
273 Ok(self
274 .cached_objects
275 .get(&child)
276 .unwrap()
277 .as_ref()
278 .map(|obj| {
279 obj.data
280 .try_as_move()
281 .unwrap()
283 }))
284 }
285
286 fn fetch_object_impl(
287 &mut self,
288 parent: ObjectID,
289 child: ObjectID,
290 child_ty: &Type,
291 child_ty_layout: &R::MoveTypeLayout,
292 child_ty_fully_annotated_layout: &A::MoveTypeLayout,
293 child_move_type: &MoveObjectType,
294 ) -> PartialVMResult<ObjectResult<(Type, GlobalValue)>> {
295 let obj = match self.get_or_fetch_object_from_store(parent, child)? {
296 None => {
297 return Ok(ObjectResult::Loaded((
298 child_ty.clone(),
299 GlobalValue::none(),
300 )));
301 }
302 Some(obj) => obj,
303 };
304 if obj.type_() != child_move_type {
306 return Ok(ObjectResult::MismatchedType);
307 }
308 let obj_contents = obj.contents();
310 let v = match Value::simple_deserialize(obj_contents, child_ty_layout) {
311 Some(v) => v,
312 None => return Err(
313 PartialVMError::new(StatusCode::FAILED_TO_DESERIALIZE_RESOURCE).with_message(
314 format!("Failed to deserialize object {child} with type {child_move_type}",),
315 ),
316 ),
317 };
318 let global_value =
319 match GlobalValue::cached(v) {
320 Ok(gv) => gv,
321 Err(e) => {
322 return Err(PartialVMError::new(StatusCode::STORAGE_ERROR).with_message(
323 format!("Object {child} did not deserialize to a struct Value. Error: {e}"),
324 ));
325 }
326 };
327 let contained_uids =
329 get_all_uids(child_ty_fully_annotated_layout, obj_contents).map_err(|e| {
330 PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
331 .with_message(format!("Failed to find UIDs. ERROR: {e}"))
332 })?;
333 let parents_root_version = self.root_version.get(&parent).copied();
334 if let Some(v) = parents_root_version {
335 debug_assert!(contained_uids.contains(&child));
336 for id in contained_uids {
337 self.root_version.insert(id, v);
338 if id != child {
339 let prev = self.wrapped_object_containers.insert(id, child);
340 debug_assert!(prev.is_none())
341 }
342 }
343 }
344 Ok(ObjectResult::Loaded((child_ty.clone(), global_value)))
345 }
346}
347
348fn deserialize_move_object(
349 obj: &MoveObject,
350 child_ty: &Type,
351 child_ty_layout: &R::MoveTypeLayout,
352 child_move_type: MoveObjectType,
353) -> PartialVMResult<ObjectResult<(Type, MoveObjectType, Value)>> {
354 let child_id = obj.id();
355 if obj.type_() != &child_move_type {
357 return Ok(ObjectResult::MismatchedType);
358 }
359 let value = match Value::simple_deserialize(obj.contents(), child_ty_layout) {
360 Some(v) => v,
361 None => {
362 return Err(
363 PartialVMError::new(StatusCode::FAILED_TO_DESERIALIZE_RESOURCE).with_message(
364 format!("Failed to deserialize object {child_id} with type {child_move_type}",),
365 ),
366 );
367 }
368 };
369 Ok(ObjectResult::Loaded((
370 child_ty.clone(),
371 child_move_type,
372 value,
373 )))
374}
375
376impl<'a> ChildObjectStore<'a> {
377 pub(super) fn new(
378 resolver: &'a dyn ChildObjectResolver,
379 root_version: BTreeMap<ObjectID, SequenceNumber>,
380 wrapped_object_containers: BTreeMap<ObjectID, ObjectID>,
381 is_metered: bool,
382 protocol_config: &'a ProtocolConfig,
383 metrics: Arc<LimitsMetrics>,
384 current_epoch_id: EpochId,
385 ) -> Self {
386 Self {
387 inner: Inner {
388 resolver,
389 root_version,
390 wrapped_object_containers,
391 cached_objects: BTreeMap::new(),
392 is_metered,
393 protocol_config,
394 metrics,
395 current_epoch_id,
396 },
397 store: BTreeMap::new(),
398 config_setting_cache: BTreeMap::new(),
399 is_metered,
400 }
401 }
402
403 pub(super) fn receive_object(
404 &mut self,
405 parent: ObjectID,
406 child: ObjectID,
407 child_version: SequenceNumber,
408 child_ty: &Type,
409 child_layout: &R::MoveTypeLayout,
410 child_fully_annotated_layout: &A::MoveTypeLayout,
411 child_move_type: MoveObjectType,
412 ) -> PartialVMResult<LoadedWithMetadataResult<ObjectResult<Value>>> {
413 let Some((obj, obj_meta)) =
414 self.inner
415 .receive_object_from_store(parent, child, child_version)?
416 else {
417 return Ok(None);
418 };
419
420 Ok(Some(
421 match deserialize_move_object(&obj, child_ty, child_layout, child_move_type)? {
422 ObjectResult::MismatchedType => (ObjectResult::MismatchedType, obj_meta),
423 ObjectResult::Loaded((_, _, v)) => {
424 let contained_uids = get_all_uids(child_fully_annotated_layout, obj.contents())
429 .map_err(|e| {
430 PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
431 .with_message(format!(
432 "Failed to find UIDs for receiving object. ERROR: {e}"
433 ))
434 })?;
435 for id in contained_uids {
436 self.inner.root_version.insert(id, child_version);
437 if id != child {
438 let prev = self.inner.wrapped_object_containers.insert(id, child);
439 debug_assert!(prev.is_none())
440 }
441 }
442 (ObjectResult::Loaded(v), obj_meta)
443 }
444 },
445 ))
446 }
447
448 pub(super) fn object_exists(
449 &mut self,
450 parent: ObjectID,
451 child: ObjectID,
452 ) -> PartialVMResult<bool> {
453 if let Some(child_object) = self.store.get(&child) {
454 return child_object.value.exists();
455 }
456 Ok(self
457 .inner
458 .get_or_fetch_object_from_store(parent, child)?
459 .is_some())
460 }
461
462 pub(super) fn object_exists_and_has_type(
463 &mut self,
464 parent: ObjectID,
465 child: ObjectID,
466 child_move_type: &MoveObjectType,
467 ) -> PartialVMResult<bool> {
468 if let Some(child_object) = self.store.get(&child) {
469 return Ok(child_object.value.exists()? && &child_object.move_type == child_move_type);
471 }
472 Ok(self
473 .inner
474 .get_or_fetch_object_from_store(parent, child)?
475 .map(|move_obj| move_obj.type_() == child_move_type)
476 .unwrap_or(false))
477 }
478
479 pub(super) fn get_or_fetch_object(
480 &mut self,
481 parent: ObjectID,
482 child: ObjectID,
483 child_ty: &Type,
484 child_layout: &R::MoveTypeLayout,
485 child_fully_annotated_layout: &A::MoveTypeLayout,
486 child_move_type: MoveObjectType,
487 ) -> PartialVMResult<ObjectResult<&mut ChildObject>> {
488 let store_entries_count = self.store.len() as u64;
489 let child_object = match self.store.entry(child) {
490 btree_map::Entry::Vacant(e) => {
491 let (ty, value) = match self.inner.fetch_object_impl(
492 parent,
493 child,
494 child_ty,
495 child_layout,
496 child_fully_annotated_layout,
497 &child_move_type,
498 )? {
499 ObjectResult::MismatchedType => return Ok(ObjectResult::MismatchedType),
500 ObjectResult::Loaded(res) => res,
501 };
502
503 if let LimitThresholdCrossed::Hard(_, lim) = check_limit_by_meter!(
504 self.is_metered,
505 store_entries_count,
506 self.inner
507 .protocol_config
508 .object_runtime_max_num_store_entries(),
509 self.inner
510 .protocol_config
511 .object_runtime_max_num_store_entries_system_tx(),
512 self.inner.metrics.excessive_object_runtime_store_entries
513 ) {
514 return Err(PartialVMError::new(StatusCode::MEMORY_LIMIT_EXCEEDED)
515 .with_message(format!(
516 "Object runtime store limit ({} entries) reached",
517 lim
518 ))
519 .with_sub_status(
520 VMMemoryLimitExceededSubStatusCode::OBJECT_RUNTIME_STORE_LIMIT_EXCEEDED
521 as u64,
522 ));
523 };
524
525 e.insert(ChildObject {
526 owner: parent,
527 ty,
528 move_type: child_move_type,
529 value,
530 })
531 }
532 btree_map::Entry::Occupied(e) => {
533 let child_object = e.into_mut();
534 if child_object.move_type != child_move_type {
535 return Ok(ObjectResult::MismatchedType);
536 }
537 child_object
538 }
539 };
540 Ok(ObjectResult::Loaded(child_object))
541 }
542
543 pub(super) fn add_object(
544 &mut self,
545 parent: ObjectID,
546 child: ObjectID,
547 child_ty: &Type,
548 child_move_type: MoveObjectType,
549 child_value: Value,
550 ) -> PartialVMResult<()> {
551 if let LimitThresholdCrossed::Hard(_, lim) = check_limit_by_meter!(
552 self.is_metered,
553 self.store.len(),
554 self.inner
555 .protocol_config
556 .object_runtime_max_num_store_entries(),
557 self.inner
558 .protocol_config
559 .object_runtime_max_num_store_entries_system_tx(),
560 self.inner.metrics.excessive_object_runtime_store_entries
561 ) {
562 return Err(PartialVMError::new(StatusCode::MEMORY_LIMIT_EXCEEDED)
563 .with_message(format!(
564 "Object runtime store limit ({} entries) reached",
565 lim
566 ))
567 .with_sub_status(
568 VMMemoryLimitExceededSubStatusCode::OBJECT_RUNTIME_STORE_LIMIT_EXCEEDED as u64,
569 ));
570 };
571
572 let mut value = if let Some(ChildObject { value, .. }) = self.store.remove(&child) {
573 if value.exists()? {
574 return Err(
575 PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
576 .with_message(
577 "Duplicate addition of a child object. \
578 The previous value cannot be dropped. Indicates possible duplication \
579 of objects as an object was fetched more than once from two different \
580 parents, yet was not removed from one first"
581 .to_string(),
582 ),
583 );
584 }
585 value
586 } else {
587 GlobalValue::none()
588 };
589 if let Err((e, _)) = value.move_to(child_value) {
590 return Err(
591 PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR).with_message(
592 format!("Unable to set value for child {child}, with error {e}",),
593 ),
594 );
595 }
596 let child_object = ChildObject {
597 owner: parent,
598 ty: child_ty.clone(),
599 move_type: child_move_type,
600 value,
601 };
602 self.store.insert(child, child_object);
603 Ok(())
604 }
605
606 pub(super) fn config_setting_unsequenced_read(
607 &mut self,
608 config_id: ObjectID,
609 name_df_id: ObjectID,
610 _field_setting_ty: &Type,
611 field_setting_layout: &R::MoveTypeLayout,
612 field_setting_object_type: &MoveObjectType,
613 ) -> PartialVMResult<ObjectResult<Option<Value>>> {
614 let parent = config_id;
615 let child = name_df_id;
616
617 let setting = match self.config_setting_cache.entry(child) {
618 btree_map::Entry::Vacant(e) => {
619 let child_move_type = field_setting_object_type;
620 let inner = &self.inner;
621 let obj_opt =
622 fetch_child_object_unbounded!(inner, parent, child, SequenceNumber::MAX, true);
623 let Some(move_obj) = obj_opt.as_ref().map(|obj| obj.data.try_as_move().unwrap())
624 else {
625 return Ok(ObjectResult::Loaded(None));
626 };
627 let Some(value) =
628 Value::simple_deserialize(move_obj.contents(), field_setting_layout)
629 else {
630 return Err(
631 PartialVMError::new(StatusCode::FAILED_TO_DESERIALIZE_RESOURCE)
632 .with_message(format!(
633 "Failed to deserialize object {child} with type {field_setting_layout}",
634 )),
635 );
636 };
637 e.insert(ConfigSetting {
638 config: parent,
639 ty: child_move_type.clone(),
640 value,
641 })
642 }
643 btree_map::Entry::Occupied(e) => {
644 let setting = e.into_mut();
645 if setting.ty != *field_setting_object_type {
646 return Ok(ObjectResult::MismatchedType);
647 }
648 if setting.config != parent {
649 return Err(
650 PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
651 .with_message(format!(
652 "Parent for config setting changed. Potential hash collision?
653 parent: {parent},
654 child: {child},
655 setting_value_object_type: {field_setting_object_type},
656 setting: {setting:#?}"
657 )),
658 );
659 }
660 setting
661 }
662 };
663 let value = setting.value.copy_value().map_err(|e| {
664 PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR).with_message(
665 format!("Failed to copy value for config setting {child}, with error {e}",),
666 )
667 })?;
668 Ok(ObjectResult::Loaded(Some(value)))
669 }
670
671 pub(super) fn config_setting_cache_update(
675 &mut self,
676 config_id: ObjectID,
677 name_df_id: ObjectID,
678 setting_value_object_type: MoveObjectType,
679 value: Option<Value>,
680 ) {
681 let child_move_type = setting_value_object_type;
682 match value {
683 Some(value) => {
684 let setting = ConfigSetting {
685 config: config_id,
686 ty: child_move_type,
687 value,
688 };
689 self.config_setting_cache.insert(name_df_id, setting);
690 }
691 None => {
692 self.config_setting_cache.remove(&name_df_id);
693 }
694 }
695 }
696
697 pub(super) fn cached_objects(&self) -> &BTreeMap<ObjectID, Option<Object>> {
698 &self.inner.cached_objects
699 }
700
701 pub(super) fn wrapped_object_containers(&self) -> &BTreeMap<ObjectID, ObjectID> {
702 &self.inner.wrapped_object_containers
703 }
704
705 pub(super) fn take_effects(&mut self) -> BTreeMap<ObjectID, ChildObjectEffect> {
707 std::mem::take(&mut self.store)
708 .into_iter()
709 .filter_map(|(id, child_object)| {
710 let ChildObject {
711 owner,
712 ty,
713 move_type: _,
714 value,
715 } = child_object;
716 let effect = value.into_effect()?;
717 let child_effect = ChildObjectEffect { owner, ty, effect };
718 Some((id, child_effect))
719 })
720 .collect()
721 }
722
723 pub(super) fn all_active_objects(&self) -> impl Iterator<Item = ActiveChildObject<'_>> {
724 self.store.iter().map(|(id, child_object)| {
725 let copied_child_value = if child_object.value.exists().unwrap() {
726 Some(
727 child_object
728 .value
729 .borrow_global()
730 .unwrap()
731 .value_as::<StructRef>()
732 .unwrap()
733 .read_ref()
734 .unwrap(),
735 )
736 } else {
737 None
738 };
739 ActiveChildObject {
740 id,
741 owner: &child_object.owner,
742 ty: &child_object.ty,
743 move_type: &child_object.move_type,
744 copied_value: copied_child_value,
745 }
746 })
747 }
748}