1use 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 pub(super) loaded_version: Option<SequenceNumber>,
39 pub(super) ty: Type,
40 pub(super) effect: Op<Value>,
41}
42
43struct Inner<'a> {
44 resolver: &'a dyn ChildObjectResolver,
46 root_version: BTreeMap<ObjectID, SequenceNumber>,
50 cached_objects: BTreeMap<ObjectID, Option<Object>>,
53 is_metered: bool,
55 constants: LocalProtocolConfig,
57 metrics: Arc<LimitsMetrics>,
59}
60
61pub(super) struct ObjectStore<'a> {
64 inner: Inner<'a>,
68 store: BTreeMap<ObjectID, ChildObject>,
71 is_metered: bool,
73 constants: LocalProtocolConfig,
75}
76
77pub(crate) enum ObjectResult<V> {
78 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 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 !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 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 if obj.type_() != &child_move_type {
196 return Ok(ObjectResult::MismatchedType);
197 }
198 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 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 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 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}