Skip to content

Commit 7363600

Browse files
Fix executor params classification and add test (#10194)
Closes #8054 Executor parameters may be hashed in two ways: a full hash that changes if any parameter changes, and a prep_hash, which changes only if the parameter change will affect the prepared artifact (thus requiring artifact recompilation). As was discovered in #8054, the initial classification of parameters was wrong, and some parameters that affect the artifact were not classified as such, and vice versa. This PR aims to fix that and to add a test that should prevent this from happening in the future. --------- Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 2dc25d2 commit 7363600

File tree

6 files changed

+144
-18
lines changed

6 files changed

+144
-18
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

polkadot/node/core/pvf/common/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ seccompiler = { workspace = true }
4343

4444
[dev-dependencies]
4545
assert_matches = { workspace = true }
46+
wat = { workspace = true }
4647

4748
[target.'cfg(target_os = "linux")'.dev-dependencies]
4849
tempfile = { workspace = true }

polkadot/node/core/pvf/common/src/executor_interface.rs

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,3 +379,119 @@ impl sp_core::traits::ReadRuntimeVersion for ReadRuntimeVersion {
379379
}
380380
}
381381
}
382+
383+
#[cfg(test)]
384+
mod tests {
385+
use super::*;
386+
387+
#[test]
388+
fn prep_hash_matches_artifact_effect_of_executor_params() {
389+
use ExecutorParam::*;
390+
391+
// If you're adding a new ExecutorParam, please add it to the `cases` below.
392+
393+
let _coverage_check = |param: &ExecutorParam| match param {
394+
MaxMemoryPages(_) => true,
395+
StackLogicalMax(_) => true,
396+
StackNativeMax(_) => true,
397+
PrecheckingMaxMemory(_) => true,
398+
PvfPrepTimeout(_, _) => true,
399+
PvfExecTimeout(_, _) => true,
400+
WasmExtBulkMemory => true,
401+
};
402+
403+
// A minimal module with memory and an exported `validate_block` function.
404+
let wat = r#"(module
405+
(memory 1)
406+
(func (export "validate_block") (param i32 i32))
407+
)"#;
408+
let wasm = wat::parse_str(wat).expect("wat parsing failed");
409+
let blob = prevalidate(&wasm).expect("valid runtime blob");
410+
411+
let base = ExecutorParams::default();
412+
413+
let prepare_with = |params: &ExecutorParams| -> Vec<u8> {
414+
prepare(blob.clone(), params).expect("prepare should succeed")
415+
};
416+
417+
// Define pairs that toggle exactly one parameter.
418+
let cases: Vec<(&str, ExecutorParams, ExecutorParams)> = vec![
419+
(
420+
"MaxMemoryPages",
421+
base.clone(),
422+
ExecutorParams::from(&[ExecutorParam::MaxMemoryPages(128)][..]),
423+
),
424+
(
425+
"StackLogicalMax",
426+
base.clone(),
427+
ExecutorParams::from(
428+
&[ExecutorParam::StackLogicalMax(DEFAULT_LOGICAL_STACK_MAX + 1)][..],
429+
),
430+
),
431+
(
432+
"StackNativeMax",
433+
base.clone(),
434+
ExecutorParams::from(
435+
&[ExecutorParam::StackNativeMax(DEFAULT_NATIVE_STACK_MAX + 1024)][..],
436+
),
437+
),
438+
(
439+
"PrecheckingMaxMemory",
440+
base.clone(),
441+
ExecutorParams::from(&[ExecutorParam::PrecheckingMaxMemory(300 * 1024 * 1024)][..]),
442+
),
443+
(
444+
"PvfPrepTimeout(Precheck)",
445+
base.clone(),
446+
ExecutorParams::from(
447+
&[ExecutorParam::PvfPrepTimeout(polkadot_primitives::PvfPrepKind::Precheck, 1)]
448+
[..],
449+
),
450+
),
451+
(
452+
"PvfPrepTimeout(Prepare)",
453+
base.clone(),
454+
ExecutorParams::from(
455+
&[ExecutorParam::PvfPrepTimeout(polkadot_primitives::PvfPrepKind::Prepare, 2)]
456+
[..],
457+
),
458+
),
459+
(
460+
"PvfExecTimeout(Backing)",
461+
base.clone(),
462+
ExecutorParams::from(
463+
&[ExecutorParam::PvfExecTimeout(polkadot_primitives::PvfExecKind::Backing, 1)]
464+
[..],
465+
),
466+
),
467+
(
468+
"PvfExecTimeout(Approval)",
469+
base.clone(),
470+
ExecutorParams::from(
471+
&[ExecutorParam::PvfExecTimeout(polkadot_primitives::PvfExecKind::Approval, 2)]
472+
[..],
473+
),
474+
),
475+
(
476+
"WasmExtBulkMemory",
477+
base.clone(),
478+
ExecutorParams::from(&[ExecutorParam::WasmExtBulkMemory][..]),
479+
),
480+
];
481+
482+
for (name, a, b) in cases.into_iter() {
483+
let art_a = prepare_with(&a);
484+
let art_b = prepare_with(&b);
485+
let artifact_changed = art_a != art_b;
486+
let prep_hash_changed = a.prep_hash() != b.prep_hash();
487+
assert_eq!(
488+
artifact_changed,
489+
prep_hash_changed,
490+
"ExecutorParam classification mismatch for {}: artifact_changed={}, prep_hash_changed={}",
491+
name,
492+
artifact_changed,
493+
prep_hash_changed,
494+
);
495+
}
496+
}
497+
}

polkadot/node/core/pvf/tests/it/main.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,7 @@ use polkadot_node_primitives::{PoV, POV_BOMB_LIMIT};
2929
use polkadot_node_subsystem::messages::PvfExecKind;
3030
use polkadot_parachain_primitives::primitives::{BlockData, ValidationResult};
3131
use polkadot_primitives::{
32-
ExecutorParam, ExecutorParams, Hash, PersistedValidationData,
33-
PvfExecKind as RuntimePvfExecKind, PvfPrepKind,
32+
ExecutorParam, ExecutorParams, Hash, PersistedValidationData, PvfExecKind as RuntimePvfExecKind,
3433
};
3534
use sp_core::H256;
3635

@@ -766,8 +765,7 @@ async fn artifact_does_reprepare_on_meaningful_exec_parameter_change() {
766765
let cache_dir = host.cache_dir.path();
767766

768767
let set1 = ExecutorParams::default();
769-
let set2 =
770-
ExecutorParams::from(&[ExecutorParam::PvfPrepTimeout(PvfPrepKind::Prepare, 60000)][..]);
768+
let set2 = ExecutorParams::from(&[ExecutorParam::MaxMemoryPages(128)][..]);
771769

772770
let _stats = host
773771
.precheck_pvf(test_parachain_halt::wasm_binary_unwrap(), set1)

polkadot/primitives/src/v9/executor_params.rs

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ impl core::fmt::LowerHex for ExecutorParamsPrepHash {
199199
// they show up, they must be clearly documented as mandatory ones.
200200
//
201201
// !!! Any new parameter that does not affect the prepared artifact must be added to the exclusion
202-
// !!! list in `prep_hash()` to avoid unneccessary artifact rebuilds.
202+
// !!! list in `prep_hash()` to avoid unnecessary artifact rebuilds.
203203
#[derive(
204204
Clone,
205205
Debug,
@@ -226,7 +226,8 @@ impl ExecutorParams {
226226
ExecutorParamsHash(BlakeTwo256::hash(&self.encode()))
227227
}
228228

229-
/// Returns hash of preparation-related executor parameters
229+
/// Returns hash of preparation-related executor parameters (those affecting the artifact
230+
/// produced on the preparation step).
230231
pub fn prep_hash(&self) -> ExecutorParamsPrepHash {
231232
use ExecutorParam::*;
232233

@@ -235,11 +236,11 @@ impl ExecutorParams {
235236
self.0
236237
.iter()
237238
.flat_map(|param| match param {
238-
MaxMemoryPages(..) => None,
239+
MaxMemoryPages(..) => Some(param),
239240
StackLogicalMax(..) => Some(param),
240241
StackNativeMax(..) => None,
241242
PrecheckingMaxMemory(..) => None,
242-
PvfPrepTimeout(..) => Some(param),
243+
PvfPrepTimeout(..) => None,
243244
PvfExecTimeout(..) => None,
244245
WasmExtBulkMemory => Some(param),
245246
})
@@ -413,7 +414,7 @@ impl From<&[ExecutorParam]> for ExecutorParams {
413414
// This test ensures the hash generated by `prep_hash()` changes if any preparation-related
414415
// executor parameter changes. If you're adding a new executor parameter, you must add it into
415416
// this test, and if changing that parameter may not affect the artifact produced on the
416-
// preparation step, it must be added to the list of exlusions in `pre_hash()` as well.
417+
// preparation step, it must be added to the list of exclusions in `pre_hash()` as well.
417418
// See also `prep_hash()` comments.
418419
#[test]
419420
fn ensure_prep_hash_changes() {
@@ -434,21 +435,17 @@ fn ensure_prep_hash_changes() {
434435

435436
for p in ep.iter() {
436437
let (ep1, ep2) = match p {
437-
MaxMemoryPages(_) => continue,
438+
MaxMemoryPages(_) => (
439+
ExecutorParams::from(&[MaxMemoryPages(1)][..]),
440+
ExecutorParams::from(&[MaxMemoryPages(2)][..]),
441+
),
438442
StackLogicalMax(_) => (
439443
ExecutorParams::from(&[StackLogicalMax(1)][..]),
440444
ExecutorParams::from(&[StackLogicalMax(2)][..]),
441445
),
442446
StackNativeMax(_) => continue,
443447
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-
),
448+
PvfPrepTimeout(_, _) => continue,
452449
PvfExecTimeout(_, _) => continue,
453450
WasmExtBulkMemory =>
454451
(ExecutorParams::default(), ExecutorParams::from(&[WasmExtBulkMemory][..])),

prdoc/pr_10194.prdoc

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
title: Fix executor parameters classification
2+
doc:
3+
- audience: Node Dev
4+
description: |-
5+
Executor parameters may be hashed in two ways: a full hash that changes if any parameter changes, and a prep_hash, which changes only if the parameter change will affect the prepared artifact (thus requiring artifact recompilation).
6+
This PR aims to fix a bug in the initial classification and to add a test that should prevent those bugs from happening in the future.
7+
crates:
8+
- name: polkadot-node-core-pvf-common
9+
bump: patch
10+
- name: polkadot-node-core-pvf
11+
bump: patch
12+
- name: polkadot-primitives
13+
bump: patch

0 commit comments

Comments
 (0)