referrerpolicy=no-referrer-when-downgrade

polkadot_node_core_pvf_common/
executor_interface.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Polkadot.
3
4// Polkadot is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Polkadot is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.
16
17//! Interface to the Substrate Executor
18
19use crate::error::ExecuteError;
20use polkadot_primitives::{
21	executor_params::{DEFAULT_LOGICAL_STACK_MAX, DEFAULT_NATIVE_STACK_MAX},
22	ExecutorParam, ExecutorParams,
23};
24use sc_executor_common::{
25	error::WasmError,
26	runtime_blob::RuntimeBlob,
27	wasm_runtime::{HeapAllocStrategy, WasmModule as _},
28};
29use sc_executor_wasmtime::{Config, DeterministicStackLimit, Semantics, WasmtimeRuntime};
30use sp_core::storage::{ChildInfo, StateVersion, TrackedStorageKey};
31use sp_externalities::MultiRemovalResults;
32use std::any::{Any, TypeId};
33
34// Memory configuration
35//
36// When Substrate Runtime is instantiated, a number of WASM pages are allocated for the Substrate
37// Runtime instance's linear memory. The exact number of pages is a sum of whatever the WASM blob
38// itself requests (by default at least enough to hold the data section as well as have some space
39// left for the stack; this is, of course, overridable at link time when compiling the runtime)
40// plus the number of pages specified in the `extra_heap_pages` passed to the executor.
41//
42// By default, rustc (or `lld` specifically) should allocate 1 MiB for the shadow stack, or 16
43// pages. The data section for runtimes are typically rather small and can fit in a single digit
44// number of WASM pages, so let's say an extra 16 pages. Thus let's assume that 32 pages or 2 MiB
45// are used for these needs by default.
46const DEFAULT_HEAP_PAGES_ESTIMATE: u32 = 32;
47const EXTRA_HEAP_PAGES: u32 = 2048;
48
49// VALUES OF THE DEFAULT CONFIGURATION SHOULD NEVER BE CHANGED
50// They are used as base values for the execution environment parametrization.
51// To overwrite them, add new ones to `EXECUTOR_PARAMS` in the `session_info` pallet and perform
52// a runtime upgrade to make them active.
53pub const DEFAULT_CONFIG: Config = Config {
54	allow_missing_func_imports: true,
55	cache_path: None,
56	semantics: Semantics {
57		heap_alloc_strategy: sc_executor_common::wasm_runtime::HeapAllocStrategy::Dynamic {
58			maximum_pages: Some(DEFAULT_HEAP_PAGES_ESTIMATE + EXTRA_HEAP_PAGES),
59		},
60
61		instantiation_strategy:
62			sc_executor_wasmtime::InstantiationStrategy::RecreateInstanceCopyOnWrite,
63
64		// Enable deterministic stack limit to pin down the exact number of items the wasmtime stack
65		// can contain before it traps with stack overflow.
66		//
67		// Here is how the values below were chosen.
68		//
69		// At the moment of writing, the default native stack size limit is 1 MiB. Assuming a
70		// logical item (see the docs about the field and the instrumentation algorithm) is 8 bytes,
71		// 1 MiB can fit 2x 65536 logical items.
72		//
73		// Since reaching the native stack limit is undesirable, we halve the logical item limit and
74		// also increase the native 256x. This hopefully should preclude wasm code from reaching
75		// the stack limit set by the wasmtime.
76		deterministic_stack_limit: Some(DeterministicStackLimit {
77			logical_max: DEFAULT_LOGICAL_STACK_MAX,
78			native_stack_max: DEFAULT_NATIVE_STACK_MAX,
79		}),
80		canonicalize_nans: true,
81		// Rationale for turning the multi-threaded compilation off is to make the preparation time
82		// easily reproducible and as deterministic as possible.
83		//
84		// Currently the prepare queue doesn't distinguish between precheck and prepare requests.
85		// On the one hand, it simplifies the code, on the other, however, slows down compile times
86		// for execute requests. This behavior may change in future.
87		parallel_compilation: false,
88
89		// WASM extensions. Only those that are meaningful to us may be controlled here. By default,
90		// we're using WASM MVP, which means all the extensions are disabled. Nevertheless, some
91		// extensions (e.g., sign extension ops) are enabled by Wasmtime and cannot be disabled.
92		wasm_reference_types: false,
93		wasm_simd: false,
94		wasm_bulk_memory: false,
95		wasm_multi_value: false,
96		epoch_interruption: false,
97	},
98};
99
100/// Executes the given PVF in the form of a compiled artifact and returns the result of
101/// execution upon success.
102///
103/// # Safety
104///
105/// The caller must ensure that the compiled artifact passed here was:
106///   1) produced by `prepare`,
107///   2) was not modified,
108///
109/// Failure to adhere to these requirements might lead to crashes and arbitrary code execution.
110pub unsafe fn execute_artifact(
111	compiled_artifact_blob: &[u8],
112	executor_params: &ExecutorParams,
113	params: &[u8],
114) -> Result<Vec<u8>, ExecuteError> {
115	let mut extensions = sp_externalities::Extensions::new();
116
117	extensions.register(sp_core::traits::ReadRuntimeVersionExt::new(ReadRuntimeVersion));
118
119	let mut ext = ValidationExternalities(extensions);
120
121	match sc_executor::with_externalities_safe(&mut ext, || {
122		let (semantics, _) = params_to_wasmtime_semantics(executor_params);
123		let runtime = create_runtime_from_artifact_bytes(compiled_artifact_blob, executor_params)?;
124		runtime
125			.new_instance(semantics.heap_alloc_strategy)?
126			.call("validate_block", params)
127	}) {
128		Ok(Ok(ok)) => Ok(ok),
129		Ok(Err(err)) | Err(err) => Err(err),
130	}
131}
132
133/// Constructs the runtime for the given PVF, given the artifact bytes.
134///
135/// # Safety
136///
137/// The caller must ensure that the compiled artifact passed here was:
138///   1) produced by `prepare`,
139///   2) was not modified,
140///
141/// Failure to adhere to these requirements might lead to crashes and arbitrary code execution.
142pub unsafe fn create_runtime_from_artifact_bytes(
143	compiled_artifact_blob: &[u8],
144	executor_params: &ExecutorParams,
145) -> Result<WasmtimeRuntime, WasmError> {
146	let mut config = DEFAULT_CONFIG.clone();
147	config.semantics = params_to_wasmtime_semantics(executor_params).0;
148
149	let ecc_hf_enabled = executor_params.iter().any(|p| {
150		p == &ExecutorParam::EnabledHostFunction(
151			polkadot_primitives::ExecutorHostFunction::EccRfc163,
152		)
153	});
154
155	if ecc_hf_enabled {
156		sc_executor_wasmtime::create_runtime_from_artifact_bytes::<HostFunctionsWithEcc>(
157			compiled_artifact_blob,
158			config,
159		)
160	} else {
161		sc_executor_wasmtime::create_runtime_from_artifact_bytes::<HostFunctions>(
162			compiled_artifact_blob,
163			config,
164		)
165	}
166}
167
168/// Takes the default config and overwrites any settings with existing executor parameters.
169///
170/// Returns the semantics as well as the stack limit (since we are guaranteed to have it).
171pub fn params_to_wasmtime_semantics(par: &ExecutorParams) -> (Semantics, DeterministicStackLimit) {
172	let mut sem = DEFAULT_CONFIG.semantics.clone();
173	let mut stack_limit = sem
174		.deterministic_stack_limit
175		.expect("There is a comment to not change the default stack limit; it should always be available; qed")
176		.clone();
177
178	for p in par.iter() {
179		match p {
180			ExecutorParam::MaxMemoryPages(max_pages) => {
181				sem.heap_alloc_strategy = HeapAllocStrategy::Dynamic {
182					maximum_pages: Some((*max_pages).saturating_add(DEFAULT_HEAP_PAGES_ESTIMATE)),
183				}
184			},
185			ExecutorParam::StackLogicalMax(slm) => stack_limit.logical_max = *slm,
186			ExecutorParam::StackNativeMax(snm) => stack_limit.native_stack_max = *snm,
187			ExecutorParam::WasmExtBulkMemory => sem.wasm_bulk_memory = true,
188			ExecutorParam::PrecheckingMaxMemory(_) |
189			ExecutorParam::PvfPrepTimeout(_, _) |
190			ExecutorParam::PvfExecTimeout(_, _) |
191			ExecutorParam::EnabledHostFunction(_) => (), // Not used here
192		}
193	}
194	sem.deterministic_stack_limit = Some(stack_limit.clone());
195	(sem, stack_limit)
196}
197
198/// Runs the prevalidation on the given code. Returns a [`RuntimeBlob`] if it succeeds.
199pub fn prevalidate(code: &[u8]) -> Result<RuntimeBlob, sc_executor_common::error::WasmError> {
200	// Construct the runtime blob and do some basic checks for consistency.
201	let blob = RuntimeBlob::new(code)?;
202	// In the future this function should take care of any further prevalidation logic.
203	Ok(blob)
204}
205
206/// Runs preparation on the given runtime blob. If successful, it returns a serialized compiled
207/// artifact which can then be used to pass into `Executor::execute` after writing it to the disk.
208pub fn prepare(
209	blob: RuntimeBlob,
210	executor_params: &ExecutorParams,
211) -> Result<Vec<u8>, sc_executor_common::error::WasmError> {
212	let (semantics, _) = params_to_wasmtime_semantics(executor_params);
213	sc_executor_wasmtime::prepare_runtime_artifact(blob, &semantics)
214}
215
216/// Available host functions. We leave out:
217///
218/// 1. storage related stuff (PVF doesn't have a notion of a persistent storage/trie)
219/// 2. tracing
220/// 3. off chain workers (PVFs do not have such a notion)
221/// 4. runtime tasks
222/// 5. sandbox
223type HostFunctions = (
224	sp_io::misc::HostFunctions,
225	sp_io::crypto::HostFunctions,
226	sp_io::hashing::HostFunctions,
227	sp_io::allocator::HostFunctions,
228	sp_io::logging::HostFunctions,
229	sp_io::trie::HostFunctions,
230);
231
232/// Host functions with ECC (elliptic curve cryptography) support.
233/// Only used when `ExecutorParam::EnabledHostFunction(ExecutorHostFunction::EccRfc163)` is present.
234type HostFunctionsWithEcc = (HostFunctions, sp_crypto_ec_utils::HostFunctionsRfc163);
235
236/// The validation externalities that will panic on any storage related access. (PVFs should not
237/// have a notion of a persistent storage/trie.)
238struct ValidationExternalities(sp_externalities::Extensions);
239
240impl sp_externalities::Externalities for ValidationExternalities {
241	fn storage(&mut self, _: &[u8]) -> Option<Vec<u8>> {
242		panic!("storage: unsupported feature for parachain validation")
243	}
244
245	fn storage_hash(&mut self, _: &[u8]) -> Option<Vec<u8>> {
246		panic!("storage_hash: unsupported feature for parachain validation")
247	}
248
249	fn child_storage_hash(&mut self, _: &ChildInfo, _: &[u8]) -> Option<Vec<u8>> {
250		panic!("child_storage_hash: unsupported feature for parachain validation")
251	}
252
253	fn child_storage(&mut self, _: &ChildInfo, _: &[u8]) -> Option<Vec<u8>> {
254		panic!("child_storage: unsupported feature for parachain validation")
255	}
256
257	fn kill_child_storage(
258		&mut self,
259		_child_info: &ChildInfo,
260		_maybe_limit: Option<u32>,
261		_maybe_cursor: Option<&[u8]>,
262	) -> MultiRemovalResults {
263		panic!("kill_child_storage: unsupported feature for parachain validation")
264	}
265
266	fn clear_prefix(
267		&mut self,
268		_prefix: &[u8],
269		_maybe_limit: Option<u32>,
270		_maybe_cursor: Option<&[u8]>,
271	) -> MultiRemovalResults {
272		panic!("clear_prefix: unsupported feature for parachain validation")
273	}
274
275	fn clear_child_prefix(
276		&mut self,
277		_child_info: &ChildInfo,
278		_prefix: &[u8],
279		_maybe_limit: Option<u32>,
280		_maybe_cursor: Option<&[u8]>,
281	) -> MultiRemovalResults {
282		panic!("clear_child_prefix: unsupported feature for parachain validation")
283	}
284
285	fn place_storage(&mut self, _: Vec<u8>, _: Option<Vec<u8>>) {
286		panic!("place_storage: unsupported feature for parachain validation")
287	}
288
289	fn place_child_storage(&mut self, _: &ChildInfo, _: Vec<u8>, _: Option<Vec<u8>>) {
290		panic!("place_child_storage: unsupported feature for parachain validation")
291	}
292
293	fn storage_root(&mut self) -> Vec<u8> {
294		panic!("storage_root: unsupported feature for parachain validation")
295	}
296
297	fn storage_root_with_version(&mut self, _: StateVersion) -> Vec<u8> {
298		panic!("storage_root_with_version: unsupported feature for parachain validation")
299	}
300
301	fn child_storage_root(&mut self, _: &ChildInfo) -> Vec<u8> {
302		panic!("child_storage_root: unsupported feature for parachain validation")
303	}
304
305	fn child_storage_root_with_version(&mut self, _: &ChildInfo, _: StateVersion) -> Vec<u8> {
306		panic!("child_storage_root_with_version: unsupported feature for parachain validation")
307	}
308
309	fn next_child_storage_key(&mut self, _: &ChildInfo, _: &[u8]) -> Option<Vec<u8>> {
310		panic!("next_child_storage_key: unsupported feature for parachain validation")
311	}
312
313	fn next_storage_key(&mut self, _: &[u8]) -> Option<Vec<u8>> {
314		panic!("next_storage_key: unsupported feature for parachain validation")
315	}
316
317	fn storage_append(&mut self, _key: Vec<u8>, _value: Vec<u8>) {
318		panic!("storage_append: unsupported feature for parachain validation")
319	}
320
321	fn storage_start_transaction(&mut self) {
322		panic!("storage_start_transaction: unsupported feature for parachain validation")
323	}
324
325	fn storage_rollback_transaction(&mut self) -> Result<(), ()> {
326		panic!("storage_rollback_transaction: unsupported feature for parachain validation")
327	}
328
329	fn storage_commit_transaction(&mut self) -> Result<(), ()> {
330		panic!("storage_commit_transaction: unsupported feature for parachain validation")
331	}
332
333	fn wipe(&mut self) {
334		panic!("wipe: unsupported feature for parachain validation")
335	}
336
337	fn commit(&mut self) {
338		panic!("commit: unsupported feature for parachain validation")
339	}
340
341	fn read_write_count(&self) -> (u32, u32, u32, u32) {
342		panic!("read_write_count: unsupported feature for parachain validation")
343	}
344
345	fn reset_read_write_count(&mut self) {
346		panic!("reset_read_write_count: unsupported feature for parachain validation")
347	}
348
349	fn get_whitelist(&self) -> Vec<TrackedStorageKey> {
350		panic!("get_whitelist: unsupported feature for parachain validation")
351	}
352
353	fn set_whitelist(&mut self, _: Vec<TrackedStorageKey>) {
354		panic!("set_whitelist: unsupported feature for parachain validation")
355	}
356
357	fn set_offchain_storage(&mut self, _: &[u8], _: std::option::Option<&[u8]>) {
358		panic!("set_offchain_storage: unsupported feature for parachain validation")
359	}
360
361	fn get_read_and_written_keys(&self) -> Vec<(Vec<u8>, u32, u32, bool)> {
362		panic!("get_read_and_written_keys: unsupported feature for parachain validation")
363	}
364}
365
366impl sp_externalities::ExtensionStore for ValidationExternalities {
367	fn extension_by_type_id(&mut self, type_id: TypeId) -> Option<&mut dyn Any> {
368		self.0.get_mut(type_id)
369	}
370
371	fn register_extension_with_type_id(
372		&mut self,
373		type_id: TypeId,
374		extension: Box<dyn sp_externalities::Extension>,
375	) -> Result<(), sp_externalities::Error> {
376		self.0.register_with_type_id(type_id, extension)
377	}
378
379	fn deregister_extension_by_type_id(
380		&mut self,
381		type_id: TypeId,
382	) -> Result<(), sp_externalities::Error> {
383		if self.0.deregister(type_id) {
384			Ok(())
385		} else {
386			Err(sp_externalities::Error::ExtensionIsNotRegistered(type_id))
387		}
388	}
389}
390
391struct ReadRuntimeVersion;
392
393impl sp_core::traits::ReadRuntimeVersion for ReadRuntimeVersion {
394	fn read_runtime_version(
395		&self,
396		wasm_code: &[u8],
397		_ext: &mut dyn sp_externalities::Externalities,
398	) -> Result<Vec<u8>, String> {
399		let blob = RuntimeBlob::uncompress_if_needed(wasm_code)
400			.map_err(|e| format!("Failed to read the PVF runtime blob: {:?}", e))?;
401
402		match sc_executor::read_embedded_version(&blob)
403			.map_err(|e| format!("Failed to read the static section from the PVF blob: {:?}", e))?
404		{
405			Some(version) => {
406				use codec::Encode;
407				Ok(version.encode())
408			},
409			None => Err("runtime version section is not found".to_string()),
410		}
411	}
412}
413
414#[cfg(test)]
415mod tests {
416	use super::*;
417
418	#[test]
419	fn prep_hash_matches_artifact_effect_of_executor_params() {
420		use ExecutorParam::*;
421
422		// If you're adding a new ExecutorParam, please add it to the `cases` below.
423
424		let _coverage_check = |param: &ExecutorParam| match param {
425			MaxMemoryPages(_) => true,
426			StackLogicalMax(_) => true,
427			StackNativeMax(_) => true,
428			PrecheckingMaxMemory(_) => true,
429			PvfPrepTimeout(_, _) => true,
430			PvfExecTimeout(_, _) => true,
431			WasmExtBulkMemory => true,
432			EnabledHostFunction(_) => true,
433		};
434
435		// A minimal module with memory and an exported `validate_block` function.
436		let wat = r#"(module
437			(memory 1)
438			(func (export "validate_block") (param i32 i32))
439		)"#;
440		let wasm = wat::parse_str(wat).expect("wat parsing failed");
441		let blob = prevalidate(&wasm).expect("valid runtime blob");
442
443		let base = ExecutorParams::default();
444
445		let prepare_with = |params: &ExecutorParams| -> Vec<u8> {
446			prepare(blob.clone(), params).expect("prepare should succeed")
447		};
448
449		// Define pairs that toggle exactly one parameter.
450		let cases: Vec<(&str, ExecutorParams, ExecutorParams)> = vec![
451			(
452				"MaxMemoryPages",
453				base.clone(),
454				ExecutorParams::from(&[ExecutorParam::MaxMemoryPages(128)][..]),
455			),
456			(
457				"StackLogicalMax",
458				base.clone(),
459				ExecutorParams::from(
460					&[ExecutorParam::StackLogicalMax(DEFAULT_LOGICAL_STACK_MAX + 1)][..],
461				),
462			),
463			(
464				"StackNativeMax",
465				base.clone(),
466				ExecutorParams::from(
467					&[ExecutorParam::StackNativeMax(DEFAULT_NATIVE_STACK_MAX + 1024)][..],
468				),
469			),
470			(
471				"PrecheckingMaxMemory",
472				base.clone(),
473				ExecutorParams::from(&[ExecutorParam::PrecheckingMaxMemory(300 * 1024 * 1024)][..]),
474			),
475			(
476				"PvfPrepTimeout(Precheck)",
477				base.clone(),
478				ExecutorParams::from(
479					&[ExecutorParam::PvfPrepTimeout(polkadot_primitives::PvfPrepKind::Precheck, 1)]
480						[..],
481				),
482			),
483			(
484				"PvfPrepTimeout(Prepare)",
485				base.clone(),
486				ExecutorParams::from(
487					&[ExecutorParam::PvfPrepTimeout(polkadot_primitives::PvfPrepKind::Prepare, 2)]
488						[..],
489				),
490			),
491			(
492				"PvfExecTimeout(Backing)",
493				base.clone(),
494				ExecutorParams::from(
495					&[ExecutorParam::PvfExecTimeout(polkadot_primitives::PvfExecKind::Backing, 1)]
496						[..],
497				),
498			),
499			(
500				"PvfExecTimeout(Approval)",
501				base.clone(),
502				ExecutorParams::from(
503					&[ExecutorParam::PvfExecTimeout(polkadot_primitives::PvfExecKind::Approval, 2)]
504						[..],
505				),
506			),
507			(
508				"WasmExtBulkMemory",
509				base.clone(),
510				ExecutorParams::from(&[ExecutorParam::WasmExtBulkMemory][..]),
511			),
512			(
513				"EnabledHostFunction(EccRfc163)",
514				base.clone(),
515				ExecutorParams::from(
516					&[ExecutorParam::EnabledHostFunction(
517						polkadot_primitives::ExecutorHostFunction::EccRfc163,
518					)][..],
519				),
520			),
521		];
522
523		for (name, a, b) in cases.into_iter() {
524			let art_a = prepare_with(&a);
525			let art_b = prepare_with(&b);
526			let artifact_changed = art_a != art_b;
527			let prep_hash_changed = a.prep_hash() != b.prep_hash();
528			assert_eq!(
529				artifact_changed,
530				prep_hash_changed,
531				"ExecutorParam classification mismatch for {}: artifact_changed={}, prep_hash_changed={}",
532				name,
533				artifact_changed,
534				prep_hash_changed,
535			);
536		}
537	}
538}