Skip to content

Commit

Permalink
limit object cache and store (MystenLabs#8967)
Browse files Browse the repository at this point in the history
## Description 

Limits object runtime internal sizes

## Test Plan 

Adapter transactional tests

---
If your changes are not user-facing and not a breaking change, you can
skip the following section. Otherwise, please indicate what changed, and
then add to the Release Notes section as highlighted during the release
process.

### Type of Change (Check all that apply)

- [x] user-visible impact
- [ ] breaking change for a client SDKs
- [ ] breaking change for FNs (FN binary must upgrade)
- [ ] breaking change for validators or node operators (must upgrade
binaries)
- [ ] breaking change for on-chain data layout
- [ ] necessitate either a data wipe or data migration

### Release notes
  • Loading branch information
oxade authored Mar 7, 2023
1 parent d0e0ce5 commit 532dd7a
Show file tree
Hide file tree
Showing 7 changed files with 136 additions and 2 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

// similar to dynamic_field_tests but over multiple transactions, as this uses a different code path
// test duplicate add

//# init --addresses a=0x0 --accounts A

//# publish
module a::m {

use sui::dynamic_field::add;
use sui::object;
use sui::tx_context::{sender, TxContext};

struct Obj has key {
id: object::UID,
}

public entry fun add_n_items(n: u64, ctx: &mut TxContext) {
let i = 0;
while (i < n) {
let id = object::new(ctx);
add<u64, u64>(&mut id, i, i);
sui::transfer::transfer(Obj { id }, sender(ctx));

i = i + 1;
};
}
}

//# run a::m::add_n_items --sender A --args 100

//# run a::m::add_n_items --sender A --args 1000

//# run a::m::add_n_items --sender A --args 1025
2 changes: 1 addition & 1 deletion crates/sui-framework/src/natives/object_runtime/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ impl<'a> ObjectRuntime<'a> {
protocol_config: &ProtocolConfig,
) -> Self {
Self {
object_store: ObjectStore::new(object_resolver),
object_store: ObjectStore::new(object_resolver, protocol_config, is_metered),
test_inventories: TestInventories::new(),
state: ObjectRuntimeState {
input_objects,
Expand Down
59 changes: 58 additions & 1 deletion crates/sui-framework/src/natives/object_runtime/object_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ use move_vm_types::{
values::{GlobalValue, StructRef, Value},
};
use std::collections::{btree_map, BTreeMap};
use sui_protocol_config::ProtocolConfig;
use sui_types::{
base_types::{ObjectID, SequenceNumber},
error::VMMemoryLimitExceededSubStatusCode,
object::{Data, MoveObject},
storage::ChildObjectResolver,
};
Expand All @@ -38,6 +40,10 @@ struct Inner<'a> {
// cached objects from the resolver. An object might be in this map but not in the store
// if it's existence was queried, but the value was not used.
cached_objects: BTreeMap<ObjectID, Option<MoveObject>>,
// This sets a limit on the number of cached objects
max_num_cached_objects: u64,
// whether or not this TX is gas metered
is_metered: bool,
}

// maintains the runtime GlobalValues for child objects and manages the fetching of objects
Expand All @@ -50,6 +56,10 @@ pub(super) struct ObjectStore<'a> {
// Maps of populated GlobalValues, meaning the child object has been accessed in this
// transaction
store: BTreeMap<ObjectID, ChildObject>,
// This sets a limit on the number of entries in the `store`
max_num_store_entries: u64,
// whether or not this TX is gas metered
is_metered: bool,
}

pub(crate) enum ObjectResult<V> {
Expand All @@ -64,6 +74,7 @@ impl<'a> Inner<'a> {
parent: ObjectID,
child: ObjectID,
) -> PartialVMResult<Option<&MoveObject>> {
let cached_objects_count = self.cached_objects.len() as u64;
if let btree_map::Entry::Vacant(e) = self.cached_objects.entry(child) {
let child_opt = self
.resolver
Expand All @@ -86,6 +97,19 @@ impl<'a> Inner<'a> {
} else {
None
};

if self.is_metered && cached_objects_count >= self.max_num_cached_objects {
return Err(PartialVMError::new(StatusCode::MEMORY_LIMIT_EXCEEDED)
.with_message(format!(
"Object runtime cached objects limit ({} entries) reached",
self.max_num_cached_objects
))
.with_sub_status(
VMMemoryLimitExceededSubStatusCode::OBJECT_RUNTIME_CACHE_LIMIT_EXCEEDED
as u64,
));
}

e.insert(obj_opt);
}
Ok(self.cached_objects.get(&child).unwrap().as_ref())
Expand Down Expand Up @@ -141,13 +165,21 @@ impl<'a> Inner<'a> {
}

impl<'a> ObjectStore<'a> {
pub(super) fn new(resolver: Box<dyn ChildObjectResolver + 'a>) -> Self {
pub(super) fn new(
resolver: Box<dyn ChildObjectResolver + 'a>,
protocol_config: &ProtocolConfig,
is_metered: bool,
) -> Self {
Self {
inner: Inner {
resolver,
cached_objects: BTreeMap::new(),
max_num_cached_objects: protocol_config.object_runtime_max_num_cached_objects(),
is_metered,
},
store: BTreeMap::new(),
max_num_store_entries: protocol_config.object_runtime_max_num_store_entries(),
is_metered,
}
}

Expand Down Expand Up @@ -190,6 +222,7 @@ impl<'a> ObjectStore<'a> {
child_layout: MoveTypeLayout,
child_tag: StructTag,
) -> PartialVMResult<ObjectResult<&mut ChildObject>> {
let store_entries_count = self.store.len() as u64;
let child_object = match self.store.entry(child) {
btree_map::Entry::Vacant(e) => {
let (ty, tag, value) = match self.inner.fetch_object_impl(
Expand All @@ -202,6 +235,19 @@ impl<'a> ObjectStore<'a> {
ObjectResult::MismatchedType => return Ok(ObjectResult::MismatchedType),
ObjectResult::Loaded(res) => res,
};

if self.is_metered && store_entries_count >= self.max_num_store_entries {
return Err(PartialVMError::new(StatusCode::MEMORY_LIMIT_EXCEEDED)
.with_message(format!(
"Object runtime store limit ({} entries) reached",
self.max_num_store_entries
))
.with_sub_status(
VMMemoryLimitExceededSubStatusCode::OBJECT_RUNTIME_STORE_LIMIT_EXCEEDED
as u64,
));
}

e.insert(ChildObject {
owner: parent,
ty,
Expand Down Expand Up @@ -235,6 +281,17 @@ impl<'a> ObjectStore<'a> {
value: GlobalValue::none(),
};
child_object.value.move_to(child_value).unwrap();
if self.is_metered && self.store.len() >= self.max_num_store_entries as usize {
return Err(PartialVMError::new(StatusCode::MEMORY_LIMIT_EXCEEDED)
.with_message(format!(
"Object runtime store limit ({} entries) reached",
self.max_num_store_entries
))
.with_sub_status(
VMMemoryLimitExceededSubStatusCode::OBJECT_RUNTIME_STORE_LIMIT_EXCEEDED as u64,
));
}

if let Some(prev) = self.store.insert(child, child_object) {
if prev.value.exists()? {
return Err(
Expand Down
17 changes: 17 additions & 0 deletions crates/sui-protocol-config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,13 @@ pub struct ProtocolConfig {
/// Maximum length of a vector in Move. Enforced by the VM during execution, and for constants, by the verifier.
max_move_vector_len: Option<u64>,

// === Object runtime internal operation limits ====
/// Maximum number of cached objects in the object runtime ObjectStore. Enforced by object runtime during execution
object_runtime_max_num_cached_objects: Option<u64>,

/// Maximum number of stored objects accessed by object runtime ObjectStore. Enforced by object runtime during execution
object_runtime_max_num_store_entries: Option<u64>,

// === Execution gas costs ====
// note: Option<per-instruction and native function gas costs live in the sui-cost-tables crate
/// Base cost for any Sui transaction
Expand Down Expand Up @@ -425,6 +432,14 @@ impl ProtocolConfig {
pub fn max_move_vector_len(&self) -> u64 {
self.max_move_vector_len.expect(CONSTANT_ERR_MSG)
}
pub fn object_runtime_max_num_cached_objects(&self) -> u64 {
self.object_runtime_max_num_cached_objects
.expect(CONSTANT_ERR_MSG)
}
pub fn object_runtime_max_num_store_entries(&self) -> u64 {
self.object_runtime_max_num_store_entries
.expect(CONSTANT_ERR_MSG)
}
pub fn base_tx_cost_fixed(&self) -> u64 {
self.base_tx_cost_fixed.expect(CONSTANT_ERR_MSG)
}
Expand Down Expand Up @@ -649,6 +664,8 @@ impl ProtocolConfig {
max_num_transfered_move_object_ids: Some(2048),
max_event_emit_size: Some(250 * 1024),
max_move_vector_len: Some(256 * 1024),
object_runtime_max_num_cached_objects: Some(1000),
object_runtime_max_num_store_entries: Some(1000),
base_tx_cost_fixed: Some(110_000),
package_publish_cost_fixed: Some(1_000),
base_tx_cost_per_byte: Some(0),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ max_num_deleted_move_object_ids: 2048
max_num_transfered_move_object_ids: 2048
max_event_emit_size: 256000
max_move_vector_len: 262144
object_runtime_max_num_cached_objects: 1000
object_runtime_max_num_store_entries: 1000
base_tx_cost_fixed: 110000
package_publish_cost_fixed: 1000
base_tx_cost_per_byte: 0
Expand Down
2 changes: 2 additions & 0 deletions crates/sui-types/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,8 @@ pub enum VMMemoryLimitExceededSubStatusCode {
NEW_ID_COUNT_LIMIT_EXCEEDED = 2,
DELETED_ID_COUNT_LIMIT_EXCEEDED = 3,
TRANSFER_ID_COUNT_LIMIT_EXCEEDED = 4,
OBJECT_RUNTIME_CACHE_LIMIT_EXCEEDED = 5,
OBJECT_RUNTIME_STORE_LIMIT_EXCEEDED = 6,
}

pub type SuiResult<T = ()> = Result<T, SuiError>;
Expand Down

0 comments on commit 532dd7a

Please sign in to comment.