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