sui_move_natives_v1/object_runtime/
object_store.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use 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    // used for loading child objects
44    resolver: &'a dyn ChildObjectResolver,
45    // The version of the root object in ownership at the beginning of the transaction.
46    // If it was a child object, it resolves to the root parent's sequence number.
47    // Otherwise, it is just the sequence number at the beginning of the transaction.
48    root_version: BTreeMap<ObjectID, SequenceNumber>,
49    // cached objects from the resolver. An object might be in this map but not in the store
50    // if it's existence was queried, but the value was not used.
51    cached_objects: BTreeMap<ObjectID, Option<Object>>,
52    // whether or not this TX is gas metered
53    is_metered: bool,
54    // Local protocol config used to enforce limits
55    local_config: LocalProtocolConfig,
56    // Metrics for reporting exceeded limits
57    metrics: Arc<LimitsMetrics>,
58    // Epoch ID for the current transaction. Used for receiving objects.
59    current_epoch_id: EpochId,
60}
61
62// maintains the runtime GlobalValues for child objects and manages the fetching of objects
63// from storage, through the `ChildObjectResolver`
64pub(super) struct ChildObjectStore<'a> {
65    // contains object resolver and object cache
66    // kept as a separate struct to deal with lifetime issues where the `store` is accessed
67    // at the same time as the `cached_objects` is populated
68    inner: Inner<'a>,
69    // Maps of populated GlobalValues, meaning the child object has been accessed in this
70    // transaction
71    store: BTreeMap<ObjectID, ChildObject>,
72    // whether or not this TX is gas metered
73    is_metered: bool,
74}
75
76pub(crate) enum ObjectResult<V> {
77    // object exists but type does not match. Should result in an abort
78    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            // guard against bugs in `receive_object_at_version`: if it returns a child object such that
99            // C.parent != parent, we raise an invariant violation since that should be checked by
100            // `receive_object_at_version`.
101            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            // `ChildObjectResolver::receive_object_at_version` should return the object at the
119            // version or nothing at all. If it returns an object with a different version, we
120            // should raise an invariant violation since it should be checked by
121            // `receive_object_at_version`.
122            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        // if not found, it must be new so it won't have any child objects, thus
157        // we can return SequenceNumber(0) as no child object will be found
158        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 there was no root version, guard against reading a child object. A newly
168                // created parent should not have a child in storage
169                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                // guard against bugs in `read_child_object`: if it returns a child object such that
175                // C.parent != parent, we raise an invariant violation
176                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 safe because we only insert Move objects
240                    .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        // object exists, but the type does not match
264        if obj.type_() != &child_move_type {
265            return Ok(ObjectResult::MismatchedType);
266        }
267        // generate a GlobalValue
268        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        // Find all UIDs inside of the value and update the object parent maps
287        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    // object exists, but the type does not match
315    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                    // Find all UIDs inside of the value and update the object parent maps with the contained
381                    // UIDs in the received value. They should all have an upper bound version as the receiving object.
382                    // Only do this if we successfully load the object though.
383                    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            // exists and has same type
421            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                // double check format did not change
534                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    // retrieve the `Op` effects for the child objects
570    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}