1use 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
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(Clone, Debug, Encode, Decode, PartialEq, Eq, TypeInfo, Serialize, Deserialize)]
71pub enum ExecutorParam {
72 #[codec(index = 1)]
75 MaxMemoryPages(u32),
76 #[codec(index = 2)]
85 StackLogicalMax(u32),
86 #[codec(index = 3)]
93 StackNativeMax(u32),
94 #[codec(index = 4)]
98 PrecheckingMaxMemory(u64),
99 #[codec(index = 5)]
103 PvfPrepTimeout(PvfPrepKind, u64),
104 #[codec(index = 6)]
108 PvfExecTimeout(PvfExecKind, u64),
109 #[codec(index = 7)]
111 WasmExtBulkMemory,
112}
113
114#[derive(Debug)]
116pub enum ExecutorParamError {
117 DuplicatedParam(&'static str),
119 OutsideLimit(&'static str),
121 IncompatibleValues(&'static str, &'static str),
123}
124
125#[derive(Clone, Copy, Encode, Decode, Hash, Eq, PartialEq, PartialOrd, Ord, TypeInfo)]
129pub struct ExecutorParamsHash(Hash);
130
131impl ExecutorParamsHash {
132 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#[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#[derive(
193 Clone, Debug, Default, Encode, Decode, PartialEq, Eq, TypeInfo, Serialize, Deserialize,
194)]
195pub struct ExecutorParams(Vec<ExecutorParam>);
196
197impl ExecutorParams {
198 pub fn new() -> Self {
200 ExecutorParams(vec![])
201 }
202
203 pub fn hash(&self) -> ExecutorParamsHash {
205 ExecutorParamsHash(BlakeTwo256::hash(&self.encode()))
206 }
207
208 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 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 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 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 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 ($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 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#[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}