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