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	/// Enables optional host functions. Multiple entries with different [`ExecutorHostFunction`]
124	/// variants can be present in the executor parameters simultaneously.
125	#[codec(index = 8)]
126	EnabledHostFunction(ExecutorHostFunction),
127}
128
129/// Optional host functions that can be enabled via [`ExecutorParam::EnabledHostFunction`].
130///
131/// Each variant represents a set of host functions that can be independently toggled on or off.
132/// New host function sets should be added here with deterministic discriminants.
133#[derive(
134	Clone,
135	Debug,
136	Encode,
137	Decode,
138	DecodeWithMemTracking,
139	PartialEq,
140	Eq,
141	TypeInfo,
142	Serialize,
143	Deserialize,
144)]
145pub enum ExecutorHostFunction {
146	/// Elliptic curve cryptography (ECC) host functions.
147	///
148	/// Specifically: BLS12-381, Ed-on-BLS12-381-Bandersnatch, Pallas, Vesta.
149	#[codec(index = 1)]
150	EccRfc163,
151}
152
153/// Possible inconsistencies of executor params.
154#[derive(Debug)]
155pub enum ExecutorParamError {
156	/// A param is duplicated.
157	DuplicatedParam(&'static str),
158	/// A param value exceeds its limitation.
159	OutsideLimit(&'static str),
160	/// Two param values are incompatible or senseless when put together.
161	IncompatibleValues(&'static str, &'static str),
162}
163
164/// Unit type wrapper around [`type@Hash`] that represents an execution parameter set hash.
165///
166/// This type is produced by [`ExecutorParams::hash`].
167#[derive(Clone, Copy, Encode, Decode, Hash, Eq, PartialEq, PartialOrd, Ord, TypeInfo)]
168pub struct ExecutorParamsHash(Hash);
169
170impl ExecutorParamsHash {
171	/// Create a new executor parameter hash from `H256` hash
172	pub fn from_hash(hash: Hash) -> Self {
173		Self(hash)
174	}
175}
176
177impl core::fmt::Display for ExecutorParamsHash {
178	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
179		self.0.fmt(f)
180	}
181}
182
183impl core::fmt::Debug for ExecutorParamsHash {
184	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
185		write!(f, "{:?}", self.0)
186	}
187}
188
189impl core::fmt::LowerHex for ExecutorParamsHash {
190	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
191		core::fmt::LowerHex::fmt(&self.0, f)
192	}
193}
194
195/// Unit type wrapper around [`type@Hash`] that represents a hash of preparation-related
196/// executor parameters.
197///
198/// This type is produced by [`ExecutorParams::prep_hash`].
199#[derive(Clone, Copy, Encode, Decode, Hash, Eq, PartialEq, PartialOrd, Ord, TypeInfo)]
200pub struct ExecutorParamsPrepHash(Hash);
201
202impl core::fmt::Display for ExecutorParamsPrepHash {
203	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
204		self.0.fmt(f)
205	}
206}
207
208impl core::fmt::Debug for ExecutorParamsPrepHash {
209	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
210		write!(f, "{:?}", self.0)
211	}
212}
213
214impl core::fmt::LowerHex for ExecutorParamsPrepHash {
215	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
216		core::fmt::LowerHex::fmt(&self.0, f)
217	}
218}
219
220/// # Deterministically serialized execution environment semantics
221/// Represents an arbitrary semantics of an arbitrary execution environment, so should be kept as
222/// abstract as possible.
223// ADR: For mandatory entries, mandatoriness should be enforced in code rather than separating them
224// into individual fields of the structure. Thus, complex migrations shall be avoided when adding
225// new entries and removing old ones. At the moment, there's no mandatory parameters defined. If
226// they show up, they must be clearly documented as mandatory ones.
227//
228// !!! Any new parameter that does not affect the prepared artifact must be added to the exclusion
229// !!! list in `prep_hash()` to avoid unnecessary artifact rebuilds.
230#[derive(
231	Clone,
232	Debug,
233	Default,
234	Encode,
235	Decode,
236	DecodeWithMemTracking,
237	PartialEq,
238	Eq,
239	TypeInfo,
240	Serialize,
241	Deserialize,
242)]
243pub struct ExecutorParams(Vec<ExecutorParam>);
244
245impl ExecutorParams {
246	/// Creates a new, empty executor parameter set
247	pub fn new() -> Self {
248		ExecutorParams(vec![])
249	}
250
251	/// Returns hash of the set of execution environment parameters
252	pub fn hash(&self) -> ExecutorParamsHash {
253		ExecutorParamsHash(BlakeTwo256::hash(&self.encode()))
254	}
255
256	/// Returns hash of preparation-related executor parameters (those affecting the artifact
257	/// produced on the preparation step).
258	pub fn prep_hash(&self) -> ExecutorParamsPrepHash {
259		use ExecutorParam::*;
260
261		let mut enc = b"prep".to_vec();
262
263		self.0
264			.iter()
265			.flat_map(|param| match param {
266				MaxMemoryPages(..) => None,
267				StackLogicalMax(..) => Some(param),
268				StackNativeMax(..) => None,
269				PrecheckingMaxMemory(..) => None,
270				PvfPrepTimeout(..) => None,
271				PvfExecTimeout(..) => None,
272				WasmExtBulkMemory => Some(param),
273				EnabledHostFunction(..) => None,
274			})
275			.for_each(|p| enc.extend(p.encode()));
276
277		ExecutorParamsPrepHash(BlakeTwo256::hash(&enc))
278	}
279
280	/// Returns a PVF preparation timeout, if any
281	pub fn pvf_prep_timeout(&self, kind: PvfPrepKind) -> Option<Duration> {
282		for param in &self.0 {
283			if let ExecutorParam::PvfPrepTimeout(k, timeout) = param {
284				if kind == *k {
285					return Some(Duration::from_millis(*timeout));
286				}
287			}
288		}
289		None
290	}
291
292	/// Returns a PVF execution timeout, if any
293	pub fn pvf_exec_timeout(&self, kind: PvfExecKind) -> Option<Duration> {
294		for param in &self.0 {
295			if let ExecutorParam::PvfExecTimeout(k, timeout) = param {
296				if kind == *k {
297					return Some(Duration::from_millis(*timeout));
298				}
299			}
300		}
301		None
302	}
303
304	/// Returns pre-checking memory limit, if any
305	pub fn prechecking_max_memory(&self) -> Option<u64> {
306		for param in &self.0 {
307			if let ExecutorParam::PrecheckingMaxMemory(limit) = param {
308				return Some(*limit);
309			}
310		}
311		None
312	}
313
314	/// Check params coherence.
315	pub fn check_consistency(&self) -> Result<(), ExecutorParamError> {
316		use ExecutorParam::*;
317		use ExecutorParamError::*;
318
319		let mut seen = BTreeMap::<&str, u64>::new();
320
321		macro_rules! check {
322			($param:ident, $val:expr $(,)?) => {
323				if seen.contains_key($param) {
324					return Err(DuplicatedParam($param));
325				}
326				seen.insert($param, $val as u64);
327			};
328
329			// should check existence before range
330			($param:ident, $val:expr, $out_of_limit:expr $(,)?) => {
331				if seen.contains_key($param) {
332					return Err(DuplicatedParam($param));
333				}
334				if $out_of_limit {
335					return Err(OutsideLimit($param));
336				}
337				seen.insert($param, $val as u64);
338			};
339		}
340
341		for param in &self.0 {
342			// should ensure to be unique
343			let param_ident = match param {
344				MaxMemoryPages(_) => "MaxMemoryPages",
345				StackLogicalMax(_) => "StackLogicalMax",
346				StackNativeMax(_) => "StackNativeMax",
347				PrecheckingMaxMemory(_) => "PrecheckingMaxMemory",
348				PvfPrepTimeout(kind, _) => match kind {
349					PvfPrepKind::Precheck => "PvfPrepKind::Precheck",
350					PvfPrepKind::Prepare => "PvfPrepKind::Prepare",
351				},
352				PvfExecTimeout(kind, _) => match kind {
353					PvfExecKind::Backing => "PvfExecKind::Backing",
354					PvfExecKind::Approval => "PvfExecKind::Approval",
355				},
356				WasmExtBulkMemory => "WasmExtBulkMemory",
357				EnabledHostFunction(hf) => match hf {
358					ExecutorHostFunction::EccRfc163 => "EnabledHostFunction::EccRfc163",
359				},
360			};
361
362			match *param {
363				MaxMemoryPages(val) => {
364					check!(param_ident, val, val == 0 || val > MEMORY_PAGES_MAX,);
365				},
366
367				StackLogicalMax(val) => {
368					check!(param_ident, val, val < LOGICAL_MAX_LO || val > LOGICAL_MAX_HI,);
369				},
370
371				StackNativeMax(val) => {
372					check!(param_ident, val);
373				},
374
375				PrecheckingMaxMemory(val) => {
376					check!(
377						param_ident,
378						val,
379						val < PRECHECK_MEM_MAX_LO || val > PRECHECK_MEM_MAX_HI,
380					);
381				},
382
383				PvfPrepTimeout(_, val) => {
384					check!(param_ident, val);
385				},
386
387				PvfExecTimeout(_, val) => {
388					check!(param_ident, val);
389				},
390
391				WasmExtBulkMemory => {
392					check!(param_ident, 1);
393				},
394
395				EnabledHostFunction(_) => {
396					check!(param_ident, 1);
397				},
398			}
399		}
400
401		if let (Some(lm), Some(nm)) = (
402			seen.get("StackLogicalMax").or(Some(&(DEFAULT_LOGICAL_STACK_MAX as u64))),
403			seen.get("StackNativeMax").or(Some(&(DEFAULT_NATIVE_STACK_MAX as u64))),
404		) {
405			if *nm < 128 * *lm {
406				return Err(IncompatibleValues("StackLogicalMax", "StackNativeMax"));
407			}
408		}
409
410		if let (Some(precheck), Some(lenient)) = (
411			seen.get("PvfPrepKind::Precheck")
412				.or(Some(&DEFAULT_PRECHECK_PREPARATION_TIMEOUT_MS)),
413			seen.get("PvfPrepKind::Prepare")
414				.or(Some(&DEFAULT_LENIENT_PREPARATION_TIMEOUT_MS)),
415		) {
416			if *precheck >= *lenient {
417				return Err(IncompatibleValues("PvfPrepKind::Precheck", "PvfPrepKind::Prepare"));
418			}
419		}
420
421		if let (Some(backing), Some(approval)) = (
422			seen.get("PvfExecKind::Backing").or(Some(&DEFAULT_BACKING_EXECUTION_TIMEOUT_MS)),
423			seen.get("PvfExecKind::Approval")
424				.or(Some(&DEFAULT_APPROVAL_EXECUTION_TIMEOUT_MS)),
425		) {
426			if *backing >= *approval {
427				return Err(IncompatibleValues("PvfExecKind::Backing", "PvfExecKind::Approval"));
428			}
429		}
430
431		Ok(())
432	}
433}
434
435impl Deref for ExecutorParams {
436	type Target = Vec<ExecutorParam>;
437
438	fn deref(&self) -> &Self::Target {
439		&self.0
440	}
441}
442
443impl From<&[ExecutorParam]> for ExecutorParams {
444	fn from(arr: &[ExecutorParam]) -> Self {
445		ExecutorParams(arr.to_vec())
446	}
447}
448
449// This test ensures the hash generated by `prep_hash()` changes if any preparation-related
450// executor parameter changes. If you're adding a new executor parameter, you must add it into
451// this test, and if changing that parameter may not affect the artifact produced on the
452// preparation step, it must be added to the list of exclusions in `pre_hash()` as well.
453// See also `prep_hash()` comments.
454#[test]
455fn ensure_prep_hash_changes() {
456	use ExecutorParam::*;
457	let ep = ExecutorParams::from(
458		&[
459			MaxMemoryPages(0),
460			StackLogicalMax(0),
461			StackNativeMax(0),
462			PrecheckingMaxMemory(0),
463			PvfPrepTimeout(PvfPrepKind::Precheck, 0),
464			PvfPrepTimeout(PvfPrepKind::Prepare, 0),
465			PvfExecTimeout(PvfExecKind::Backing, 0),
466			PvfExecTimeout(PvfExecKind::Approval, 0),
467			WasmExtBulkMemory,
468			EnabledHostFunction(ExecutorHostFunction::EccRfc163),
469		][..],
470	);
471
472	for p in ep.iter() {
473		let (ep1, ep2) = match p {
474			MaxMemoryPages(_) => continue,
475			StackLogicalMax(_) => (
476				ExecutorParams::from(&[StackLogicalMax(1)][..]),
477				ExecutorParams::from(&[StackLogicalMax(2)][..]),
478			),
479			StackNativeMax(_) => continue,
480			PrecheckingMaxMemory(_) => continue,
481			PvfPrepTimeout(_, _) => continue,
482			PvfExecTimeout(_, _) => continue,
483			WasmExtBulkMemory => {
484				(ExecutorParams::default(), ExecutorParams::from(&[WasmExtBulkMemory][..]))
485			},
486			EnabledHostFunction(_) => continue,
487		};
488
489		assert_ne!(ep1.prep_hash(), ep2.prep_hash());
490	}
491}