1use 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 pub(super) loaded_version: Option<SequenceNumber>,
38 pub(super) ty: Type,
39 pub(super) effect: Op<Value>,
40}
41
42struct Inner<'a> {
43 resolver: &'a dyn ChildObjectResolver,
45 root_version: BTreeMap<ObjectID, SequenceNumber>,
49 cached_objects: BTreeMap<ObjectID, Option<Object>>,
52 is_metered: bool,
54 constants: LocalProtocolConfig,
56 metrics: Arc<LimitsMetrics>,
58}
59
60pub(super) struct ObjectStore<'a> {
63 inner: Inner<'a>,
67 store: BTreeMap<ObjectID, ChildObject>,
70 is_metered: bool,
72 constants: LocalProtocolConfig,
74}
75
76pub(crate) enum ObjectResult<V> {
77 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 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 !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 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 if obj.type_() != &child_move_type {
195 return Ok(ObjectResult::MismatchedType);
196 }
197 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 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 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 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}