1use 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
32pub const DEFAULT_LOGICAL_STACK_MAX: u32 = 65536;
34pub const DEFAULT_NATIVE_STACK_MAX: u32 = 256 * 1024 * 1024;
36
37pub const MEMORY_PAGES_MAX: u32 = 65536;
39pub const LOGICAL_MAX_LO: u32 = 1024;
41pub const LOGICAL_MAX_HI: u32 = 2 * 65536;
43pub const PRECHECK_MEM_MAX_LO: u64 = 256 * 1024 * 1024;
45pub const PRECHECK_MEM_MAX_HI: u64 = 16 * 1024 * 1024 * 1024;
47
48pub const DEFAULT_PRECHECK_PREPARATION_TIMEOUT: Duration = Duration::from_secs(60);
53pub const DEFAULT_LENIENT_PREPARATION_TIMEOUT: Duration = Duration::from_secs(360);
55pub const DEFAULT_BACKING_EXECUTION_TIMEOUT: Duration = Duration::from_secs(2);
57pub 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#[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 #[codec(index = 1)]
86 MaxMemoryPages(u32),
87 #[codec(index = 2)]
96 StackLogicalMax(u32),
97 #[codec(index = 3)]
104 StackNativeMax(u32),
105 #[codec(index = 4)]
109 PrecheckingMaxMemory(u64),
110 #[codec(index = 5)]
114 PvfPrepTimeout(PvfPrepKind, u64),
115 #[codec(index = 6)]
119 PvfExecTimeout(PvfExecKind, u64),
120 #[codec(index = 7)]
122 WasmExtBulkMemory,
123 #[codec(index = 8)]
126 EnabledHostFunction(ExecutorHostFunction),
127}
128
129#[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 #[codec(index = 1)]
150 EccRfc163,
151}
152
153#[derive(Debug)]
155pub enum ExecutorParamError {
156 DuplicatedParam(&'static str),
158 OutsideLimit(&'static str),
160 IncompatibleValues(&'static str, &'static str),
162}
163
164#[derive(Clone, Copy, Encode, Decode, Hash, Eq, PartialEq, PartialOrd, Ord, TypeInfo)]
168pub struct ExecutorParamsHash(Hash);
169
170impl ExecutorParamsHash {
171 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#[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#[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 pub fn new() -> Self {
248 ExecutorParams(vec![])
249 }
250
251 pub fn hash(&self) -> ExecutorParamsHash {
253 ExecutorParamsHash(BlakeTwo256::hash(&self.encode()))
254 }
255
256 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 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 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 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 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 ($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 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#[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}