From 11fdff18e8cddcdee90b0e2c3e35a2b0124de4de Mon Sep 17 00:00:00 2001 From: Sebastian Miasojed Date: Tue, 6 Aug 2024 17:12:47 +0200 Subject: [PATCH] [pallet_contracts] Increase the weight of the deposit_event host function to limit the memory used by events. (#4973) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --------- Co-authored-by: Alexander Theißen --- prdoc/pr_4973.prdoc | 15 +++++ substrate/frame/contracts/src/lib.rs | 63 ++++++++++++++++++- substrate/frame/contracts/src/schedule.rs | 10 +++ substrate/frame/contracts/src/wasm/runtime.rs | 8 ++- 4 files changed, 93 insertions(+), 3 deletions(-) create mode 100644 prdoc/pr_4973.prdoc diff --git a/prdoc/pr_4973.prdoc b/prdoc/pr_4973.prdoc new file mode 100644 index 000000000000..20b8c94dd8a9 --- /dev/null +++ b/prdoc/pr_4973.prdoc @@ -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 diff --git a/substrate/frame/contracts/src/lib.rs b/substrate/frame/contracts/src/lib.rs index 093adc07ab48..7bb5b46cf527 100644 --- a/substrate/frame/contracts/src/lib.rs +++ b/substrate/frame/contracts/src/lib.rs @@ -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; @@ -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::::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 / + (>::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 / + (>::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 + ); } } diff --git a/substrate/frame/contracts/src/schedule.rs b/substrate/frame/contracts/src/schedule.rs index 60c9520677eb..80b8c54b1e1d 100644 --- a/substrate/frame/contracts/src/schedule.rs +++ b/substrate/frame/contracts/src/schedule.rs @@ -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 { @@ -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, } } } diff --git a/substrate/frame/contracts/src/wasm/runtime.rs b/substrate/frame/contracts/src/wasm/runtime.rs index fee127f65852..984e5712ae06 100644 --- a/substrate/frame/contracts/src/wasm/runtime.rs +++ b/substrate/frame/contracts/src/wasm/runtime.rs @@ -329,7 +329,13 @@ impl Token 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),