Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[pallet_contracts] Increase the weight of the deposit_event host function to limit the memory used by events. #4973

Merged
merged 13 commits into from
Aug 6, 2024
15 changes: 15 additions & 0 deletions prdoc/pr_4973.prdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0
# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json

title: "[pallet_contracts] Increase the weight of the deposit_event host function to limit the memory used by events."

doc:
- audience: Runtime User
description: |
This PR updates the weight of the deposit_event host function by adding
a fixed ref_time of 60,000 picoseconds per byte. Given a block time of 2 seconds
and this specified ref_time, the total allocation size is 32MB.

crates:
- name: pallet-contracts
bump: major
63 changes: 61 additions & 2 deletions substrate/frame/contracts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ use crate::{
},
gas::GasMeter,
storage::{meter::Meter as StorageMeter, ContractInfo, DeletionQueueManager},
wasm::{CodeInfo, WasmBlob},
wasm::{CodeInfo, RuntimeCosts, WasmBlob},
};
use codec::{Codec, Decode, Encode, HasCompact, MaxEncodedLen};
use core::fmt::Debug;
Expand Down Expand Up @@ -670,7 +670,66 @@ pub mod pallet {
"Debug buffer should have minimum size of {} (current setting is {})",
MIN_DEBUG_BUF_SIZE,
T::MaxDebugBufferLen::get(),
)
);

// Validators are configured to be able to use more memory than block builders. This is
// because in addition to `max_runtime_mem` they need to hold additional data in
// memory: PoV in multiple copies (1x encoded + 2x decoded) and all storage which
// includes emitted events. The assumption is that storage/events size
// can be a maximum of half of the validator runtime memory - max_runtime_mem.
let max_block_ref_time = T::BlockWeights::get()
.get(DispatchClass::Normal)
.max_total
.unwrap_or_else(|| T::BlockWeights::get().max_block)
.ref_time();
let max_payload_size = T::Schedule::get().limits.payload_len;
let max_key_size =
Key::<T>::try_from_var(alloc::vec![0u8; T::MaxStorageKeyLen::get() as usize])
.expect("Key of maximal size shall be created")
.hash()
.len() as u32;

// We can use storage to store items using the available block ref_time with the
// `set_storage` host function.
let max_storage_size: u32 = ((max_block_ref_time /
(<RuntimeCosts as gas::Token<T>>::weight(&RuntimeCosts::SetStorage {
new_bytes: max_payload_size,
old_bytes: 0,
})
.ref_time()))
.saturating_mul(max_payload_size.saturating_add(max_key_size) as u64))
.try_into()
.expect("Storage size too big");

let max_validator_runtime_mem: u32 = T::Schedule::get().limits.validator_runtime_memory;
let storage_size_limit = max_validator_runtime_mem.saturating_sub(max_runtime_mem) / 2;

assert!(
max_storage_size < storage_size_limit,
"Maximal storage size {} exceeds the storage limit {}",
max_storage_size,
storage_size_limit
);

// We can use storage to store events using the available block ref_time with the
// `deposit_event` host function. The overhead of stored events, which is around 100B,
// is not taken into account to simplify calculations, as it does not change much.
let max_events_size: u32 = ((max_block_ref_time /
(<RuntimeCosts as gas::Token<T>>::weight(&RuntimeCosts::DepositEvent {
num_topic: 0,
len: max_payload_size,
})
.ref_time()))
.saturating_mul(max_payload_size as u64))
.try_into()
.expect("Events size too big");

assert!(
max_events_size < storage_size_limit,
"Maximal events size {} exceeds the events limit {}",
max_events_size,
storage_size_limit
);
}
}

Expand Down
10 changes: 10 additions & 0 deletions substrate/frame/contracts/src/schedule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,14 @@ pub struct Limits {
/// The maximum node runtime memory. This is for integrity checks only and does not affect the
/// real setting.
pub runtime_memory: u32,

/// The maximum validator node runtime memory. This is for integrity checks only and does not
/// affect the real setting.
pub validator_runtime_memory: u32,

/// The additional ref_time added to the `deposit_event` host function call per event data
/// byte.
pub event_ref_time: u64,
}

impl Limits {
Expand Down Expand Up @@ -121,6 +129,8 @@ impl Default for Limits {
subject_len: 32,
payload_len: 16 * 1024,
runtime_memory: 1024 * 1024 * 128,
validator_runtime_memory: 1024 * 1024 * 512,
event_ref_time: 60_000,
}
}
}
Expand Down
8 changes: 7 additions & 1 deletion substrate/frame/contracts/src/wasm/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,13 @@ impl<T: Config> Token<T> for RuntimeCosts {
WeightToFee => T::WeightInfo::seal_weight_to_fee(),
Terminate(locked_dependencies) => T::WeightInfo::seal_terminate(locked_dependencies),
Random => T::WeightInfo::seal_random(),
DepositEvent { num_topic, len } => T::WeightInfo::seal_deposit_event(num_topic, len),
// Given a 2-second block time and hardcoding a `ref_time` of 60,000 picoseconds per
// byte (event_ref_time), the max allocation size is 32MB per block.
DepositEvent { num_topic, len } => T::WeightInfo::seal_deposit_event(num_topic, len)
.saturating_add(Weight::from_parts(
T::Schedule::get().limits.event_ref_time.saturating_mul(len.into()),
0,
)),
DebugMessage(len) => T::WeightInfo::seal_debug_message(len),
SetStorage { new_bytes, old_bytes } =>
cost_storage!(write, seal_set_storage, new_bytes, old_bytes),
Expand Down
Loading