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::LimitsMetrics,
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<LimitsMetrics>,
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                };
130                match object.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(_) => Some(object),
140                }
141            } else {
142                None
143            };
144
145            if let LimitThresholdCrossed::Hard(_, lim) = check_limit_by_meter!(
146                self.is_metered,
147                cached_objects_count,
148                self.constants.object_runtime_max_num_cached_objects,
149                self.constants
150                    .object_runtime_max_num_cached_objects_system_tx,
151                self.metrics.excessive_object_runtime_cached_objects
152            ) {
153                return Err(PartialVMError::new(StatusCode::MEMORY_LIMIT_EXCEEDED)
154                    .with_message(format!(
155                        "Object runtime cached objects limit ({} entries) reached",
156                        lim
157                    ))
158                    .with_sub_status(
159                        VMMemoryLimitExceededSubStatusCode::OBJECT_RUNTIME_CACHE_LIMIT_EXCEEDED
160                            as u64,
161                    ));
162            };
163
164            e.insert(obj_opt);
165        }
166        Ok(self
167            .cached_objects
168            .get(&child)
169            .unwrap()
170            .as_ref()
171            .map(|obj| obj.data.try_as_move().unwrap()))
172    }
173
174    fn fetch_object_impl(
175        &mut self,
176        parent: ObjectID,
177        child: ObjectID,
178        child_ty: &Type,
179        child_ty_layout: &R::MoveTypeLayout,
180        child_ty_fully_annotated_layout: &A::MoveTypeLayout,
181        child_move_type: MoveObjectType,
182    ) -> PartialVMResult<ObjectResult<(Type, MoveObjectType, GlobalValue)>> {
183        let obj = match self.get_or_fetch_object_from_store(parent, child)? {
184            None => {
185                return Ok(ObjectResult::Loaded((
186                    child_ty.clone(),
187                    child_move_type,
188                    GlobalValue::none(),
189                )))
190            }
191            Some(obj) => obj,
192        };
193        // object exists, but the type does not match
194        if obj.type_() != &child_move_type {
195            return Ok(ObjectResult::MismatchedType);
196        }
197        // generate a GlobalValue
198        let obj_contents = obj.contents();
199        let v = match Value::simple_deserialize(obj_contents, child_ty_layout) {
200            Some(v) => v,
201            None => return Err(
202                PartialVMError::new(StatusCode::FAILED_TO_DESERIALIZE_RESOURCE).with_message(
203                    format!("Failed to deserialize object {child} with type {child_move_type}",),
204                ),
205            ),
206        };
207        let global_value =
208            match GlobalValue::cached(v) {
209                Ok(gv) => gv,
210                Err(e) => {
211                    return Err(PartialVMError::new(StatusCode::STORAGE_ERROR).with_message(
212                        format!("Object {child} did not deserialize to a struct Value. Error: {e}"),
213                    ))
214                }
215            };
216        // Find all UIDs inside of the value and update the object parent maps
217        let contained_uids =
218            get_all_uids(child_ty_fully_annotated_layout, obj_contents).map_err(|e| {
219                PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
220                    .with_message(format!("Failed to find UIDs. ERROR: {e}"))
221            })?;
222        let parents_root_version = self.root_version.get(&parent).copied();
223        if let Some(v) = parents_root_version {
224            debug_assert!(contained_uids.contains(&child));
225            for id in contained_uids {
226                self.root_version.insert(id, v);
227            }
228        }
229        Ok(ObjectResult::Loaded((
230            child_ty.clone(),
231            child_move_type,
232            global_value,
233        )))
234    }
235}
236
237impl<'a> ObjectStore<'a> {
238    pub(super) fn new(
239        resolver: &'a dyn ChildObjectResolver,
240        root_version: BTreeMap<ObjectID, SequenceNumber>,
241        is_metered: bool,
242        constants: LocalProtocolConfig,
243        metrics: Arc<LimitsMetrics>,
244    ) -> Self {
245        Self {
246            inner: Inner {
247                resolver,
248                root_version,
249                cached_objects: BTreeMap::new(),
250                is_metered,
251                constants: constants.clone(),
252                metrics,
253            },
254            store: BTreeMap::new(),
255            is_metered,
256            constants,
257        }
258    }
259
260    pub(super) fn object_exists(
261        &mut self,
262        parent: ObjectID,
263        child: ObjectID,
264    ) -> PartialVMResult<bool> {
265        if let Some(child_object) = self.store.get(&child) {
266            return child_object.value.exists();
267        }
268        Ok(self
269            .inner
270            .get_or_fetch_object_from_store(parent, child)?
271            .is_some())
272    }
273
274    pub(super) fn object_exists_and_has_type(
275        &mut self,
276        parent: ObjectID,
277        child: ObjectID,
278        child_move_type: &MoveObjectType,
279    ) -> PartialVMResult<bool> {
280        if let Some(child_object) = self.store.get(&child) {
281            // exists and has same type
282            return Ok(child_object.value.exists()? && &child_object.move_type == child_move_type);
283        }
284        Ok(self
285            .inner
286            .get_or_fetch_object_from_store(parent, child)?
287            .map(|move_obj| move_obj.type_() == child_move_type)
288            .unwrap_or(false))
289    }
290
291    pub(super) fn get_or_fetch_object(
292        &mut self,
293        parent: ObjectID,
294        child: ObjectID,
295        child_ty: &Type,
296        child_layout: &R::MoveTypeLayout,
297        child_fully_annotated_layout: &A::MoveTypeLayout,
298        child_move_type: MoveObjectType,
299    ) -> PartialVMResult<ObjectResult<&mut ChildObject>> {
300        let store_entries_count = self.store.len() as u64;
301        let child_object = match self.store.entry(child) {
302            btree_map::Entry::Vacant(e) => {
303                let (ty, move_type, value) = match self.inner.fetch_object_impl(
304                    parent,
305                    child,
306                    child_ty,
307                    child_layout,
308                    child_fully_annotated_layout,
309                    child_move_type,
310                )? {
311                    ObjectResult::MismatchedType => return Ok(ObjectResult::MismatchedType),
312                    ObjectResult::Loaded(res) => res,
313                };
314
315                if let LimitThresholdCrossed::Hard(_, lim) = check_limit_by_meter!(
316                    self.is_metered,
317                    store_entries_count,
318                    self.constants.object_runtime_max_num_store_entries,
319                    self.constants
320                        .object_runtime_max_num_store_entries_system_tx,
321                    self.inner.metrics.excessive_object_runtime_store_entries
322                ) {
323                    return Err(PartialVMError::new(StatusCode::MEMORY_LIMIT_EXCEEDED)
324                        .with_message(format!(
325                            "Object runtime store limit ({} entries) reached",
326                            lim
327                        ))
328                        .with_sub_status(
329                            VMMemoryLimitExceededSubStatusCode::OBJECT_RUNTIME_STORE_LIMIT_EXCEEDED
330                                as u64,
331                        ));
332                };
333
334                e.insert(ChildObject {
335                    owner: parent,
336                    ty,
337                    move_type,
338                    value,
339                })
340            }
341            btree_map::Entry::Occupied(e) => {
342                let child_object = e.into_mut();
343                if child_object.move_type != child_move_type {
344                    return Ok(ObjectResult::MismatchedType);
345                }
346                child_object
347            }
348        };
349        Ok(ObjectResult::Loaded(child_object))
350    }
351
352    pub(super) fn add_object(
353        &mut self,
354        parent: ObjectID,
355        child: ObjectID,
356        child_ty: &Type,
357        child_move_type: MoveObjectType,
358        child_value: Value,
359    ) -> PartialVMResult<()> {
360        let mut child_object = ChildObject {
361            owner: parent,
362            ty: child_ty.clone(),
363            move_type: child_move_type,
364            value: GlobalValue::none(),
365        };
366        child_object.value.move_to(child_value).unwrap();
367
368        if let LimitThresholdCrossed::Hard(_, lim) = check_limit_by_meter!(
369            self.is_metered,
370            self.store.len(),
371            self.constants.object_runtime_max_num_store_entries,
372            self.constants
373                .object_runtime_max_num_store_entries_system_tx,
374            self.inner.metrics.excessive_object_runtime_store_entries
375        ) {
376            return Err(PartialVMError::new(StatusCode::MEMORY_LIMIT_EXCEEDED)
377                .with_message(format!(
378                    "Object runtime store limit ({} entries) reached",
379                    lim
380                ))
381                .with_sub_status(
382                    VMMemoryLimitExceededSubStatusCode::OBJECT_RUNTIME_STORE_LIMIT_EXCEEDED as u64,
383                ));
384        };
385
386        if let Some(prev) = self.store.insert(child, child_object) {
387            if prev.value.exists()? {
388                return Err(
389                    PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
390                        .with_message(
391                            "Duplicate addition of a child object. \
392                            The previous value cannot be dropped. Indicates possible duplication \
393                            of objects as an object was fetched more than once from two different \
394                            parents, yet was not removed from one first"
395                                .to_string(),
396                        ),
397                );
398            }
399        }
400        Ok(())
401    }
402
403    pub(super) fn cached_objects(&self) -> &BTreeMap<ObjectID, Option<Object>> {
404        &self.inner.cached_objects
405    }
406
407    // retrieve the `Op` effects for the child objects
408    pub(super) fn take_effects(
409        &mut self,
410    ) -> (
411        BTreeMap<ObjectID, SequenceNumber>,
412        BTreeMap<ObjectID, ChildObjectEffect>,
413    ) {
414        let loaded_versions: BTreeMap<ObjectID, SequenceNumber> = self
415            .inner
416            .cached_objects
417            .iter()
418            .filter_map(|(id, obj_opt)| Some((*id, obj_opt.as_ref()?.version())))
419            .collect();
420        let child_object_effects = std::mem::take(&mut self.store)
421            .into_iter()
422            .filter_map(|(id, child_object)| {
423                let ChildObject {
424                    owner,
425                    ty,
426                    move_type: _,
427                    value,
428                } = child_object;
429                let loaded_version = loaded_versions.get(&id).copied();
430                let effect = value.into_effect()?;
431                let child_effect = ChildObjectEffect {
432                    owner,
433                    loaded_version,
434                    ty,
435                    effect,
436                };
437                Some((id, child_effect))
438            })
439            .collect();
440        (loaded_versions, child_object_effects)
441    }
442
443    pub(super) fn all_active_objects(&self) -> impl Iterator<Item = (&ObjectID, &Type, Value)> {
444        self.store.iter().filter_map(|(id, child_object)| {
445            let child_exists = child_object.value.exists().unwrap();
446            if !child_exists {
447                None
448            } else {
449                let copied_child_value = child_object
450                    .value
451                    .borrow_global()
452                    .unwrap()
453                    .value_as::<StructRef>()
454                    .unwrap()
455                    .read_ref()
456                    .unwrap();
457                Some((id, &child_object.ty, copied_child_value))
458            }
459        })
460    }
461}