referrerpolicy=no-referrer-when-downgrade

polkadot_primitives/v9/
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, DecodeWithMemTracking, 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(
71	Clone,
72	Debug,
73	Encode,
74	Decode,
75	DecodeWithMemTracking,
76	PartialEq,
77	Eq,
78	TypeInfo,
79	Serialize,
80	Deserialize,
81)]
82pub enum ExecutorParam {
83	/// Maximum number of memory pages (64KiB bytes per page) the executor can allocate.
84	/// A valid value lies within (0, 65536].
85	#[codec(index = 1)]
86	MaxMemoryPages(u32),
87	/// Wasm logical stack size limit (max. number of Wasm values on stack).
88	/// A valid value lies within [[`LOGICAL_MAX_LO`], [`LOGICAL_MAX_HI`]].
89	///
90	/// For WebAssembly, the stack limit is subject to implementations, meaning that it may vary on
91	/// different platforms. However, we want execution to be deterministic across machines of
92	/// different architectures, including failures like stack overflow. For deterministic
93	/// overflow, we rely on a **logical** limit, the maximum number of values allowed to be pushed
94	/// on the stack.
95	#[codec(index = 2)]
96	StackLogicalMax(u32),
97	/// Executor machine stack size limit, in bytes.
98	/// If `StackLogicalMax` is also present, a valid value should not fall below
99	/// 128 * `StackLogicalMax`.
100	///
101	/// For deterministic overflow, `StackLogicalMax` should be reached before the native stack is
102	/// exhausted.
103	#[codec(index = 3)]
104	StackNativeMax(u32),
105	/// Max. amount of memory the preparation worker is allowed to use during
106	/// pre-checking, in bytes.
107	/// Valid max. memory ranges from [`PRECHECK_MEM_MAX_LO`] to [`PRECHECK_MEM_MAX_HI`].
108	#[codec(index = 4)]
109	PrecheckingMaxMemory(u64),
110	/// PVF preparation timeouts, in millisecond.
111	/// Always ensure that `precheck_timeout` < `lenient_timeout`.
112	/// When absent, the default values will be used.
113	#[codec(index = 5)]
114	PvfPrepTimeout(PvfPrepKind, u64),
115	/// PVF execution timeouts, in millisecond.
116	/// Always ensure that `backing_timeout` < `approval_timeout`.
117	/// When absent, the default values will be used.
118	#[codec(index = 6)]
119	PvfExecTimeout(PvfExecKind, u64),
120	/// Enables WASM bulk memory proposal
121	#[codec(index = 7)]
122	WasmExtBulkMemory,
123}
124
125/// Possible inconsistencies of executor params.
126#[derive(Debug)]
127pub enum ExecutorParamError {
128	/// A param is duplicated.
129	DuplicatedParam(&'static str),
130	/// A param value exceeds its limitation.
131	OutsideLimit(&'static str),
132	/// Two param values are incompatible or senseless when put together.
133	IncompatibleValues(&'static str, &'static str),
134}
135
136/// Unit type wrapper around [`type@Hash`] that represents an execution parameter set hash.
137///
138/// This type is produced by [`ExecutorParams::hash`].
139#[derive(Clone, Copy, Encode, Decode, Hash, Eq, PartialEq, PartialOrd, Ord, TypeInfo)]
140pub struct ExecutorParamsHash(Hash);
141
142impl ExecutorParamsHash {
143	/// Create a new executor parameter hash from `H256` hash
144	pub fn from_hash(hash: Hash) -> Self {
145		Self(hash)
146	}
147}
148
149impl core::fmt::Display for ExecutorParamsHash {
150	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
151		self.0.fmt(f)
152	}
153}
154
155impl core::fmt::Debug for ExecutorParamsHash {
156	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
157		write!(f, "{:?}", self.0)
158	}
159}
160
161impl core::fmt::LowerHex for ExecutorParamsHash {
162	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
163		core::fmt::LowerHex::fmt(&self.0, f)
164	}
165}
166
167/// Unit type wrapper around [`type@Hash`] that represents a hash of preparation-related
168/// executor parameters.
169///
170/// This type is produced by [`ExecutorParams::prep_hash`].
171#[derive(Clone, Copy, Encode, Decode, Hash, Eq, PartialEq, PartialOrd, Ord, TypeInfo)]
172pub struct ExecutorParamsPrepHash(Hash);
173
174impl core::fmt::Display for ExecutorParamsPrepHash {
175	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
176		self.0.fmt(f)
177	}
178}
179
180impl core::fmt::Debug for ExecutorParamsPrepHash {
181	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
182		write!(f, "{:?}", self.0)
183	}
184}
185
186impl core::fmt::LowerHex for ExecutorParamsPrepHash {
187	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
188		core::fmt::LowerHex::fmt(&self.0, f)
189	}
190}
191
192/// # Deterministically serialized execution environment semantics
193/// Represents an arbitrary semantics of an arbitrary execution environment, so should be kept as
194/// abstract as possible.
195//
196// ADR: For mandatory entries, mandatoriness should be enforced in code rather than separating them
197// into individual fields of the structure. Thus, complex migrations shall be avoided when adding
198// new entries and removing old ones. At the moment, there's no mandatory parameters defined. If
199// they show up, they must be clearly documented as mandatory ones.
200//
201// !!! Any new parameter that does not affect the prepared artifact must be added to the exclusion
202// !!! list in `prep_hash()` to avoid unneccessary artifact rebuilds.
203#[derive(
204	Clone,
205	Debug,
206	Default,
207	Encode,
208	Decode,
209	DecodeWithMemTracking,
210	PartialEq,
211	Eq,
212	TypeInfo,
213	Serialize,
214	Deserialize,
215)]
216pub struct ExecutorParams(Vec<ExecutorParam>);
217
218impl ExecutorParams {
219	/// Creates a new, empty executor parameter set
220	pub fn new() -> Self {
221		ExecutorParams(vec![])
222	}
223
224	/// Returns hash of the set of execution environment parameters
225	pub fn hash(&self) -> ExecutorParamsHash {
226		ExecutorParamsHash(BlakeTwo256::hash(&self.encode()))
227	}
228
229	/// Returns hash of preparation-related executor parameters
230	pub fn prep_hash(&self) -> ExecutorParamsPrepHash {
231		use ExecutorParam::*;
232
233		let mut enc = b"prep".to_vec();
234
235		self.0
236			.iter()
237			.flat_map(|param| match param {
238				MaxMemoryPages(..) => None,
239				StackLogicalMax(..) => Some(param),
240				StackNativeMax(..) => None,
241				PrecheckingMaxMemory(..) => None,
242				PvfPrepTimeout(..) => Some(param),
243				PvfExecTimeout(..) => None,
244				WasmExtBulkMemory => Some(param),
245			})
246			.for_each(|p| enc.extend(p.encode()));
247
248		ExecutorParamsPrepHash(BlakeTwo256::hash(&enc))
249	}
250
251	/// Returns a PVF preparation timeout, if any
252	pub fn pvf_prep_timeout(&self, kind: PvfPrepKind) -> Option<Duration> {
253		for param in &self.0 {
254			if let ExecutorParam::PvfPrepTimeout(k, timeout) = param {
255				if kind == *k {
256					return Some(Duration::from_millis(*timeout))
257				}
258			}
259		}
260		None
261	}
262
263	/// Returns a PVF execution timeout, if any
264	pub fn pvf_exec_timeout(&self, kind: PvfExecKind) -> Option<Duration> {
265		for param in &self.0 {
266			if let ExecutorParam::PvfExecTimeout(k, timeout) = param {
267				if kind == *k {
268					return Some(Duration::from_millis(*timeout))
269				}
270			}
271		}
272		None
273	}
274
275	/// Returns pre-checking memory limit, if any
276	pub fn prechecking_max_memory(&self) -> Option<u64> {
277		for param in &self.0 {
278			if let ExecutorParam::PrecheckingMaxMemory(limit) = param {
279				return Some(*limit)
280			}
281		}
282		None
283	}
284
285	/// Check params coherence.
286	pub fn check_consistency(&self) -> Result<(), ExecutorParamError> {
287		use ExecutorParam::*;
288		use ExecutorParamError::*;
289
290		let mut seen = BTreeMap::<&str, u64>::new();
291
292		macro_rules! check {
293			($param:ident, $val:expr $(,)?) => {
294				if seen.contains_key($param) {
295					return Err(DuplicatedParam($param))
296				}
297				seen.insert($param, $val as u64);
298			};
299
300			// should check existence before range
301			($param:ident, $val:expr, $out_of_limit:expr $(,)?) => {
302				if seen.contains_key($param) {
303					return Err(DuplicatedParam($param))
304				}
305				if $out_of_limit {
306					return Err(OutsideLimit($param))
307				}
308				seen.insert($param, $val as u64);
309			};
310		}
311
312		for param in &self.0 {
313			// should ensure to be unique
314			let param_ident = match *param {
315				MaxMemoryPages(_) => "MaxMemoryPages",
316				StackLogicalMax(_) => "StackLogicalMax",
317				StackNativeMax(_) => "StackNativeMax",
318				PrecheckingMaxMemory(_) => "PrecheckingMaxMemory",
319				PvfPrepTimeout(kind, _) => match kind {
320					PvfPrepKind::Precheck => "PvfPrepKind::Precheck",
321					PvfPrepKind::Prepare => "PvfPrepKind::Prepare",
322				},
323				PvfExecTimeout(kind, _) => match kind {
324					PvfExecKind::Backing => "PvfExecKind::Backing",
325					PvfExecKind::Approval => "PvfExecKind::Approval",
326				},
327				WasmExtBulkMemory => "WasmExtBulkMemory",
328			};
329
330			match *param {
331				MaxMemoryPages(val) => {
332					check!(param_ident, val, val == 0 || val > MEMORY_PAGES_MAX,);
333				},
334
335				StackLogicalMax(val) => {
336					check!(param_ident, val, val < LOGICAL_MAX_LO || val > LOGICAL_MAX_HI,);
337				},
338
339				StackNativeMax(val) => {
340					check!(param_ident, val);
341				},
342
343				PrecheckingMaxMemory(val) => {
344					check!(
345						param_ident,
346						val,
347						val < PRECHECK_MEM_MAX_LO || val > PRECHECK_MEM_MAX_HI,
348					);
349				},
350
351				PvfPrepTimeout(_, val) => {
352					check!(param_ident, val);
353				},
354
355				PvfExecTimeout(_, val) => {
356					check!(param_ident, val);
357				},
358
359				WasmExtBulkMemory => {
360					check!(param_ident, 1);
361				},
362			}
363		}
364
365		if let (Some(lm), Some(nm)) = (
366			seen.get("StackLogicalMax").or(Some(&(DEFAULT_LOGICAL_STACK_MAX as u64))),
367			seen.get("StackNativeMax").or(Some(&(DEFAULT_NATIVE_STACK_MAX as u64))),
368		) {
369			if *nm < 128 * *lm {
370				return Err(IncompatibleValues("StackLogicalMax", "StackNativeMax"))
371			}
372		}
373
374		if let (Some(precheck), Some(lenient)) = (
375			seen.get("PvfPrepKind::Precheck")
376				.or(Some(&DEFAULT_PRECHECK_PREPARATION_TIMEOUT_MS)),
377			seen.get("PvfPrepKind::Prepare")
378				.or(Some(&DEFAULT_LENIENT_PREPARATION_TIMEOUT_MS)),
379		) {
380			if *precheck >= *lenient {
381				return Err(IncompatibleValues("PvfPrepKind::Precheck", "PvfPrepKind::Prepare"))
382			}
383		}
384
385		if let (Some(backing), Some(approval)) = (
386			seen.get("PvfExecKind::Backing").or(Some(&DEFAULT_BACKING_EXECUTION_TIMEOUT_MS)),
387			seen.get("PvfExecKind::Approval")
388				.or(Some(&DEFAULT_APPROVAL_EXECUTION_TIMEOUT_MS)),
389		) {
390			if *backing >= *approval {
391				return Err(IncompatibleValues("PvfExecKind::Backing", "PvfExecKind::Approval"))
392			}
393		}
394
395		Ok(())
396	}
397}
398
399impl Deref for ExecutorParams {
400	type Target = Vec<ExecutorParam>;
401
402	fn deref(&self) -> &Self::Target {
403		&self.0
404	}
405}
406
407impl From<&[ExecutorParam]> for ExecutorParams {
408	fn from(arr: &[ExecutorParam]) -> Self {
409		ExecutorParams(arr.to_vec())
410	}
411}
412
413// This test ensures the hash generated by `prep_hash()` changes if any preparation-related
414// executor parameter changes. If you're adding a new executor parameter, you must add it into
415// this test, and if changing that parameter may not affect the artifact produced on the
416// preparation step, it must be added to the list of exlusions in `pre_hash()` as well.
417// See also `prep_hash()` comments.
418#[test]
419fn ensure_prep_hash_changes() {
420	use ExecutorParam::*;
421	let ep = ExecutorParams::from(
422		&[
423			MaxMemoryPages(0),
424			StackLogicalMax(0),
425			StackNativeMax(0),
426			PrecheckingMaxMemory(0),
427			PvfPrepTimeout(PvfPrepKind::Precheck, 0),
428			PvfPrepTimeout(PvfPrepKind::Prepare, 0),
429			PvfExecTimeout(PvfExecKind::Backing, 0),
430			PvfExecTimeout(PvfExecKind::Approval, 0),
431			WasmExtBulkMemory,
432		][..],
433	);
434
435	for p in ep.iter() {
436		let (ep1, ep2) = match p {
437			MaxMemoryPages(_) => continue,
438			StackLogicalMax(_) => (
439				ExecutorParams::from(&[StackLogicalMax(1)][..]),
440				ExecutorParams::from(&[StackLogicalMax(2)][..]),
441			),
442			StackNativeMax(_) => continue,
443			PrecheckingMaxMemory(_) => continue,
444			PvfPrepTimeout(PvfPrepKind::Precheck, _) => (
445				ExecutorParams::from(&[PvfPrepTimeout(PvfPrepKind::Precheck, 1)][..]),
446				ExecutorParams::from(&[PvfPrepTimeout(PvfPrepKind::Precheck, 2)][..]),
447			),
448			PvfPrepTimeout(PvfPrepKind::Prepare, _) => (
449				ExecutorParams::from(&[PvfPrepTimeout(PvfPrepKind::Prepare, 1)][..]),
450				ExecutorParams::from(&[PvfPrepTimeout(PvfPrepKind::Prepare, 2)][..]),
451			),
452			PvfExecTimeout(_, _) => continue,
453			WasmExtBulkMemory =>
454				(ExecutorParams::default(), ExecutorParams::from(&[WasmExtBulkMemory][..])),
455		};
456
457		assert_ne!(ep1.prep_hash(), ep2.prep_hash());
458	}
459}