use crate::{BlakeTwo256, HashT as _, PvfExecKind, PvfPrepKind};
use alloc::{collections::btree_map::BTreeMap, vec, vec::Vec};
use codec::{Decode, Encode};
use core::{ops::Deref, time::Duration};
use polkadot_core_primitives::Hash;
use scale_info::TypeInfo;
use serde::{Deserialize, Serialize};
pub const DEFAULT_LOGICAL_STACK_MAX: u32 = 65536;
pub const DEFAULT_NATIVE_STACK_MAX: u32 = 256 * 1024 * 1024;
pub const MEMORY_PAGES_MAX: u32 = 65536;
pub const LOGICAL_MAX_LO: u32 = 1024;
pub const LOGICAL_MAX_HI: u32 = 2 * 65536;
pub const PRECHECK_MEM_MAX_LO: u64 = 256 * 1024 * 1024;
pub const PRECHECK_MEM_MAX_HI: u64 = 16 * 1024 * 1024 * 1024;
pub const DEFAULT_PRECHECK_PREPARATION_TIMEOUT: Duration = Duration::from_secs(60);
pub const DEFAULT_LENIENT_PREPARATION_TIMEOUT: Duration = Duration::from_secs(360);
pub const DEFAULT_BACKING_EXECUTION_TIMEOUT: Duration = Duration::from_secs(2);
pub const DEFAULT_APPROVAL_EXECUTION_TIMEOUT: Duration = Duration::from_secs(12);
const DEFAULT_PRECHECK_PREPARATION_TIMEOUT_MS: u64 =
DEFAULT_PRECHECK_PREPARATION_TIMEOUT.as_millis() as u64;
const DEFAULT_LENIENT_PREPARATION_TIMEOUT_MS: u64 =
DEFAULT_LENIENT_PREPARATION_TIMEOUT.as_millis() as u64;
const DEFAULT_BACKING_EXECUTION_TIMEOUT_MS: u64 =
DEFAULT_BACKING_EXECUTION_TIMEOUT.as_millis() as u64;
const DEFAULT_APPROVAL_EXECUTION_TIMEOUT_MS: u64 =
DEFAULT_APPROVAL_EXECUTION_TIMEOUT.as_millis() as u64;
#[derive(Clone, Debug, Encode, Decode, PartialEq, Eq, TypeInfo, Serialize, Deserialize)]
pub enum ExecutorParam {
#[codec(index = 1)]
MaxMemoryPages(u32),
#[codec(index = 2)]
StackLogicalMax(u32),
#[codec(index = 3)]
StackNativeMax(u32),
#[codec(index = 4)]
PrecheckingMaxMemory(u64),
#[codec(index = 5)]
PvfPrepTimeout(PvfPrepKind, u64),
#[codec(index = 6)]
PvfExecTimeout(PvfExecKind, u64),
#[codec(index = 7)]
WasmExtBulkMemory,
}
#[derive(Debug)]
pub enum ExecutorParamError {
DuplicatedParam(&'static str),
OutsideLimit(&'static str),
IncompatibleValues(&'static str, &'static str),
}
#[derive(Clone, Copy, Encode, Decode, Hash, Eq, PartialEq, PartialOrd, Ord, TypeInfo)]
pub struct ExecutorParamsHash(Hash);
impl ExecutorParamsHash {
pub fn from_hash(hash: Hash) -> Self {
Self(hash)
}
}
impl core::fmt::Display for ExecutorParamsHash {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.0.fmt(f)
}
}
impl core::fmt::Debug for ExecutorParamsHash {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{:?}", self.0)
}
}
impl core::fmt::LowerHex for ExecutorParamsHash {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::LowerHex::fmt(&self.0, f)
}
}
#[derive(Clone, Copy, Encode, Decode, Hash, Eq, PartialEq, PartialOrd, Ord, TypeInfo)]
pub struct ExecutorParamsPrepHash(Hash);
impl core::fmt::Display for ExecutorParamsPrepHash {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.0.fmt(f)
}
}
impl core::fmt::Debug for ExecutorParamsPrepHash {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{:?}", self.0)
}
}
impl core::fmt::LowerHex for ExecutorParamsPrepHash {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::LowerHex::fmt(&self.0, f)
}
}
#[derive(
Clone, Debug, Default, Encode, Decode, PartialEq, Eq, TypeInfo, Serialize, Deserialize,
)]
pub struct ExecutorParams(Vec<ExecutorParam>);
impl ExecutorParams {
pub fn new() -> Self {
ExecutorParams(vec![])
}
pub fn hash(&self) -> ExecutorParamsHash {
ExecutorParamsHash(BlakeTwo256::hash(&self.encode()))
}
pub fn prep_hash(&self) -> ExecutorParamsPrepHash {
use ExecutorParam::*;
let mut enc = b"prep".to_vec();
self.0
.iter()
.flat_map(|param| match param {
MaxMemoryPages(..) => None,
StackLogicalMax(..) => Some(param),
StackNativeMax(..) => None,
PrecheckingMaxMemory(..) => None,
PvfPrepTimeout(..) => Some(param),
PvfExecTimeout(..) => None,
WasmExtBulkMemory => Some(param),
})
.for_each(|p| enc.extend(p.encode()));
ExecutorParamsPrepHash(BlakeTwo256::hash(&enc))
}
pub fn pvf_prep_timeout(&self, kind: PvfPrepKind) -> Option<Duration> {
for param in &self.0 {
if let ExecutorParam::PvfPrepTimeout(k, timeout) = param {
if kind == *k {
return Some(Duration::from_millis(*timeout))
}
}
}
None
}
pub fn pvf_exec_timeout(&self, kind: PvfExecKind) -> Option<Duration> {
for param in &self.0 {
if let ExecutorParam::PvfExecTimeout(k, timeout) = param {
if kind == *k {
return Some(Duration::from_millis(*timeout))
}
}
}
None
}
pub fn prechecking_max_memory(&self) -> Option<u64> {
for param in &self.0 {
if let ExecutorParam::PrecheckingMaxMemory(limit) = param {
return Some(*limit)
}
}
None
}
pub fn check_consistency(&self) -> Result<(), ExecutorParamError> {
use ExecutorParam::*;
use ExecutorParamError::*;
let mut seen = BTreeMap::<&str, u64>::new();
macro_rules! check {
($param:ident, $val:expr $(,)?) => {
if seen.contains_key($param) {
return Err(DuplicatedParam($param))
}
seen.insert($param, $val as u64);
};
($param:ident, $val:expr, $out_of_limit:expr $(,)?) => {
if seen.contains_key($param) {
return Err(DuplicatedParam($param))
}
if $out_of_limit {
return Err(OutsideLimit($param))
}
seen.insert($param, $val as u64);
};
}
for param in &self.0 {
let param_ident = match *param {
MaxMemoryPages(_) => "MaxMemoryPages",
StackLogicalMax(_) => "StackLogicalMax",
StackNativeMax(_) => "StackNativeMax",
PrecheckingMaxMemory(_) => "PrecheckingMaxMemory",
PvfPrepTimeout(kind, _) => match kind {
PvfPrepKind::Precheck => "PvfPrepKind::Precheck",
PvfPrepKind::Prepare => "PvfPrepKind::Prepare",
},
PvfExecTimeout(kind, _) => match kind {
PvfExecKind::Backing => "PvfExecKind::Backing",
PvfExecKind::Approval => "PvfExecKind::Approval",
},
WasmExtBulkMemory => "WasmExtBulkMemory",
};
match *param {
MaxMemoryPages(val) => {
check!(param_ident, val, val == 0 || val > MEMORY_PAGES_MAX,);
},
StackLogicalMax(val) => {
check!(param_ident, val, val < LOGICAL_MAX_LO || val > LOGICAL_MAX_HI,);
},
StackNativeMax(val) => {
check!(param_ident, val);
},
PrecheckingMaxMemory(val) => {
check!(
param_ident,
val,
val < PRECHECK_MEM_MAX_LO || val > PRECHECK_MEM_MAX_HI,
);
},
PvfPrepTimeout(_, val) => {
check!(param_ident, val);
},
PvfExecTimeout(_, val) => {
check!(param_ident, val);
},
WasmExtBulkMemory => {
check!(param_ident, 1);
},
}
}
if let (Some(lm), Some(nm)) = (
seen.get("StackLogicalMax").or(Some(&(DEFAULT_LOGICAL_STACK_MAX as u64))),
seen.get("StackNativeMax").or(Some(&(DEFAULT_NATIVE_STACK_MAX as u64))),
) {
if *nm < 128 * *lm {
return Err(IncompatibleValues("StackLogicalMax", "StackNativeMax"))
}
}
if let (Some(precheck), Some(lenient)) = (
seen.get("PvfPrepKind::Precheck")
.or(Some(&DEFAULT_PRECHECK_PREPARATION_TIMEOUT_MS)),
seen.get("PvfPrepKind::Prepare")
.or(Some(&DEFAULT_LENIENT_PREPARATION_TIMEOUT_MS)),
) {
if *precheck >= *lenient {
return Err(IncompatibleValues("PvfPrepKind::Precheck", "PvfPrepKind::Prepare"))
}
}
if let (Some(backing), Some(approval)) = (
seen.get("PvfExecKind::Backing").or(Some(&DEFAULT_BACKING_EXECUTION_TIMEOUT_MS)),
seen.get("PvfExecKind::Approval")
.or(Some(&DEFAULT_APPROVAL_EXECUTION_TIMEOUT_MS)),
) {
if *backing >= *approval {
return Err(IncompatibleValues("PvfExecKind::Backing", "PvfExecKind::Approval"))
}
}
Ok(())
}
}
impl Deref for ExecutorParams {
type Target = Vec<ExecutorParam>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl From<&[ExecutorParam]> for ExecutorParams {
fn from(arr: &[ExecutorParam]) -> Self {
ExecutorParams(arr.to_vec())
}
}
#[test]
fn ensure_prep_hash_changes() {
use ExecutorParam::*;
let ep = ExecutorParams::from(
&[
MaxMemoryPages(0),
StackLogicalMax(0),
StackNativeMax(0),
PrecheckingMaxMemory(0),
PvfPrepTimeout(PvfPrepKind::Precheck, 0),
PvfPrepTimeout(PvfPrepKind::Prepare, 0),
PvfExecTimeout(PvfExecKind::Backing, 0),
PvfExecTimeout(PvfExecKind::Approval, 0),
WasmExtBulkMemory,
][..],
);
for p in ep.iter() {
let (ep1, ep2) = match p {
MaxMemoryPages(_) => continue,
StackLogicalMax(_) => (
ExecutorParams::from(&[StackLogicalMax(1)][..]),
ExecutorParams::from(&[StackLogicalMax(2)][..]),
),
StackNativeMax(_) => continue,
PrecheckingMaxMemory(_) => continue,
PvfPrepTimeout(PvfPrepKind::Precheck, _) => (
ExecutorParams::from(&[PvfPrepTimeout(PvfPrepKind::Precheck, 1)][..]),
ExecutorParams::from(&[PvfPrepTimeout(PvfPrepKind::Precheck, 2)][..]),
),
PvfPrepTimeout(PvfPrepKind::Prepare, _) => (
ExecutorParams::from(&[PvfPrepTimeout(PvfPrepKind::Prepare, 1)][..]),
ExecutorParams::from(&[PvfPrepTimeout(PvfPrepKind::Prepare, 2)][..]),
),
PvfExecTimeout(_, _) => continue,
WasmExtBulkMemory =>
(ExecutorParams::default(), ExecutorParams::from(&[WasmExtBulkMemory][..])),
};
assert_ne!(ep1.prep_hash(), ep2.prep_hash());
}
}