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