polkadot_primitives/v8/
executor_params.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Polkadot.
3
4// Polkadot is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Polkadot is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.
16
17//! Abstract execution environment parameter set.
18//!
19//! Parameter set is encoded as an opaque vector which structure depends on the execution
20//! environment itself (except for environment type/version which is always represented
21//! by the first element of the vector). Decoding to a usable semantics structure is
22//! done in `polkadot-node-core-pvf`.
23
24use crate::{BlakeTwo256, HashT as _, PvfExecKind, PvfPrepKind};
25use alloc::{collections::btree_map::BTreeMap, vec, vec::Vec};
26use codec::{Decode, Encode};
27use core::{ops::Deref, time::Duration};
28use polkadot_core_primitives::Hash;
29use scale_info::TypeInfo;
30use serde::{Deserialize, Serialize};
31
32/// Default maximum number of wasm values allowed for the stack during execution of a PVF.
33pub const DEFAULT_LOGICAL_STACK_MAX: u32 = 65536;
34/// Default maximum number of bytes devoted for the stack during execution of a PVF.
35pub const DEFAULT_NATIVE_STACK_MAX: u32 = 256 * 1024 * 1024;
36
37/// The limit of [`ExecutorParam::MaxMemoryPages`].
38pub const MEMORY_PAGES_MAX: u32 = 65536;
39/// The lower bound of [`ExecutorParam::StackLogicalMax`].
40pub const LOGICAL_MAX_LO: u32 = 1024;
41/// The upper bound of [`ExecutorParam::StackLogicalMax`].
42pub const LOGICAL_MAX_HI: u32 = 2 * 65536;
43/// The lower bound of [`ExecutorParam::PrecheckingMaxMemory`].
44pub const PRECHECK_MEM_MAX_LO: u64 = 256 * 1024 * 1024;
45/// The upper bound of [`ExecutorParam::PrecheckingMaxMemory`].
46pub const PRECHECK_MEM_MAX_HI: u64 = 16 * 1024 * 1024 * 1024;
47
48// Default PVF timeouts. Must never be changed! Use executor environment parameters to adjust them.
49// See also `PvfPrepKind` and `PvfExecKind` docs.
50
51/// Default PVF preparation timeout for prechecking requests.
52pub const DEFAULT_PRECHECK_PREPARATION_TIMEOUT: Duration = Duration::from_secs(60);
53/// Default PVF preparation timeout for execution requests.
54pub const DEFAULT_LENIENT_PREPARATION_TIMEOUT: Duration = Duration::from_secs(360);
55/// Default PVF execution timeout for backing.
56pub const DEFAULT_BACKING_EXECUTION_TIMEOUT: Duration = Duration::from_secs(2);
57/// Default PVF execution timeout for approval or disputes.
58pub const DEFAULT_APPROVAL_EXECUTION_TIMEOUT: Duration = Duration::from_secs(12);
59
60const DEFAULT_PRECHECK_PREPARATION_TIMEOUT_MS: u64 =
61	DEFAULT_PRECHECK_PREPARATION_TIMEOUT.as_millis() as u64;
62const DEFAULT_LENIENT_PREPARATION_TIMEOUT_MS: u64 =
63	DEFAULT_LENIENT_PREPARATION_TIMEOUT.as_millis() as u64;
64const DEFAULT_BACKING_EXECUTION_TIMEOUT_MS: u64 =
65	DEFAULT_BACKING_EXECUTION_TIMEOUT.as_millis() as u64;
66const DEFAULT_APPROVAL_EXECUTION_TIMEOUT_MS: u64 =
67	DEFAULT_APPROVAL_EXECUTION_TIMEOUT.as_millis() as u64;
68
69/// The different executor parameters for changing the execution environment semantics.
70#[derive(Clone, Debug, Encode, Decode, PartialEq, Eq, TypeInfo, Serialize, Deserialize)]
71pub enum ExecutorParam {
72	/// Maximum number of memory pages (64KiB bytes per page) the executor can allocate.
73	/// A valid value lies within (0, 65536].
74	#[codec(index = 1)]
75	MaxMemoryPages(u32),
76	/// Wasm logical stack size limit (max. number of Wasm values on stack).
77	/// A valid value lies within [[`LOGICAL_MAX_LO`], [`LOGICAL_MAX_HI`]].
78	///
79	/// For WebAssembly, the stack limit is subject to implementations, meaning that it may vary on
80	/// different platforms. However, we want execution to be deterministic across machines of
81	/// different architectures, including failures like stack overflow. For deterministic
82	/// overflow, we rely on a **logical** limit, the maximum number of values allowed to be pushed
83	/// on the stack.
84	#[codec(index = 2)]
85	StackLogicalMax(u32),
86	/// Executor machine stack size limit, in bytes.
87	/// If `StackLogicalMax` is also present, a valid value should not fall below
88	/// 128 * `StackLogicalMax`.
89	///
90	/// For deterministic overflow, `StackLogicalMax` should be reached before the native stack is
91	/// exhausted.
92	#[codec(index = 3)]
93	StackNativeMax(u32),
94	/// Max. amount of memory the preparation worker is allowed to use during
95	/// pre-checking, in bytes.
96	/// Valid max. memory ranges from [`PRECHECK_MEM_MAX_LO`] to [`PRECHECK_MEM_MAX_HI`].
97	#[codec(index = 4)]
98	PrecheckingMaxMemory(u64),
99	/// PVF preparation timeouts, in millisecond.
100	/// Always ensure that `precheck_timeout` < `lenient_timeout`.
101	/// When absent, the default values will be used.
102	#[codec(index = 5)]
103	PvfPrepTimeout(PvfPrepKind, u64),
104	/// PVF execution timeouts, in millisecond.
105	/// Always ensure that `backing_timeout` < `approval_timeout`.
106	/// When absent, the default values will be used.
107	#[codec(index = 6)]
108	PvfExecTimeout(PvfExecKind, u64),
109	/// Enables WASM bulk memory proposal
110	#[codec(index = 7)]
111	WasmExtBulkMemory,
112}
113
114/// Possible inconsistencies of executor params.
115#[derive(Debug)]
116pub enum ExecutorParamError {
117	/// A param is duplicated.
118	DuplicatedParam(&'static str),
119	/// A param value exceeds its limitation.
120	OutsideLimit(&'static str),
121	/// Two param values are incompatible or senseless when put together.
122	IncompatibleValues(&'static str, &'static str),
123}
124
125/// Unit type wrapper around [`type@Hash`] that represents an execution parameter set hash.
126///
127/// This type is produced by [`ExecutorParams::hash`].
128#[derive(Clone, Copy, Encode, Decode, Hash, Eq, PartialEq, PartialOrd, Ord, TypeInfo)]
129pub struct ExecutorParamsHash(Hash);
130
131impl ExecutorParamsHash {
132	/// Create a new executor parameter hash from `H256` hash
133	pub fn from_hash(hash: Hash) -> Self {
134		Self(hash)
135	}
136}
137
138impl core::fmt::Display for ExecutorParamsHash {
139	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
140		self.0.fmt(f)
141	}
142}
143
144impl core::fmt::Debug for ExecutorParamsHash {
145	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
146		write!(f, "{:?}", self.0)
147	}
148}
149
150impl core::fmt::LowerHex for ExecutorParamsHash {
151	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
152		core::fmt::LowerHex::fmt(&self.0, f)
153	}
154}
155
156/// Unit type wrapper around [`type@Hash`] that represents a hash of preparation-related
157/// executor parameters.
158///
159/// This type is produced by [`ExecutorParams::prep_hash`].
160#[derive(Clone, Copy, Encode, Decode, Hash, Eq, PartialEq, PartialOrd, Ord, TypeInfo)]
161pub struct ExecutorParamsPrepHash(Hash);
162
163impl core::fmt::Display for ExecutorParamsPrepHash {
164	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
165		self.0.fmt(f)
166	}
167}
168
169impl core::fmt::Debug for ExecutorParamsPrepHash {
170	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
171		write!(f, "{:?}", self.0)
172	}
173}
174
175impl core::fmt::LowerHex for ExecutorParamsPrepHash {
176	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
177		core::fmt::LowerHex::fmt(&self.0, f)
178	}
179}
180
181/// # Deterministically serialized execution environment semantics
182/// Represents an arbitrary semantics of an arbitrary execution environment, so should be kept as
183/// abstract as possible.
184//
185// ADR: For mandatory entries, mandatoriness should be enforced in code rather than separating them
186// into individual fields of the structure. Thus, complex migrations shall be avoided when adding
187// new entries and removing old ones. At the moment, there's no mandatory parameters defined. If
188// they show up, they must be clearly documented as mandatory ones.
189//
190// !!! Any new parameter that does not affect the prepared artifact must be added to the exclusion
191// !!! list in `prep_hash()` to avoid unneccessary artifact rebuilds.
192#[derive(
193	Clone, Debug, Default, Encode, Decode, PartialEq, Eq, TypeInfo, Serialize, Deserialize,
194)]
195pub struct ExecutorParams(Vec<ExecutorParam>);
196
197impl ExecutorParams {
198	/// Creates a new, empty executor parameter set
199	pub fn new() -> Self {
200		ExecutorParams(vec![])
201	}
202
203	/// Returns hash of the set of execution environment parameters
204	pub fn hash(&self) -> ExecutorParamsHash {
205		ExecutorParamsHash(BlakeTwo256::hash(&self.encode()))
206	}
207
208	/// Returns hash of preparation-related executor parameters
209	pub fn prep_hash(&self) -> ExecutorParamsPrepHash {
210		use ExecutorParam::*;
211
212		let mut enc = b"prep".to_vec();
213
214		self.0
215			.iter()
216			.flat_map(|param| match param {
217				MaxMemoryPages(..) => None,
218				StackLogicalMax(..) => Some(param),
219				StackNativeMax(..) => None,
220				PrecheckingMaxMemory(..) => None,
221				PvfPrepTimeout(..) => Some(param),
222				PvfExecTimeout(..) => None,
223				WasmExtBulkMemory => Some(param),
224			})
225			.for_each(|p| enc.extend(p.encode()));
226
227		ExecutorParamsPrepHash(BlakeTwo256::hash(&enc))
228	}
229
230	/// Returns a PVF preparation timeout, if any
231	pub fn pvf_prep_timeout(&self, kind: PvfPrepKind) -> Option<Duration> {
232		for param in &self.0 {
233			if let ExecutorParam::PvfPrepTimeout(k, timeout) = param {
234				if kind == *k {
235					return Some(Duration::from_millis(*timeout))
236				}
237			}
238		}
239		None
240	}
241
242	/// Returns a PVF execution timeout, if any
243	pub fn pvf_exec_timeout(&self, kind: PvfExecKind) -> Option<Duration> {
244		for param in &self.0 {
245			if let ExecutorParam::PvfExecTimeout(k, timeout) = param {
246				if kind == *k {
247					return Some(Duration::from_millis(*timeout))
248				}
249			}
250		}
251		None
252	}
253
254	/// Returns pre-checking memory limit, if any
255	pub fn prechecking_max_memory(&self) -> Option<u64> {
256		for param in &self.0 {
257			if let ExecutorParam::PrecheckingMaxMemory(limit) = param {
258				return Some(*limit)
259			}
260		}
261		None
262	}
263
264	/// Check params coherence.
265	pub fn check_consistency(&self) -> Result<(), ExecutorParamError> {
266		use ExecutorParam::*;
267		use ExecutorParamError::*;
268
269		let mut seen = BTreeMap::<&str, u64>::new();
270
271		macro_rules! check {
272			($param:ident, $val:expr $(,)?) => {
273				if seen.contains_key($param) {
274					return Err(DuplicatedParam($param))
275				}
276				seen.insert($param, $val as u64);
277			};
278
279			// should check existence before range
280			($param:ident, $val:expr, $out_of_limit:expr $(,)?) => {
281				if seen.contains_key($param) {
282					return Err(DuplicatedParam($param))
283				}
284				if $out_of_limit {
285					return Err(OutsideLimit($param))
286				}
287				seen.insert($param, $val as u64);
288			};
289		}
290
291		for param in &self.0 {
292			// should ensure to be unique
293			let param_ident = match *param {
294				MaxMemoryPages(_) => "MaxMemoryPages",
295				StackLogicalMax(_) => "StackLogicalMax",
296				StackNativeMax(_) => "StackNativeMax",
297				PrecheckingMaxMemory(_) => "PrecheckingMaxMemory",
298				PvfPrepTimeout(kind, _) => match kind {
299					PvfPrepKind::Precheck => "PvfPrepKind::Precheck",
300					PvfPrepKind::Prepare => "PvfPrepKind::Prepare",
301				},
302				PvfExecTimeout(kind, _) => match kind {
303					PvfExecKind::Backing => "PvfExecKind::Backing",
304					PvfExecKind::Approval => "PvfExecKind::Approval",
305				},
306				WasmExtBulkMemory => "WasmExtBulkMemory",
307			};
308
309			match *param {
310				MaxMemoryPages(val) => {
311					check!(param_ident, val, val == 0 || val > MEMORY_PAGES_MAX,);
312				},
313
314				StackLogicalMax(val) => {
315					check!(param_ident, val, val < LOGICAL_MAX_LO || val > LOGICAL_MAX_HI,);
316				},
317
318				StackNativeMax(val) => {
319					check!(param_ident, val);
320				},
321
322				PrecheckingMaxMemory(val) => {
323					check!(
324						param_ident,
325						val,
326						val < PRECHECK_MEM_MAX_LO || val > PRECHECK_MEM_MAX_HI,
327					);
328				},
329
330				PvfPrepTimeout(_, val) => {
331					check!(param_ident, val);
332				},
333
334				PvfExecTimeout(_, val) => {
335					check!(param_ident, val);
336				},
337
338				WasmExtBulkMemory => {
339					check!(param_ident, 1);
340				},
341			}
342		}
343
344		if let (Some(lm), Some(nm)) = (
345			seen.get("StackLogicalMax").or(Some(&(DEFAULT_LOGICAL_STACK_MAX as u64))),
346			seen.get("StackNativeMax").or(Some(&(DEFAULT_NATIVE_STACK_MAX as u64))),
347		) {
348			if *nm < 128 * *lm {
349				return Err(IncompatibleValues("StackLogicalMax", "StackNativeMax"))
350			}
351		}
352
353		if let (Some(precheck), Some(lenient)) = (
354			seen.get("PvfPrepKind::Precheck")
355				.or(Some(&DEFAULT_PRECHECK_PREPARATION_TIMEOUT_MS)),
356			seen.get("PvfPrepKind::Prepare")
357				.or(Some(&DEFAULT_LENIENT_PREPARATION_TIMEOUT_MS)),
358		) {
359			if *precheck >= *lenient {
360				return Err(IncompatibleValues("PvfPrepKind::Precheck", "PvfPrepKind::Prepare"))
361			}
362		}
363
364		if let (Some(backing), Some(approval)) = (
365			seen.get("PvfExecKind::Backing").or(Some(&DEFAULT_BACKING_EXECUTION_TIMEOUT_MS)),
366			seen.get("PvfExecKind::Approval")
367				.or(Some(&DEFAULT_APPROVAL_EXECUTION_TIMEOUT_MS)),
368		) {
369			if *backing >= *approval {
370				return Err(IncompatibleValues("PvfExecKind::Backing", "PvfExecKind::Approval"))
371			}
372		}
373
374		Ok(())
375	}
376}
377
378impl Deref for ExecutorParams {
379	type Target = Vec<ExecutorParam>;
380
381	fn deref(&self) -> &Self::Target {
382		&self.0
383	}
384}
385
386impl From<&[ExecutorParam]> for ExecutorParams {
387	fn from(arr: &[ExecutorParam]) -> Self {
388		ExecutorParams(arr.to_vec())
389	}
390}
391
392// This test ensures the hash generated by `prep_hash()` changes if any preparation-related
393// executor parameter changes. If you're adding a new executor parameter, you must add it into
394// this test, and if changing that parameter may not affect the artifact produced on the
395// preparation step, it must be added to the list of exlusions in `pre_hash()` as well.
396// See also `prep_hash()` comments.
397#[test]
398fn ensure_prep_hash_changes() {
399	use ExecutorParam::*;
400	let ep = ExecutorParams::from(
401		&[
402			MaxMemoryPages(0),
403			StackLogicalMax(0),
404			StackNativeMax(0),
405			PrecheckingMaxMemory(0),
406			PvfPrepTimeout(PvfPrepKind::Precheck, 0),
407			PvfPrepTimeout(PvfPrepKind::Prepare, 0),
408			PvfExecTimeout(PvfExecKind::Backing, 0),
409			PvfExecTimeout(PvfExecKind::Approval, 0),
410			WasmExtBulkMemory,
411		][..],
412	);
413
414	for p in ep.iter() {
415		let (ep1, ep2) = match p {
416			MaxMemoryPages(_) => continue,
417			StackLogicalMax(_) => (
418				ExecutorParams::from(&[StackLogicalMax(1)][..]),
419				ExecutorParams::from(&[StackLogicalMax(2)][..]),
420			),
421			StackNativeMax(_) => continue,
422			PrecheckingMaxMemory(_) => continue,
423			PvfPrepTimeout(PvfPrepKind::Precheck, _) => (
424				ExecutorParams::from(&[PvfPrepTimeout(PvfPrepKind::Precheck, 1)][..]),
425				ExecutorParams::from(&[PvfPrepTimeout(PvfPrepKind::Precheck, 2)][..]),
426			),
427			PvfPrepTimeout(PvfPrepKind::Prepare, _) => (
428				ExecutorParams::from(&[PvfPrepTimeout(PvfPrepKind::Prepare, 1)][..]),
429				ExecutorParams::from(&[PvfPrepTimeout(PvfPrepKind::Prepare, 2)][..]),
430			),
431			PvfExecTimeout(_, _) => continue,
432			WasmExtBulkMemory =>
433				(ExecutorParams::default(), ExecutorParams::from(&[WasmExtBulkMemory][..])),
434		};
435
436		assert_ne!(ep1.prep_hash(), ep2.prep_hash());
437	}
438}