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}
124
125#[derive(Debug)]
127pub enum ExecutorParamError {
128 DuplicatedParam(&'static str),
130 OutsideLimit(&'static str),
132 IncompatibleValues(&'static str, &'static str),
134}
135
136#[derive(Clone, Copy, Encode, Decode, Hash, Eq, PartialEq, PartialOrd, Ord, TypeInfo)]
140pub struct ExecutorParamsHash(Hash);
141
142impl ExecutorParamsHash {
143 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#[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#[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 pub fn new() -> Self {
221 ExecutorParams(vec![])
222 }
223
224 pub fn hash(&self) -> ExecutorParamsHash {
226 ExecutorParamsHash(BlakeTwo256::hash(&self.encode()))
227 }
228
229 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 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 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 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 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 ($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 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#[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}