referrerpolicy=no-referrer-when-downgrade

sc_executor/
executor.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19use crate::{
20	error::{Error, Result},
21	wasm_runtime::{RuntimeCache, WasmExecutionMethod},
22	RuntimeVersionOf,
23};
24
25use std::{
26	marker::PhantomData,
27	panic::{AssertUnwindSafe, UnwindSafe},
28	path::PathBuf,
29	sync::Arc,
30};
31
32use codec::Encode;
33use sc_executor_common::{
34	runtime_blob::RuntimeBlob,
35	wasm_runtime::{
36		AllocationStats, HeapAllocStrategy, WasmInstance, WasmModule, DEFAULT_HEAP_ALLOC_STRATEGY,
37	},
38};
39use sp_core::traits::{CallContext, CodeExecutor, Externalities, RuntimeCode};
40use sp_version::RuntimeVersion;
41use sp_wasm_interface::HostFunctions;
42
43/// Set up the externalities and safe calling environment to execute runtime calls.
44///
45/// If the inner closure panics, it will be caught and return an error.
46pub fn with_externalities_safe<F, U>(ext: &mut dyn Externalities, f: F) -> Result<U>
47where
48	F: UnwindSafe + FnOnce() -> U,
49{
50	sp_externalities::set_and_run_with_externalities(ext, move || {
51		// Substrate uses custom panic hook that terminates process on panic. Disable
52		// termination for the native call.
53		let _guard = sp_panic_handler::AbortGuard::force_unwind();
54		std::panic::catch_unwind(f).map_err(|e| {
55			if let Some(err) = e.downcast_ref::<String>() {
56				Error::RuntimePanicked(err.clone())
57			} else if let Some(err) = e.downcast_ref::<&'static str>() {
58				Error::RuntimePanicked(err.to_string())
59			} else {
60				Error::RuntimePanicked("Unknown panic".into())
61			}
62		})
63	})
64}
65
66fn unwrap_heap_pages(pages: Option<HeapAllocStrategy>) -> HeapAllocStrategy {
67	pages.unwrap_or_else(|| DEFAULT_HEAP_ALLOC_STRATEGY)
68}
69
70/// Builder for creating a [`WasmExecutor`] instance.
71pub struct WasmExecutorBuilder<H = sp_io::SubstrateHostFunctions> {
72	_phantom: PhantomData<H>,
73	method: WasmExecutionMethod,
74	onchain_heap_alloc_strategy: Option<HeapAllocStrategy>,
75	offchain_heap_alloc_strategy: Option<HeapAllocStrategy>,
76	ignore_onchain_heap_pages: bool,
77	max_runtime_instances: usize,
78	cache_path: Option<PathBuf>,
79	allow_missing_host_functions: bool,
80	runtime_cache_size: u8,
81}
82
83impl<H> WasmExecutorBuilder<H> {
84	/// Create a new instance of `Self`
85	///
86	/// - `method`: The wasm execution method that should be used by the executor.
87	pub fn new() -> Self {
88		Self {
89			_phantom: PhantomData,
90			method: WasmExecutionMethod::default(),
91			onchain_heap_alloc_strategy: None,
92			offchain_heap_alloc_strategy: None,
93			ignore_onchain_heap_pages: false,
94			max_runtime_instances: 2,
95			runtime_cache_size: 4,
96			allow_missing_host_functions: false,
97			cache_path: None,
98		}
99	}
100
101	/// Create the wasm executor with execution method that should be used by the executor.
102	pub fn with_execution_method(mut self, method: WasmExecutionMethod) -> Self {
103		self.method = method;
104		self
105	}
106
107	/// Create the wasm executor with the given number of `heap_alloc_strategy` for onchain runtime
108	/// calls.
109	pub fn with_onchain_heap_alloc_strategy(
110		mut self,
111		heap_alloc_strategy: HeapAllocStrategy,
112	) -> Self {
113		self.onchain_heap_alloc_strategy = Some(heap_alloc_strategy);
114		self
115	}
116
117	/// Create the wasm executor with the given number of `heap_alloc_strategy` for offchain runtime
118	/// calls.
119	pub fn with_offchain_heap_alloc_strategy(
120		mut self,
121		heap_alloc_strategy: HeapAllocStrategy,
122	) -> Self {
123		self.offchain_heap_alloc_strategy = Some(heap_alloc_strategy);
124		self
125	}
126
127	/// Create the wasm executor and follow/ignore onchain heap pages value.
128	///
129	/// By default this the onchain heap pages value is followed.
130	pub fn with_ignore_onchain_heap_pages(mut self, ignore_onchain_heap_pages: bool) -> Self {
131		self.ignore_onchain_heap_pages = ignore_onchain_heap_pages;
132		self
133	}
134
135	/// Create the wasm executor with the given maximum number of `instances`.
136	///
137	/// The number of `instances` defines how many different instances of a runtime the cache is
138	/// storing.
139	///
140	/// By default the maximum number of `instances` is `2`.
141	pub fn with_max_runtime_instances(mut self, instances: usize) -> Self {
142		self.max_runtime_instances = instances;
143		self
144	}
145
146	/// Create the wasm executor with the given `cache_path`.
147	///
148	/// The `cache_path` is A path to a directory where the executor can place its files for
149	/// purposes of caching. This may be important in cases when there are many different modules
150	/// with the compiled execution method is used.
151	///
152	/// By default there is no `cache_path` given.
153	pub fn with_cache_path(mut self, cache_path: impl Into<PathBuf>) -> Self {
154		self.cache_path = Some(cache_path.into());
155		self
156	}
157
158	/// Create the wasm executor and allow/forbid missing host functions.
159	///
160	/// If missing host functions are forbidden, the instantiation of a wasm blob will fail
161	/// for imported host functions that the executor is not aware of. If they are allowed,
162	/// a stub is generated that will return an error when being called while executing the wasm.
163	///
164	/// By default missing host functions are forbidden.
165	pub fn with_allow_missing_host_functions(mut self, allow: bool) -> Self {
166		self.allow_missing_host_functions = allow;
167		self
168	}
169
170	/// Create the wasm executor with the given `runtime_cache_size`.
171	///
172	/// Defines the number of different runtimes/instantiated wasm blobs the cache stores.
173	/// Runtimes/wasm blobs are differentiated based on the hash and the number of heap pages.
174	///
175	/// By default this value is set to `4`.
176	pub fn with_runtime_cache_size(mut self, runtime_cache_size: u8) -> Self {
177		self.runtime_cache_size = runtime_cache_size;
178		self
179	}
180
181	/// Build the configured [`WasmExecutor`].
182	pub fn build(self) -> WasmExecutor<H> {
183		WasmExecutor {
184			method: self.method,
185			default_offchain_heap_alloc_strategy: unwrap_heap_pages(
186				self.offchain_heap_alloc_strategy,
187			),
188			default_onchain_heap_alloc_strategy: unwrap_heap_pages(
189				self.onchain_heap_alloc_strategy,
190			),
191			ignore_onchain_heap_pages: self.ignore_onchain_heap_pages,
192			cache: Arc::new(RuntimeCache::new(
193				self.max_runtime_instances,
194				self.cache_path.clone(),
195				self.runtime_cache_size,
196			)),
197			cache_path: self.cache_path,
198			allow_missing_host_functions: self.allow_missing_host_functions,
199			phantom: PhantomData,
200		}
201	}
202}
203
204/// An abstraction over Wasm code executor. Supports selecting execution backend and
205/// manages runtime cache.
206pub struct WasmExecutor<H = sp_io::SubstrateHostFunctions> {
207	/// Method used to execute fallback Wasm code.
208	method: WasmExecutionMethod,
209	/// The heap allocation strategy for onchain Wasm calls.
210	default_onchain_heap_alloc_strategy: HeapAllocStrategy,
211	/// The heap allocation strategy for offchain Wasm calls.
212	default_offchain_heap_alloc_strategy: HeapAllocStrategy,
213	/// Ignore onchain heap pages value.
214	ignore_onchain_heap_pages: bool,
215	/// WASM runtime cache.
216	cache: Arc<RuntimeCache>,
217	/// The path to a directory which the executor can leverage for a file cache, e.g. put there
218	/// compiled artifacts.
219	cache_path: Option<PathBuf>,
220	/// Ignore missing function imports.
221	allow_missing_host_functions: bool,
222	phantom: PhantomData<H>,
223}
224
225impl<H> Clone for WasmExecutor<H> {
226	fn clone(&self) -> Self {
227		Self {
228			method: self.method,
229			default_onchain_heap_alloc_strategy: self.default_onchain_heap_alloc_strategy,
230			default_offchain_heap_alloc_strategy: self.default_offchain_heap_alloc_strategy,
231			ignore_onchain_heap_pages: self.ignore_onchain_heap_pages,
232			cache: self.cache.clone(),
233			cache_path: self.cache_path.clone(),
234			allow_missing_host_functions: self.allow_missing_host_functions,
235			phantom: self.phantom,
236		}
237	}
238}
239
240impl Default for WasmExecutor<sp_io::SubstrateHostFunctions> {
241	fn default() -> Self {
242		WasmExecutorBuilder::new().build()
243	}
244}
245
246impl<H> WasmExecutor<H> {
247	/// Create new instance.
248	///
249	/// # Parameters
250	///
251	/// `method` - Method used to execute Wasm code.
252	///
253	/// `default_heap_pages` - Number of 64KB pages to allocate for Wasm execution. Internally this
254	/// will be mapped as [`HeapAllocStrategy::Static`] where `default_heap_pages` represent the
255	/// static number of heap pages to allocate. Defaults to `DEFAULT_HEAP_ALLOC_STRATEGY` if `None`
256	/// is provided.
257	///
258	/// `max_runtime_instances` - The number of runtime instances to keep in memory ready for reuse.
259	///
260	/// `cache_path` - A path to a directory where the executor can place its files for purposes of
261	///   caching. This may be important in cases when there are many different modules with the
262	///   compiled execution method is used.
263	///
264	/// `runtime_cache_size` - The capacity of runtime cache.
265	#[deprecated(note = "use `Self::builder` method instead of it")]
266	pub fn new(
267		method: WasmExecutionMethod,
268		default_heap_pages: Option<u64>,
269		max_runtime_instances: usize,
270		cache_path: Option<PathBuf>,
271		runtime_cache_size: u8,
272	) -> Self {
273		WasmExecutor {
274			method,
275			default_onchain_heap_alloc_strategy: unwrap_heap_pages(
276				default_heap_pages.map(|h| HeapAllocStrategy::Static { extra_pages: h as _ }),
277			),
278			default_offchain_heap_alloc_strategy: unwrap_heap_pages(
279				default_heap_pages.map(|h| HeapAllocStrategy::Static { extra_pages: h as _ }),
280			),
281			ignore_onchain_heap_pages: false,
282			cache: Arc::new(RuntimeCache::new(
283				max_runtime_instances,
284				cache_path.clone(),
285				runtime_cache_size,
286			)),
287			cache_path,
288			allow_missing_host_functions: false,
289			phantom: PhantomData,
290		}
291	}
292
293	/// Instantiate a builder for creating an instance of `Self`.
294	pub fn builder() -> WasmExecutorBuilder<H> {
295		WasmExecutorBuilder::new()
296	}
297
298	/// Ignore missing function imports if set true.
299	#[deprecated(note = "use `Self::builder` method instead of it")]
300	pub fn allow_missing_host_functions(&mut self, allow_missing_host_functions: bool) {
301		self.allow_missing_host_functions = allow_missing_host_functions
302	}
303}
304
305impl<H> WasmExecutor<H>
306where
307	H: HostFunctions,
308{
309	/// Execute the given closure `f` with the latest runtime (based on `runtime_code`).
310	///
311	/// The closure `f` is expected to return `Err(_)` when there happened a `panic!` in native code
312	/// while executing the runtime in Wasm. If a `panic!` occurred, the runtime is invalidated to
313	/// prevent any poisoned state. Native runtime execution does not need to report back
314	/// any `panic!`.
315	///
316	/// # Safety
317	///
318	/// `runtime` and `ext` are given as `AssertUnwindSafe` to the closure. As described above, the
319	/// runtime is invalidated on any `panic!` to prevent a poisoned state. `ext` is already
320	/// implicitly handled as unwind safe, as we store it in a global variable while executing the
321	/// native runtime.
322	pub fn with_instance<R, F>(
323		&self,
324		runtime_code: &RuntimeCode,
325		ext: &mut dyn Externalities,
326		heap_alloc_strategy: HeapAllocStrategy,
327		f: F,
328	) -> Result<R>
329	where
330		F: FnOnce(
331			AssertUnwindSafe<&dyn WasmModule>,
332			AssertUnwindSafe<&mut dyn WasmInstance>,
333			Option<&RuntimeVersion>,
334			AssertUnwindSafe<&mut dyn Externalities>,
335		) -> Result<Result<R>>,
336	{
337		match self.cache.with_instance::<H, _, _>(
338			runtime_code,
339			ext,
340			self.method,
341			heap_alloc_strategy,
342			self.allow_missing_host_functions,
343			|module, instance, version, ext| {
344				let module = AssertUnwindSafe(module);
345				let instance = AssertUnwindSafe(instance);
346				let ext = AssertUnwindSafe(ext);
347				f(module, instance, version, ext)
348			},
349		)? {
350			Ok(r) => r,
351			Err(e) => Err(e),
352		}
353	}
354
355	/// Perform a call into the given runtime.
356	///
357	/// The runtime is passed as a [`RuntimeBlob`]. The runtime will be instantiated with the
358	/// parameters this `WasmExecutor` was initialized with.
359	///
360	/// In case of problems with during creation of the runtime or instantiation, a `Err` is
361	/// returned. that describes the message.
362	#[doc(hidden)] // We use this function for tests across multiple crates.
363	pub fn uncached_call(
364		&self,
365		runtime_blob: RuntimeBlob,
366		ext: &mut dyn Externalities,
367		allow_missing_host_functions: bool,
368		export_name: &str,
369		call_data: &[u8],
370	) -> std::result::Result<Vec<u8>, Error> {
371		self.uncached_call_impl(
372			runtime_blob,
373			ext,
374			allow_missing_host_functions,
375			export_name,
376			call_data,
377			&mut None,
378		)
379	}
380
381	/// Same as `uncached_call`, except it also returns allocation statistics.
382	#[doc(hidden)] // We use this function in tests.
383	pub fn uncached_call_with_allocation_stats(
384		&self,
385		runtime_blob: RuntimeBlob,
386		ext: &mut dyn Externalities,
387		allow_missing_host_functions: bool,
388		export_name: &str,
389		call_data: &[u8],
390	) -> (std::result::Result<Vec<u8>, Error>, Option<AllocationStats>) {
391		let mut allocation_stats = None;
392		let result = self.uncached_call_impl(
393			runtime_blob,
394			ext,
395			allow_missing_host_functions,
396			export_name,
397			call_data,
398			&mut allocation_stats,
399		);
400		(result, allocation_stats)
401	}
402
403	fn uncached_call_impl(
404		&self,
405		runtime_blob: RuntimeBlob,
406		ext: &mut dyn Externalities,
407		allow_missing_host_functions: bool,
408		export_name: &str,
409		call_data: &[u8],
410		allocation_stats_out: &mut Option<AllocationStats>,
411	) -> std::result::Result<Vec<u8>, Error> {
412		let module = crate::wasm_runtime::create_wasm_runtime_with_code::<H>(
413			self.method,
414			self.default_onchain_heap_alloc_strategy,
415			runtime_blob,
416			allow_missing_host_functions,
417			self.cache_path.as_deref(),
418		)
419		.map_err(|e| format!("Failed to create module: {}", e))?;
420
421		let instance = module
422			.new_instance(self.default_onchain_heap_alloc_strategy)
423			.map_err(|e| format!("Failed to create instance: {}", e))?;
424
425		let mut instance = AssertUnwindSafe(instance);
426		let mut ext = AssertUnwindSafe(ext);
427		let mut allocation_stats_out = AssertUnwindSafe(allocation_stats_out);
428
429		with_externalities_safe(&mut **ext, move || {
430			let (result, allocation_stats) =
431				instance.call_with_allocation_stats(export_name.into(), call_data);
432			**allocation_stats_out = allocation_stats;
433			result
434		})
435		.and_then(|r| r)
436	}
437}
438
439impl<H> sp_core::traits::ReadRuntimeVersion for WasmExecutor<H>
440where
441	H: HostFunctions,
442{
443	fn read_runtime_version(
444		&self,
445		wasm_code: &[u8],
446		ext: &mut dyn Externalities,
447	) -> std::result::Result<Vec<u8>, String> {
448		let runtime_blob = RuntimeBlob::uncompress_if_needed(wasm_code)
449			.map_err(|e| format!("Failed to create runtime blob: {:?}", e))?;
450
451		if let Some(version) = crate::wasm_runtime::read_embedded_version(&runtime_blob)
452			.map_err(|e| format!("Failed to read the static section: {:?}", e))
453			.map(|v| v.map(|v| v.encode()))?
454		{
455			return Ok(version);
456		}
457
458		// If the blob didn't have embedded runtime version section, we fallback to the legacy
459		// way of fetching the version: i.e. instantiating the given instance and calling
460		// `Core_version` on it.
461
462		self.uncached_call(
463			runtime_blob,
464			ext,
465			// If a runtime upgrade introduces new host functions that are not provided by
466			// the node, we should not fail at instantiation. Otherwise nodes that are
467			// updated could run this successfully and it could lead to a storage root
468			// mismatch when importing this block.
469			true,
470			"Core_version",
471			&[],
472		)
473		.map_err(|e| e.to_string())
474	}
475}
476
477impl<H> CodeExecutor for WasmExecutor<H>
478where
479	H: HostFunctions,
480{
481	type Error = Error;
482
483	fn call(
484		&self,
485		ext: &mut dyn Externalities,
486		runtime_code: &RuntimeCode,
487		method: &str,
488		data: &[u8],
489		context: CallContext,
490	) -> (Result<Vec<u8>>, bool) {
491		tracing::trace!(
492			target: "executor",
493			%method,
494			"Executing function",
495		);
496
497		let on_chain_heap_alloc_strategy = if self.ignore_onchain_heap_pages {
498			self.default_onchain_heap_alloc_strategy
499		} else {
500			runtime_code
501				.heap_pages
502				.map(|h| HeapAllocStrategy::Static { extra_pages: h as _ })
503				.unwrap_or_else(|| self.default_onchain_heap_alloc_strategy)
504		};
505
506		let heap_alloc_strategy = match context {
507			CallContext::Offchain => self.default_offchain_heap_alloc_strategy,
508			CallContext::Onchain { import: false } => on_chain_heap_alloc_strategy,
509			CallContext::Onchain { import: true } => on_chain_heap_alloc_strategy.double(),
510		};
511
512		let result = self.with_instance(
513			runtime_code,
514			ext,
515			heap_alloc_strategy,
516			|_, mut instance, _on_chain_version, mut ext| {
517				with_externalities_safe(&mut **ext, move || instance.call_export(method, data))
518			},
519		);
520
521		(result, false)
522	}
523}
524
525impl<H> RuntimeVersionOf for WasmExecutor<H>
526where
527	H: HostFunctions,
528{
529	fn runtime_version(
530		&self,
531		ext: &mut dyn Externalities,
532		runtime_code: &RuntimeCode,
533	) -> Result<RuntimeVersion> {
534		let on_chain_heap_pages = if self.ignore_onchain_heap_pages {
535			self.default_onchain_heap_alloc_strategy
536		} else {
537			runtime_code
538				.heap_pages
539				.map(|h| HeapAllocStrategy::Static { extra_pages: h as _ })
540				.unwrap_or_else(|| self.default_onchain_heap_alloc_strategy)
541		};
542
543		self.with_instance(
544			runtime_code,
545			ext,
546			on_chain_heap_pages,
547			|_module, _instance, version, _ext| {
548				Ok(version.cloned().ok_or_else(|| Error::ApiError("Unknown version".into())))
549			},
550		)
551	}
552}
553
554#[cfg(test)]
555mod tests {
556	use super::*;
557	use sp_runtime_interface::{pass_by::PassFatPointerAndRead, runtime_interface};
558	use sp_wasm_interface::ExtendedHostFunctions;
559
560	#[runtime_interface]
561	trait MyInterface {
562		fn say_hello_world(data: PassFatPointerAndRead<&str>) {
563			println!("Hello world from: {}", data);
564		}
565	}
566
567	#[test]
568	fn wasm_executor_registers_custom_interface() {
569		type Hosts = ExtendedHostFunctions<
570			sp_io::SubstrateHostFunctions,
571			(my_interface::HostFunctions, my_interface::HostFunctions),
572		>;
573
574		let executor = WasmExecutor::<Hosts>::builder().build();
575
576		fn extract_host_functions<H>(
577			_: &WasmExecutor<H>,
578		) -> Vec<&'static dyn sp_wasm_interface::Function>
579		where
580			H: HostFunctions,
581		{
582			H::host_functions()
583		}
584
585		my_interface::HostFunctions::host_functions().iter().for_each(|function| {
586			assert_eq!(
587				extract_host_functions(&executor).iter().filter(|f| f == &function).count(),
588				2
589			);
590		});
591
592		my_interface::say_hello_world("hey");
593	}
594}