Skip to main content

soil_client/executor/
executor.rs

1// This file is part of Soil.
2
3// Copyright (C) Soil contributors.
4// Copyright (C) Parity Technologies (UK) Ltd.
5// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
6
7use crate::executor::{
8	error::{Error, Result},
9	wasm_runtime::{RuntimeCache, WasmExecutionMethod},
10	RuntimeVersionOf,
11};
12
13use std::{
14	marker::PhantomData,
15	panic::{AssertUnwindSafe, UnwindSafe},
16	path::PathBuf,
17	sync::Arc,
18};
19
20use crate::executor::common::{
21	runtime_blob::RuntimeBlob,
22	wasm_runtime::{
23		AllocationStats, HeapAllocStrategy, WasmInstance, WasmModule, DEFAULT_HEAP_ALLOC_STRATEGY,
24	},
25};
26use codec::Encode;
27use subsoil::core::traits::{CallContext, CodeExecutor, Externalities, RuntimeCode};
28use subsoil::version::{GetNativeVersion, NativeVersion, RuntimeVersion};
29use subsoil::wasm_interface::{ExtendedHostFunctions, HostFunctions};
30
31/// Set up the externalities and safe calling environment to execute runtime calls.
32///
33/// If the inner closure panics, it will be caught and return an error.
34pub fn with_externalities_safe<F, U>(ext: &mut dyn Externalities, f: F) -> Result<U>
35where
36	F: UnwindSafe + FnOnce() -> U,
37{
38	subsoil::externalities::set_and_run_with_externalities(ext, move || {
39		// Substrate uses custom panic hook that terminates process on panic. Disable
40		// termination for the native call.
41		let _guard = subsoil::panic_handler::AbortGuard::force_unwind();
42		std::panic::catch_unwind(f).map_err(|e| {
43			if let Some(err) = e.downcast_ref::<String>() {
44				Error::RuntimePanicked(err.clone())
45			} else if let Some(err) = e.downcast_ref::<&'static str>() {
46				Error::RuntimePanicked(err.to_string())
47			} else {
48				Error::RuntimePanicked("Unknown panic".into())
49			}
50		})
51	})
52}
53
54/// Delegate for dispatching a CodeExecutor call.
55///
56/// By dispatching we mean that we execute a runtime function specified by it's name.
57pub trait NativeExecutionDispatch: Send + Sync {
58	/// Host functions for custom runtime interfaces that should be callable from within the runtime
59	/// besides the default Substrate runtime interfaces.
60	type ExtendHostFunctions: HostFunctions;
61
62	/// Dispatch a method in the runtime.
63	fn dispatch(method: &str, data: &[u8]) -> Option<Vec<u8>>;
64
65	/// Provide native runtime version.
66	fn native_version() -> NativeVersion;
67}
68
69fn unwrap_heap_pages(pages: Option<HeapAllocStrategy>) -> HeapAllocStrategy {
70	pages.unwrap_or_else(|| DEFAULT_HEAP_ALLOC_STRATEGY)
71}
72
73/// Builder for creating a [`WasmExecutor`] instance.
74pub struct WasmExecutorBuilder<H = subsoil::io::SubstrateHostFunctions> {
75	_phantom: PhantomData<H>,
76	method: WasmExecutionMethod,
77	onchain_heap_alloc_strategy: Option<HeapAllocStrategy>,
78	offchain_heap_alloc_strategy: Option<HeapAllocStrategy>,
79	ignore_onchain_heap_pages: bool,
80	max_runtime_instances: usize,
81	cache_path: Option<PathBuf>,
82	allow_missing_host_functions: bool,
83	runtime_cache_size: u8,
84}
85
86impl<H> WasmExecutorBuilder<H> {
87	/// Create a new instance of `Self`
88	///
89	/// - `method`: The wasm execution method that should be used by the executor.
90	pub fn new() -> Self {
91		Self {
92			_phantom: PhantomData,
93			method: WasmExecutionMethod::default(),
94			onchain_heap_alloc_strategy: None,
95			offchain_heap_alloc_strategy: None,
96			ignore_onchain_heap_pages: false,
97			max_runtime_instances: 2,
98			runtime_cache_size: 4,
99			allow_missing_host_functions: false,
100			cache_path: None,
101		}
102	}
103
104	/// Create the wasm executor with execution method that should be used by the executor.
105	pub fn with_execution_method(mut self, method: WasmExecutionMethod) -> Self {
106		self.method = method;
107		self
108	}
109
110	/// Create the wasm executor with the given number of `heap_alloc_strategy` for onchain runtime
111	/// calls.
112	pub fn with_onchain_heap_alloc_strategy(
113		mut self,
114		heap_alloc_strategy: HeapAllocStrategy,
115	) -> Self {
116		self.onchain_heap_alloc_strategy = Some(heap_alloc_strategy);
117		self
118	}
119
120	/// Create the wasm executor with the given number of `heap_alloc_strategy` for offchain runtime
121	/// calls.
122	pub fn with_offchain_heap_alloc_strategy(
123		mut self,
124		heap_alloc_strategy: HeapAllocStrategy,
125	) -> Self {
126		self.offchain_heap_alloc_strategy = Some(heap_alloc_strategy);
127		self
128	}
129
130	/// Create the wasm executor and follow/ignore onchain heap pages value.
131	///
132	/// By default this the onchain heap pages value is followed.
133	pub fn with_ignore_onchain_heap_pages(mut self, ignore_onchain_heap_pages: bool) -> Self {
134		self.ignore_onchain_heap_pages = ignore_onchain_heap_pages;
135		self
136	}
137
138	/// Create the wasm executor with the given maximum number of `instances`.
139	///
140	/// The number of `instances` defines how many different instances of a runtime the cache is
141	/// storing.
142	///
143	/// By default the maximum number of `instances` is `2`.
144	pub fn with_max_runtime_instances(mut self, instances: usize) -> Self {
145		self.max_runtime_instances = instances;
146		self
147	}
148
149	/// Create the wasm executor with the given `cache_path`.
150	///
151	/// The `cache_path` is A path to a directory where the executor can place its files for
152	/// purposes of caching. This may be important in cases when there are many different modules
153	/// with the compiled execution method is used.
154	///
155	/// By default there is no `cache_path` given.
156	pub fn with_cache_path(mut self, cache_path: impl Into<PathBuf>) -> Self {
157		self.cache_path = Some(cache_path.into());
158		self
159	}
160
161	/// Create the wasm executor and allow/forbid missing host functions.
162	///
163	/// If missing host functions are forbidden, the instantiation of a wasm blob will fail
164	/// for imported host functions that the executor is not aware of. If they are allowed,
165	/// a stub is generated that will return an error when being called while executing the wasm.
166	///
167	/// By default missing host functions are forbidden.
168	pub fn with_allow_missing_host_functions(mut self, allow: bool) -> Self {
169		self.allow_missing_host_functions = allow;
170		self
171	}
172
173	/// Create the wasm executor with the given `runtime_cache_size`.
174	///
175	/// Defines the number of different runtimes/instantiated wasm blobs the cache stores.
176	/// Runtimes/wasm blobs are differentiated based on the hash and the number of heap pages.
177	///
178	/// By default this value is set to `4`.
179	pub fn with_runtime_cache_size(mut self, runtime_cache_size: u8) -> Self {
180		self.runtime_cache_size = runtime_cache_size;
181		self
182	}
183
184	/// Build the configured [`WasmExecutor`].
185	pub fn build(self) -> WasmExecutor<H> {
186		WasmExecutor {
187			method: self.method,
188			default_offchain_heap_alloc_strategy: unwrap_heap_pages(
189				self.offchain_heap_alloc_strategy,
190			),
191			default_onchain_heap_alloc_strategy: unwrap_heap_pages(
192				self.onchain_heap_alloc_strategy,
193			),
194			ignore_onchain_heap_pages: self.ignore_onchain_heap_pages,
195			cache: Arc::new(RuntimeCache::new(
196				self.max_runtime_instances,
197				self.cache_path.clone(),
198				self.runtime_cache_size,
199			)),
200			cache_path: self.cache_path,
201			allow_missing_host_functions: self.allow_missing_host_functions,
202			phantom: PhantomData,
203		}
204	}
205}
206
207/// An abstraction over Wasm code executor. Supports selecting execution backend and
208/// manages runtime cache.
209pub struct WasmExecutor<H = subsoil::io::SubstrateHostFunctions> {
210	/// Method used to execute fallback Wasm code.
211	method: WasmExecutionMethod,
212	/// The heap allocation strategy for onchain Wasm calls.
213	default_onchain_heap_alloc_strategy: HeapAllocStrategy,
214	/// The heap allocation strategy for offchain Wasm calls.
215	default_offchain_heap_alloc_strategy: HeapAllocStrategy,
216	/// Ignore onchain heap pages value.
217	ignore_onchain_heap_pages: bool,
218	/// WASM runtime cache.
219	cache: Arc<RuntimeCache>,
220	/// The path to a directory which the executor can leverage for a file cache, e.g. put there
221	/// compiled artifacts.
222	cache_path: Option<PathBuf>,
223	/// Ignore missing function imports.
224	allow_missing_host_functions: bool,
225	phantom: PhantomData<H>,
226}
227
228impl<H> Clone for WasmExecutor<H> {
229	fn clone(&self) -> Self {
230		Self {
231			method: self.method,
232			default_onchain_heap_alloc_strategy: self.default_onchain_heap_alloc_strategy,
233			default_offchain_heap_alloc_strategy: self.default_offchain_heap_alloc_strategy,
234			ignore_onchain_heap_pages: self.ignore_onchain_heap_pages,
235			cache: self.cache.clone(),
236			cache_path: self.cache_path.clone(),
237			allow_missing_host_functions: self.allow_missing_host_functions,
238			phantom: self.phantom,
239		}
240	}
241}
242
243impl Default for WasmExecutor<subsoil::io::SubstrateHostFunctions> {
244	fn default() -> Self {
245		WasmExecutorBuilder::new().build()
246	}
247}
248
249impl<H> WasmExecutor<H> {
250	/// Create new instance.
251	///
252	/// # Parameters
253	///
254	/// `method` - Method used to execute Wasm code.
255	///
256	/// `default_heap_pages` - Number of 64KB pages to allocate for Wasm execution. Internally this
257	/// will be mapped as [`HeapAllocStrategy::Static`] where `default_heap_pages` represent the
258	/// static number of heap pages to allocate. Defaults to `DEFAULT_HEAP_ALLOC_STRATEGY` if `None`
259	/// is provided.
260	///
261	/// `max_runtime_instances` - The number of runtime instances to keep in memory ready for reuse.
262	///
263	/// `cache_path` - A path to a directory where the executor can place its files for purposes of
264	///   caching. This may be important in cases when there are many different modules with the
265	///   compiled execution method is used.
266	///
267	/// `runtime_cache_size` - The capacity of runtime cache.
268	#[deprecated(note = "use `Self::builder` method instead of it")]
269	pub fn new(
270		method: WasmExecutionMethod,
271		default_heap_pages: Option<u64>,
272		max_runtime_instances: usize,
273		cache_path: Option<PathBuf>,
274		runtime_cache_size: u8,
275	) -> Self {
276		WasmExecutor {
277			method,
278			default_onchain_heap_alloc_strategy: unwrap_heap_pages(
279				default_heap_pages.map(|h| HeapAllocStrategy::Static { extra_pages: h as _ }),
280			),
281			default_offchain_heap_alloc_strategy: unwrap_heap_pages(
282				default_heap_pages.map(|h| HeapAllocStrategy::Static { extra_pages: h as _ }),
283			),
284			ignore_onchain_heap_pages: false,
285			cache: Arc::new(RuntimeCache::new(
286				max_runtime_instances,
287				cache_path.clone(),
288				runtime_cache_size,
289			)),
290			cache_path,
291			allow_missing_host_functions: false,
292			phantom: PhantomData,
293		}
294	}
295
296	/// Instantiate a builder for creating an instance of `Self`.
297	pub fn builder() -> WasmExecutorBuilder<H> {
298		WasmExecutorBuilder::new()
299	}
300
301	/// Ignore missing function imports if set true.
302	#[deprecated(note = "use `Self::builder` method instead of it")]
303	pub fn allow_missing_host_functions(&mut self, allow_missing_host_functions: bool) {
304		self.allow_missing_host_functions = allow_missing_host_functions
305	}
306}
307
308impl<H> WasmExecutor<H>
309where
310	H: HostFunctions,
311{
312	/// Execute the given closure `f` with the latest runtime (based on `runtime_code`).
313	///
314	/// The closure `f` is expected to return `Err(_)` when there happened a `panic!` in native code
315	/// while executing the runtime in Wasm. If a `panic!` occurred, the runtime is invalidated to
316	/// prevent any poisoned state. Native runtime execution does not need to report back
317	/// any `panic!`.
318	///
319	/// # Safety
320	///
321	/// `runtime` and `ext` are given as `AssertUnwindSafe` to the closure. As described above, the
322	/// runtime is invalidated on any `panic!` to prevent a poisoned state. `ext` is already
323	/// implicitly handled as unwind safe, as we store it in a global variable while executing the
324	/// native runtime.
325	pub fn with_instance<R, F>(
326		&self,
327		runtime_code: &RuntimeCode,
328		ext: &mut dyn Externalities,
329		heap_alloc_strategy: HeapAllocStrategy,
330		f: F,
331	) -> Result<R>
332	where
333		F: FnOnce(
334			AssertUnwindSafe<&dyn WasmModule>,
335			AssertUnwindSafe<&mut dyn WasmInstance>,
336			Option<&RuntimeVersion>,
337			AssertUnwindSafe<&mut dyn Externalities>,
338		) -> Result<Result<R>>,
339	{
340		match self.cache.with_instance::<H, _, _>(
341			runtime_code,
342			ext,
343			self.method,
344			heap_alloc_strategy,
345			self.allow_missing_host_functions,
346			|module, instance, version, ext| {
347				let module = AssertUnwindSafe(module);
348				let instance = AssertUnwindSafe(instance);
349				let ext = AssertUnwindSafe(ext);
350				f(module, instance, version, ext)
351			},
352		)? {
353			Ok(r) => r,
354			Err(e) => Err(e),
355		}
356	}
357
358	/// Perform a call into the given runtime.
359	///
360	/// The runtime is passed as a [`RuntimeBlob`]. The runtime will be instantiated with the
361	/// parameters this `WasmExecutor` was initialized with.
362	///
363	/// In case of problems with during creation of the runtime or instantiation, a `Err` is
364	/// returned. that describes the message.
365	#[doc(hidden)] // We use this function for tests across multiple crates.
366	pub fn uncached_call(
367		&self,
368		runtime_blob: RuntimeBlob,
369		ext: &mut dyn Externalities,
370		allow_missing_host_functions: bool,
371		export_name: &str,
372		call_data: &[u8],
373	) -> std::result::Result<Vec<u8>, Error> {
374		self.uncached_call_impl(
375			runtime_blob,
376			ext,
377			allow_missing_host_functions,
378			export_name,
379			call_data,
380			&mut None,
381		)
382	}
383
384	/// Same as `uncached_call`, except it also returns allocation statistics.
385	#[doc(hidden)] // We use this function in tests.
386	pub fn uncached_call_with_allocation_stats(
387		&self,
388		runtime_blob: RuntimeBlob,
389		ext: &mut dyn Externalities,
390		allow_missing_host_functions: bool,
391		export_name: &str,
392		call_data: &[u8],
393	) -> (std::result::Result<Vec<u8>, Error>, Option<AllocationStats>) {
394		let mut allocation_stats = None;
395		let result = self.uncached_call_impl(
396			runtime_blob,
397			ext,
398			allow_missing_host_functions,
399			export_name,
400			call_data,
401			&mut allocation_stats,
402		);
403		(result, allocation_stats)
404	}
405
406	fn uncached_call_impl(
407		&self,
408		runtime_blob: RuntimeBlob,
409		ext: &mut dyn Externalities,
410		allow_missing_host_functions: bool,
411		export_name: &str,
412		call_data: &[u8],
413		allocation_stats_out: &mut Option<AllocationStats>,
414	) -> std::result::Result<Vec<u8>, Error> {
415		let module = crate::executor::wasm_runtime::create_wasm_runtime_with_code::<H>(
416			self.method,
417			self.default_onchain_heap_alloc_strategy,
418			runtime_blob,
419			allow_missing_host_functions,
420			self.cache_path.as_deref(),
421		)
422		.map_err(|e| format!("Failed to create module: {}", e))?;
423
424		let instance =
425			module.new_instance().map_err(|e| format!("Failed to create instance: {}", e))?;
426
427		let mut instance = AssertUnwindSafe(instance);
428		let mut ext = AssertUnwindSafe(ext);
429		let mut allocation_stats_out = AssertUnwindSafe(allocation_stats_out);
430
431		with_externalities_safe(&mut **ext, move || {
432			let (result, allocation_stats) =
433				instance.call_with_allocation_stats(export_name.into(), call_data);
434			**allocation_stats_out = allocation_stats;
435			result
436		})
437		.and_then(|r| r)
438	}
439}
440
441impl<H> subsoil::core::traits::ReadRuntimeVersion for WasmExecutor<H>
442where
443	H: HostFunctions,
444{
445	fn read_runtime_version(
446		&self,
447		wasm_code: &[u8],
448		ext: &mut dyn Externalities,
449	) -> std::result::Result<Vec<u8>, String> {
450		let runtime_blob = RuntimeBlob::uncompress_if_needed(wasm_code)
451			.map_err(|e| format!("Failed to create runtime blob: {:?}", e))?;
452
453		if let Some(version) = crate::executor::wasm_runtime::read_embedded_version(&runtime_blob)
454			.map_err(|e| format!("Failed to read the static section: {:?}", e))
455			.map(|v| v.map(|v| v.encode()))?
456		{
457			return Ok(version);
458		}
459
460		// If the blob didn't have embedded runtime version section, we fallback to the legacy
461		// way of fetching the version: i.e. instantiating the given instance and calling
462		// `Core_version` on it.
463
464		self.uncached_call(
465			runtime_blob,
466			ext,
467			// If a runtime upgrade introduces new host functions that are not provided by
468			// the node, we should not fail at instantiation. Otherwise nodes that are
469			// updated could run this successfully and it could lead to a storage root
470			// mismatch when importing this block.
471			true,
472			"Core_version",
473			&[],
474		)
475		.map_err(|e| e.to_string())
476	}
477}
478
479impl<H> CodeExecutor for WasmExecutor<H>
480where
481	H: HostFunctions,
482{
483	type Error = Error;
484
485	fn call(
486		&self,
487		ext: &mut dyn Externalities,
488		runtime_code: &RuntimeCode,
489		method: &str,
490		data: &[u8],
491		context: CallContext,
492	) -> (Result<Vec<u8>>, bool) {
493		tracing::trace!(
494			target: "executor",
495			%method,
496			"Executing function",
497		);
498
499		let on_chain_heap_alloc_strategy = if self.ignore_onchain_heap_pages {
500			self.default_onchain_heap_alloc_strategy
501		} else {
502			runtime_code
503				.heap_pages
504				.map(|h| HeapAllocStrategy::Static { extra_pages: h as _ })
505				.unwrap_or_else(|| self.default_onchain_heap_alloc_strategy)
506		};
507
508		let heap_alloc_strategy = match context {
509			CallContext::Offchain => self.default_offchain_heap_alloc_strategy,
510			CallContext::Onchain => on_chain_heap_alloc_strategy,
511		};
512
513		let result = self.with_instance(
514			runtime_code,
515			ext,
516			heap_alloc_strategy,
517			|_, mut instance, _on_chain_version, mut ext| {
518				with_externalities_safe(&mut **ext, move || instance.call_export(method, data))
519			},
520		);
521
522		(result, false)
523	}
524}
525
526impl<H> RuntimeVersionOf for WasmExecutor<H>
527where
528	H: HostFunctions,
529{
530	fn runtime_version(
531		&self,
532		ext: &mut dyn Externalities,
533		runtime_code: &RuntimeCode,
534	) -> Result<RuntimeVersion> {
535		let on_chain_heap_pages = if self.ignore_onchain_heap_pages {
536			self.default_onchain_heap_alloc_strategy
537		} else {
538			runtime_code
539				.heap_pages
540				.map(|h| HeapAllocStrategy::Static { extra_pages: h as _ })
541				.unwrap_or_else(|| self.default_onchain_heap_alloc_strategy)
542		};
543
544		self.with_instance(
545			runtime_code,
546			ext,
547			on_chain_heap_pages,
548			|_module, _instance, version, _ext| {
549				Ok(version.cloned().ok_or_else(|| Error::ApiError("Unknown version".into())))
550			},
551		)
552	}
553}
554
555/// A generic `CodeExecutor` implementation that uses a delegate to determine wasm code equivalence
556/// and dispatch to native code when possible, falling back on `WasmExecutor` when not.
557#[deprecated(
558	note = "Native execution will be deprecated, please replace with `WasmExecutor`. Will be removed at end of 2024."
559)]
560pub struct NativeElseWasmExecutor<D: NativeExecutionDispatch> {
561	/// Native runtime version info.
562	native_version: NativeVersion,
563	/// Fallback wasm executor.
564	wasm: WasmExecutor<
565		ExtendedHostFunctions<subsoil::io::SubstrateHostFunctions, D::ExtendHostFunctions>,
566	>,
567
568	use_native: bool,
569}
570
571#[allow(deprecated)]
572impl<D: NativeExecutionDispatch> NativeElseWasmExecutor<D> {
573	/// Create new instance.
574	///
575	/// # Parameters
576	///
577	/// `fallback_method` - Method used to execute fallback Wasm code.
578	///
579	/// `default_heap_pages` - Number of 64KB pages to allocate for Wasm execution. Internally this
580	/// will be mapped as [`HeapAllocStrategy::Static`] where `default_heap_pages` represent the
581	/// static number of heap pages to allocate. Defaults to `DEFAULT_HEAP_ALLOC_STRATEGY` if `None`
582	/// is provided.
583	///
584	/// `max_runtime_instances` - The number of runtime instances to keep in memory ready for reuse.
585	///
586	/// `runtime_cache_size` - The capacity of runtime cache.
587	#[deprecated(note = "use `Self::new_with_wasm_executor` method instead of it")]
588	pub fn new(
589		fallback_method: WasmExecutionMethod,
590		default_heap_pages: Option<u64>,
591		max_runtime_instances: usize,
592		runtime_cache_size: u8,
593	) -> Self {
594		let heap_pages = default_heap_pages.map_or(DEFAULT_HEAP_ALLOC_STRATEGY, |h| {
595			HeapAllocStrategy::Static { extra_pages: h as _ }
596		});
597		let wasm = WasmExecutor::builder()
598			.with_execution_method(fallback_method)
599			.with_onchain_heap_alloc_strategy(heap_pages)
600			.with_offchain_heap_alloc_strategy(heap_pages)
601			.with_max_runtime_instances(max_runtime_instances)
602			.with_runtime_cache_size(runtime_cache_size)
603			.build();
604
605		NativeElseWasmExecutor { native_version: D::native_version(), wasm, use_native: true }
606	}
607
608	/// Create a new instance using the given [`WasmExecutor`].
609	pub fn new_with_wasm_executor(
610		executor: WasmExecutor<
611			ExtendedHostFunctions<subsoil::io::SubstrateHostFunctions, D::ExtendHostFunctions>,
612		>,
613	) -> Self {
614		Self { native_version: D::native_version(), wasm: executor, use_native: true }
615	}
616
617	/// Disable to use native runtime when possible just behave like `WasmExecutor`.
618	///
619	/// Default to enabled.
620	pub fn disable_use_native(&mut self) {
621		self.use_native = false;
622	}
623
624	/// Ignore missing function imports if set true.
625	#[deprecated(note = "use `Self::new_with_wasm_executor` method instead of it")]
626	pub fn allow_missing_host_functions(&mut self, allow_missing_host_functions: bool) {
627		self.wasm.allow_missing_host_functions = allow_missing_host_functions
628	}
629}
630
631#[allow(deprecated)]
632impl<D: NativeExecutionDispatch> RuntimeVersionOf for NativeElseWasmExecutor<D> {
633	fn runtime_version(
634		&self,
635		ext: &mut dyn Externalities,
636		runtime_code: &RuntimeCode,
637	) -> Result<RuntimeVersion> {
638		self.wasm.runtime_version(ext, runtime_code)
639	}
640}
641
642#[allow(deprecated)]
643impl<D: NativeExecutionDispatch> GetNativeVersion for NativeElseWasmExecutor<D> {
644	fn native_version(&self) -> &NativeVersion {
645		&self.native_version
646	}
647}
648
649#[allow(deprecated)]
650impl<D: NativeExecutionDispatch + 'static> CodeExecutor for NativeElseWasmExecutor<D> {
651	type Error = Error;
652
653	fn call(
654		&self,
655		ext: &mut dyn Externalities,
656		runtime_code: &RuntimeCode,
657		method: &str,
658		data: &[u8],
659		context: CallContext,
660	) -> (Result<Vec<u8>>, bool) {
661		let use_native = self.use_native;
662
663		tracing::trace!(
664			target: "executor",
665			function = %method,
666			"Executing function",
667		);
668
669		let on_chain_heap_alloc_strategy = if self.wasm.ignore_onchain_heap_pages {
670			self.wasm.default_onchain_heap_alloc_strategy
671		} else {
672			runtime_code
673				.heap_pages
674				.map(|h| HeapAllocStrategy::Static { extra_pages: h as _ })
675				.unwrap_or_else(|| self.wasm.default_onchain_heap_alloc_strategy)
676		};
677
678		let heap_alloc_strategy = match context {
679			CallContext::Offchain => self.wasm.default_offchain_heap_alloc_strategy,
680			CallContext::Onchain => on_chain_heap_alloc_strategy,
681		};
682
683		let mut used_native = false;
684		let result = self.wasm.with_instance(
685			runtime_code,
686			ext,
687			heap_alloc_strategy,
688			|_, mut instance, on_chain_version, mut ext| {
689				let on_chain_version =
690					on_chain_version.ok_or_else(|| Error::ApiError("Unknown version".into()))?;
691
692				let can_call_with =
693					on_chain_version.can_call_with(&self.native_version.runtime_version);
694
695				if use_native && can_call_with {
696					tracing::trace!(
697						target: "executor",
698						native = %self.native_version.runtime_version,
699						chain = %on_chain_version,
700						"Request for native execution succeeded",
701					);
702
703					used_native = true;
704					Ok(with_externalities_safe(&mut **ext, move || D::dispatch(method, data))?
705						.ok_or_else(|| Error::MethodNotFound(method.to_owned())))
706				} else {
707					if !can_call_with {
708						tracing::trace!(
709							target: "executor",
710							native = %self.native_version.runtime_version,
711							chain = %on_chain_version,
712							"Request for native execution failed",
713						);
714					}
715
716					with_externalities_safe(&mut **ext, move || instance.call_export(method, data))
717				}
718			},
719		);
720		(result, used_native)
721	}
722}
723
724#[allow(deprecated)]
725impl<D: NativeExecutionDispatch> Clone for NativeElseWasmExecutor<D> {
726	fn clone(&self) -> Self {
727		NativeElseWasmExecutor {
728			native_version: D::native_version(),
729			wasm: self.wasm.clone(),
730			use_native: self.use_native,
731		}
732	}
733}
734
735#[allow(deprecated)]
736impl<D: NativeExecutionDispatch> subsoil::core::traits::ReadRuntimeVersion
737	for NativeElseWasmExecutor<D>
738{
739	fn read_runtime_version(
740		&self,
741		wasm_code: &[u8],
742		ext: &mut dyn Externalities,
743	) -> std::result::Result<Vec<u8>, String> {
744		self.wasm.read_runtime_version(wasm_code, ext)
745	}
746}
747
748#[cfg(test)]
749mod tests {
750	use super::*;
751	use std::borrow::Cow;
752	use subsoil::runtime_interface::{pass_by::PassFatPointerAndRead, runtime_interface};
753	use subsoil::version::RuntimeVersion;
754
755	#[runtime_interface]
756	trait MyInterface {
757		fn say_hello_world(data: PassFatPointerAndRead<&str>) {
758			println!("Hello world from: {}", data);
759		}
760	}
761
762	pub struct MyExecutorDispatch;
763
764	impl NativeExecutionDispatch for MyExecutorDispatch {
765		type ExtendHostFunctions = (my_interface::HostFunctions, my_interface::HostFunctions);
766
767		fn dispatch(method: &str, data: &[u8]) -> Option<Vec<u8>> {
768			let _ = (method, data);
769			None
770		}
771
772		fn native_version() -> NativeVersion {
773			NativeVersion {
774				runtime_version: RuntimeVersion {
775					spec_name: Cow::Borrowed("executor-test"),
776					impl_name: Cow::Borrowed("executor-test"),
777					authoring_version: 1,
778					spec_version: 1,
779					impl_version: 1,
780					apis: Default::default(),
781					transaction_version: 1,
782					system_version: 1,
783				},
784				can_author_with: Default::default(),
785			}
786		}
787	}
788
789	#[test]
790	#[allow(deprecated)]
791	fn native_executor_registers_custom_interface() {
792		let executor = NativeElseWasmExecutor::<MyExecutorDispatch>::new_with_wasm_executor(
793			WasmExecutor::builder().build(),
794		);
795
796		fn extract_host_functions<H>(
797			_: &WasmExecutor<H>,
798		) -> Vec<&'static dyn subsoil::wasm_interface::Function>
799		where
800			H: HostFunctions,
801		{
802			H::host_functions()
803		}
804
805		my_interface::HostFunctions::host_functions().iter().for_each(|function| {
806			assert_eq!(
807				extract_host_functions(&executor.wasm).iter().filter(|f| f == &function).count(),
808				2
809			);
810		});
811
812		my_interface::say_hello_world("hey");
813	}
814}