sui_move_natives_v0/object_runtime/
object_store.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::object_runtime::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    error::VMMemoryLimitExceededSubStatusCode,
20    metrics::ExecutionMetrics,
21    object::{Data, MoveObject, Object, Owner},
22    storage::ChildObjectResolver,
23};
24
25use super::get_all_uids;
26pub(super) struct ChildObject {
27    pub(super) owner: ObjectID,
28    pub(super) ty: Type,
29    pub(super) move_type: MoveObjectType,
30    pub(super) value: GlobalValue,
31}
32
33#[derive(Debug)]
34pub(crate) struct ChildObjectEffect {
35    pub(super) owner: ObjectID,
36    // none if it was an input object
37    pub(super) loaded_version: Option<SequenceNumber>,
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    constants: LocalProtocolConfig,
56    // Metrics for reporting exceeded limits
57    metrics: Arc<ExecutionMetrics>,
58}
59
60// maintains the runtime GlobalValues for child objects and manages the fetching of objects
61// from storage, through the `ChildObjectResolver`
62pub(super) struct ObjectStore<'a> {
63    // contains object resolver and object cache
64    // kept as a separate struct to deal with lifetime issues where the `store` is accessed
65    // at the same time as the `cached_objects` is populated
66    inner: Inner<'a>,
67    // Maps of populated GlobalValues, meaning the child object has been accessed in this
68    // transaction
69    store: BTreeMap<ObjectID, ChildObject>,
70    // whether or not this TX is gas metered
71    is_metered: bool,
72    // Local protocol config used to enforce limits
73    constants: LocalProtocolConfig,
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
82impl Inner<'_> {
83    fn get_or_fetch_object_from_store(
84        &mut self,
85        parent: ObjectID,
86        child: ObjectID,
87    ) -> PartialVMResult<Option<&MoveObject>> {
88        let cached_objects_count = self.cached_objects.len() as u64;
89        let parents_root_version = self.root_version.get(&parent).copied();
90        let had_parent_root_version = parents_root_version.is_some();
91        // if not found, it must be new so it won't have any child objects, thus
92        // we can return SequenceNumber(0) as no child object will be found
93        let parents_root_version = parents_root_version.unwrap_or(SequenceNumber::new());
94        if let btree_map::Entry::Vacant(e) = self.cached_objects.entry(child) {
95            let child_opt = self
96                .resolver
97                .read_child_object(&parent, &child, parents_root_version)
98                .map_err(|msg| {
99                    PartialVMError::new(StatusCode::STORAGE_ERROR).with_message(format!("{msg}"))
100                })?;
101            let obj_opt = if let Some(object) = child_opt {
102                // if there was no root version, guard against reading a child object. A newly
103                // created parent should not have a child in storage
104                if !had_parent_root_version {
105                    return Err(PartialVMError::new(StatusCode::STORAGE_ERROR).with_message(
106                        format!("A new parent {parent} should not have a child object {child}."),
107                    ));
108                }
109                // guard against bugs in `read_child_object`: if it returns a child object such that
110                // C.parent != parent, we raise an invariant violation
111                match &object.owner {
112                    Owner::ObjectOwner(id) => {
113                        if ObjectID::from(*id) != parent {
114                            return Err(PartialVMError::new(StatusCode::STORAGE_ERROR).with_message(
115                                format!("Bad owner for {child}. \
116                                Expected owner {parent} but found owner {id}")
117                            ))
118                        }
119                    }
120                    Owner::AddressOwner(_) | Owner::Immutable | Owner::Shared { .. } => {
121                        return Err(PartialVMError::new(StatusCode::STORAGE_ERROR).with_message(
122                            format!("Bad owner for {child}. \
123                            Expected an id owner {parent} but found an address, immutable, or shared owner")
124                        ))
125                    }
126                    Owner::ConsensusAddressOwner { .. } => {
127                        unimplemented!("ConsensusAddressOwner does not exist for this execution version")
128                    }
129                    Owner::Party { .. } => {
130                        unimplemented!("Party does not exist for this execution version")
131                    }
132                };
133                match object.data {
134                    Data::Package(_) => {
135                        return Err(PartialVMError::new(StatusCode::STORAGE_ERROR).with_message(
136                            format!(
137                                "Mismatched object type for {child}. \
138                                Expected a Move object but found a Move package"
139                            ),
140                        ))
141                    }
142                    Data::Move(_) => Some(object),
143                }
144            } else {
145                None
146            };
147
148            if let LimitThresholdCrossed::Hard(_, lim) = check_limit_by_meter!(
149                self.is_metered,
150                cached_objects_count,
151                self.constants.object_runtime_max_num_cached_objects,
152                self.constants
153                    .object_runtime_max_num_cached_objects_system_tx,
154                self.metrics
155                    .limits_metrics
156                    .excessive_object_runtime_cached_objects
157            ) {
158                return Err(PartialVMError::new(StatusCode::MEMORY_LIMIT_EXCEEDED)
159                    .with_message(format!(
160                        "Object runtime cached objects limit ({} entries) reached",
161                        lim
162                    ))
163                    .with_sub_status(
164                        VMMemoryLimitExceededSubStatusCode::OBJECT_RUNTIME_CACHE_LIMIT_EXCEEDED
165                            as u64,
166                    ));
167            };
168
169            e.insert(obj_opt);
170        }
171        Ok(self
172            .cached_objects
173            .get(&child)
174            .unwrap()
175            .as_ref()
176            .map(|obj| obj.data.try_as_move().unwrap()))
177    }
178
179    fn fetch_object_impl(
180        &mut self,
181        parent: ObjectID,
182        child: ObjectID,
183        child_ty: &Type,
184        child_ty_layout: &R::MoveTypeLayout,
185        child_ty_fully_annotated_layout: &A::MoveTypeLayout,
186        child_move_type: MoveObjectType,
187    ) -> PartialVMResult<ObjectResult<(Type, MoveObjectType, GlobalValue)>> {
188        let obj = match self.get_or_fetch_object_from_store(parent, child)? {
189            None => {
190                return Ok(ObjectResult::Loaded((
191                    child_ty.clone(),
192                    child_move_type,
193                    GlobalValue::none(),
194                )))
195            }
196            Some(obj) => obj,
197        };
198        // object exists, but the type does not match
199        if obj.type_() != &child_move_type {
200            return Ok(ObjectResult::MismatchedType);
201        }
202        // generate a GlobalValue
203        let obj_contents = obj.contents();
204        let v = match Value::simple_deserialize(obj_contents, child_ty_layout) {
205            Some(v) => v,
206            None => return Err(
207                PartialVMError::new(StatusCode::FAILED_TO_DESERIALIZE_RESOURCE).with_message(
208                    format!("Failed to deserialize object {child} with type {child_move_type}",),
209                ),
210            ),
211        };
212        let global_value =
213            match GlobalValue::cached(v) {
214                Ok(gv) => gv,
215                Err(e) => {
216                    return Err(PartialVMError::new(StatusCode::STORAGE_ERROR).with_message(
217                        format!("Object {child} did not deserialize to a struct Value. Error: {e}"),
218                    ))
219                }
220            };
221        // Find all UIDs inside of the value and update the object parent maps
222        let contained_uids =
223            get_all_uids(child_ty_fully_annotated_layout, obj_contents).map_err(|e| {
224                PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
225                    .with_message(format!("Failed to find UIDs. ERROR: {e}"))
226            })?;
227        let parents_root_version = self.root_version.get(&parent).copied();
228        if let Some(v) = parents_root_version {
229            debug_assert!(contained_uids.contains(&child));
230            for id in contained_uids {
231                self.root_version.insert(id, v);
232            }
233        }
234        Ok(ObjectResult::Loaded((
235            child_ty.clone(),
236            child_move_type,
237            global_value,
238        )))
239    }
240}
241
242impl<'a> ObjectStore<'a> {
243    pub(super) fn new(
244        resolver: &'a dyn ChildObjectResolver,
245        root_version: BTreeMap<ObjectID, SequenceNumber>,
246        is_metered: bool,
247        constants: LocalProtocolConfig,
248        metrics: Arc<ExecutionMetrics>,
249    ) -> Self {
250        Self {
251            inner: Inner {
252                resolver,
253                root_version,
254                cached_objects: BTreeMap::new(),
255                is_metered,
256                constants: constants.clone(),
257                metrics,
258            },
259            store: BTreeMap::new(),
260            is_metered,
261            constants,
262        }
263    }
264
265    pub(super) fn object_exists(
266        &mut self,
267        parent: ObjectID,
268        child: ObjectID,
269    ) -> PartialVMResult<bool> {
270        if let Some(child_object) = self.store.get(&child) {
271            return child_object.value.exists();
272        }
273        Ok(self
274            .inner
275            .get_or_fetch_object_from_store(parent, child)?
276            .is_some())
277    }
278
279    pub(super) fn object_exists_and_has_type(
280        &mut self,
281        parent: ObjectID,
282        child: ObjectID,
283        child_move_type: &MoveObjectType,
284    ) -> PartialVMResult<bool> {
285        if let Some(child_object) = self.store.get(&child) {
286            // exists and has same type
287            return Ok(child_object.value.exists()? && &child_object.move_type == child_move_type);
288        }
289        Ok(self
290            .inner
291            .get_or_fetch_object_from_store(parent, child)?
292            .map(|move_obj| move_obj.type_() == child_move_type)
293            .unwrap_or(false))
294    }
295
296    pub(super) fn get_or_fetch_object(
297        &mut self,
298        parent: ObjectID,
299        child: ObjectID,
300        child_ty: &Type,
301        child_layout: &R::MoveTypeLayout,
302        child_fully_annotated_layout: &A::MoveTypeLayout,
303        child_move_type: MoveObjectType,
304    ) -> PartialVMResult<ObjectResult<&mut ChildObject>> {
305        let store_entries_count = self.store.len() as u64;
306        let child_object = match self.store.entry(child) {
307            btree_map::Entry::Vacant(e) => {
308                let (ty, move_type, value) = match self.inner.fetch_object_impl(
309                    parent,
310                    child,
311                    child_ty,
312                    child_layout,
313                    child_fully_annotated_layout,
314                    child_move_type,
315                )? {
316                    ObjectResult::MismatchedType => return Ok(ObjectResult::MismatchedType),
317                    ObjectResult::Loaded(res) => res,
318                };
319
320                if let LimitThresholdCrossed::Hard(_, lim) = check_limit_by_meter!(
321                    self.is_metered,
322                    store_entries_count,
323                    self.constants.object_runtime_max_num_store_entries,
324                    self.constants
325                        .object_runtime_max_num_store_entries_system_tx,
326                    self.inner
327                        .metrics
328                        .limits_metrics
329                        .excessive_object_runtime_store_entries
330                ) {
331                    return Err(PartialVMError::new(StatusCode::MEMORY_LIMIT_EXCEEDED)
332                        .with_message(format!(
333                            "Object runtime store limit ({} entries) reached",
334                            lim
335                        ))
336                        .with_sub_status(
337                            VMMemoryLimitExceededSubStatusCode::OBJECT_RUNTIME_STORE_LIMIT_EXCEEDED
338                                as u64,
339                        ));
340                };
341
342                e.insert(ChildObject {
343                    owner: parent,
344                    ty,
345                    move_type,
346                    value,
347                })
348            }
349            btree_map::Entry::Occupied(e) => {
350                let child_object = e.into_mut();
351                if child_object.move_type != child_move_type {
352                    return Ok(ObjectResult::MismatchedType);
353                }
354                child_object
355            }
356        };
357        Ok(ObjectResult::Loaded(child_object))
358    }
359
360    pub(super) fn add_object(
361        &mut self,
362        parent: ObjectID,
363        child: ObjectID,
364        child_ty: &Type,
365        child_move_type: MoveObjectType,
366        child_value: Value,
367    ) -> PartialVMResult<()> {
368        let mut child_object = ChildObject {
369            owner: parent,
370            ty: child_ty.clone(),
371            move_type: child_move_type,
372            value: GlobalValue::none(),
373        };
374        child_object.value.move_to(child_value).unwrap();
375
376        if let LimitThresholdCrossed::Hard(_, lim) = check_limit_by_meter!(
377            self.is_metered,
378            self.store.len(),
379            self.constants.object_runtime_max_num_store_entries,
380            self.constants
381                .object_runtime_max_num_store_entries_system_tx,
382            self.inner
383                .metrics
384                .limits_metrics
385                .excessive_object_runtime_store_entries
386        ) {
387            return Err(PartialVMError::new(StatusCode::MEMORY_LIMIT_EXCEEDED)
388                .with_message(format!(
389                    "Object runtime store limit ({} entries) reached",
390                    lim
391                ))
392                .with_sub_status(
393                    VMMemoryLimitExceededSubStatusCode::OBJECT_RUNTIME_STORE_LIMIT_EXCEEDED as u64,
394                ));
395        };
396
397        if let Some(prev) = self.store.insert(child, child_object) {
398            if prev.value.exists()? {
399                return Err(
400                    PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
401                        .with_message(
402                            "Duplicate addition of a child object. \
403                            The previous value cannot be dropped. Indicates possible duplication \
404                            of objects as an object was fetched more than once from two different \
405                            parents, yet was not removed from one first"
406                                .to_string(),
407                        ),
408                );
409            }
410        }
411        Ok(())
412    }
413
414    pub(super) fn cached_objects(&self) -> &BTreeMap<ObjectID, Option<Object>> {
415        &self.inner.cached_objects
416    }
417
418    // retrieve the `Op` effects for the child objects
419    pub(super) fn take_effects(
420        &mut self,
421    ) -> (
422        BTreeMap<ObjectID, SequenceNumber>,
423        BTreeMap<ObjectID, ChildObjectEffect>,
424    ) {
425        let loaded_versions: BTreeMap<ObjectID, SequenceNumber> = self
426            .inner
427            .cached_objects
428            .iter()
429            .filter_map(|(id, obj_opt)| Some((*id, obj_opt.as_ref()?.version())))
430            .collect();
431        let child_object_effects = std::mem::take(&mut self.store)
432            .into_iter()
433            .filter_map(|(id, child_object)| {
434                let ChildObject {
435                    owner,
436                    ty,
437                    move_type: _,
438                    value,
439                } = child_object;
440                let loaded_version = loaded_versions.get(&id).copied();
441                let effect = value.into_effect()?;
442                let child_effect = ChildObjectEffect {
443                    owner,
444                    loaded_version,
445                    ty,
446                    effect,
447                };
448                Some((id, child_effect))
449            })
450            .collect();
451        (loaded_versions, child_object_effects)
452    }
453
454    pub(super) fn all_active_objects(&self) -> impl Iterator<Item = (&ObjectID, &Type, Value)> {
455        self.store.iter().filter_map(|(id, child_object)| {
456            let child_exists = child_object.value.exists().unwrap();
457            if !child_exists {
458                None
459            } else {
460                let copied_child_value = child_object
461                    .value
462                    .borrow_global()
463                    .unwrap()
464                    .value_as::<StructRef>()
465                    .unwrap()
466                    .read_ref()
467                    .unwrap();
468                Some((id, &child_object.ty, copied_child_value))
469            }
470        })
471    }
472}