referrerpolicy=no-referrer-when-downgrade

sc_executor/
wasm_runtime.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
19//! Traits and accessor functions for calling into the Substrate Wasm runtime.
20//!
21//! The primary means of accessing the runtimes is through a cache which saves the reusable
22//! components of the runtime that are expensive to initialize.
23
24use crate::error::{Error, WasmError};
25
26use codec::Decode;
27use parking_lot::Mutex;
28use sc_executor_common::{
29	runtime_blob::RuntimeBlob,
30	wasm_runtime::{HeapAllocStrategy, WasmInstance, WasmModule},
31};
32use schnellru::{ByLength, LruMap};
33use sp_core::traits::{Externalities, FetchRuntimeCode, RuntimeCode};
34use sp_version::RuntimeVersion;
35use sp_wasm_interface::HostFunctions;
36
37use std::{
38	panic::AssertUnwindSafe,
39	path::{Path, PathBuf},
40	sync::{Arc, LazyLock},
41};
42
43/// Specification of different methods of executing the runtime Wasm code.
44#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
45pub enum WasmExecutionMethod {
46	/// Uses the Wasmtime compiled runtime.
47	Compiled {
48		/// The instantiation strategy to use.
49		instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy,
50	},
51}
52
53impl Default for WasmExecutionMethod {
54	fn default() -> Self {
55		Self::Compiled {
56			instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::PoolingCopyOnWrite,
57		}
58	}
59}
60
61#[derive(Debug, PartialEq, Eq, Hash, Clone)]
62struct VersionedRuntimeId {
63	/// Runtime code hash.
64	code_hash: Vec<u8>,
65	/// Wasm runtime type.
66	wasm_method: WasmExecutionMethod,
67}
68
69type ModuleResult = Result<Box<dyn WasmModule>, WasmError>;
70type ModuleInit = Box<dyn FnOnce() -> ModuleResult + Send>;
71
72/// Module of a cached runtime compiled with support for execution timeouts (see
73/// [`WasmInstance::call_with_timeout`]).
74///
75/// Initialized on first use, or ahead of time by [`Self::compile_in_background`]. For wasmtime
76/// this is a separate epoch-interruption compile; PolkaVM modules support timeouts as-is
77/// (uncapped), so their initialization is trivial.
78#[derive(Clone)]
79struct InterruptibleModule(Arc<LazyLock<ModuleResult, ModuleInit>>);
80
81impl InterruptibleModule {
82	fn new(init: impl FnOnce() -> ModuleResult + Send + 'static) -> Self {
83		let init: ModuleInit = Box::new(init);
84		Self(Arc::new(LazyLock::new(init)))
85	}
86
87	/// Get the module, blocking until it is available. Compiles it on the calling thread unless
88	/// another thread already is or has.
89	fn get(&self) -> Result<&dyn WasmModule, Error> {
90		match LazyLock::force(&self.0) {
91			Ok(module) => Ok(&**module),
92			Err(e) => Err(Error::from(e.clone())),
93		}
94	}
95
96	/// Compile the module on a background thread, so that the first call with a timeout
97	/// (usually) doesn't have to wait for it. If the thread cannot be spawned, that first call
98	/// compiles it instead.
99	///
100	/// A panic during compilation aborts the process through Substrate's panic hook, exactly as
101	/// it would when compiling the main module.
102	fn compile_in_background(&self) {
103		let module = self.clone();
104		let spawned = std::thread::Builder::new().name("wasm-interruptible-compile".into()).spawn(
105			move || {
106				let _ = LazyLock::force(&module.0);
107			},
108		);
109
110		if let Err(err) = spawned {
111			tracing::warn!(
112				target: "wasm-runtime",
113				error = %err,
114				"Cannot spawn the interruptible runtime compilation thread, compiling on first use",
115			);
116		}
117	}
118}
119
120/// A Wasm runtime object along with its cached runtime version.
121struct VersionedRuntime {
122	/// Shared runtime that can spawn instances.
123	module: Box<dyn WasmModule>,
124	/// The same runtime compiled with support for execution timeouts.
125	interruptible_module: InterruptibleModule,
126	/// Runtime version according to `Core_version` if any.
127	version: Option<RuntimeVersion>,
128
129	// TODO: Remove this once the legacy instance reuse instantiation strategy
130	//       for `wasmtime` is gone, as this only makes sense with that particular strategy.
131	/// Cached instance pool.
132	instances: Vec<Mutex<Option<Box<dyn WasmInstance>>>>,
133}
134
135impl VersionedRuntime {
136	/// Run the given closure `f` with an instance of this runtime.
137	fn with_instance<R, F>(
138		&self,
139		ext: &mut dyn Externalities,
140		heap_alloc_strategy: HeapAllocStrategy,
141		f: F,
142	) -> Result<R, Error>
143	where
144		F: FnOnce(
145			&dyn WasmModule,
146			&mut dyn WasmInstance,
147			Option<&RuntimeVersion>,
148			&mut dyn Externalities,
149		) -> Result<R, Error>,
150	{
151		// Find a free instance
152		let instance = self
153			.instances
154			.iter()
155			.enumerate()
156			.find_map(|(index, i)| i.try_lock().map(|i| (index, i)));
157
158		match instance {
159			Some((index, mut locked)) => {
160				let (mut instance, new_inst) =
161					locked.take().map(|r| Ok((r, false))).unwrap_or_else(|| {
162						self.module.new_instance(heap_alloc_strategy).map(|i| (i, true))
163					})?;
164
165				// Update the heap allocation strategy for pooled instances, since the
166				// caller may need different memory limits than what the instance was
167				// originally created with.
168				if !new_inst {
169					instance.set_heap_alloc_strategy(heap_alloc_strategy);
170				}
171
172				let result = f(&*self.module, &mut *instance, self.version.as_ref(), ext);
173				if let Err(e) = &result {
174					if new_inst {
175						tracing::warn!(
176							target: "wasm-runtime",
177							error = %e,
178							"Fresh runtime instance failed",
179						)
180					} else {
181						tracing::warn!(
182							target: "wasm-runtime",
183							error = %e,
184							"Evicting failed runtime instance",
185						);
186					}
187				} else {
188					*locked = Some(instance);
189
190					if new_inst {
191						tracing::debug!(
192							target: "wasm-runtime",
193							"Allocated WASM instance {}/{}",
194							index + 1,
195							self.instances.len(),
196						);
197					}
198				}
199
200				result
201			},
202			None => {
203				tracing::warn!(target: "wasm-runtime", "Ran out of free WASM instances");
204
205				// Allocate a new instance
206				let mut instance = self.module.new_instance(heap_alloc_strategy)?;
207
208				f(&*self.module, &mut *instance, self.version.as_ref(), ext)
209			},
210		}
211	}
212
213	/// Run the given closure `f` with a fresh instance of this runtime supporting execution
214	/// timeouts.
215	///
216	/// Blocks until the interruptible module is available, compiling it if needed. These
217	/// instances are not pooled: every call gets a fresh instance.
218	fn with_interruptible_instance<R, F>(
219		&self,
220		ext: &mut dyn Externalities,
221		heap_alloc_strategy: HeapAllocStrategy,
222		f: F,
223	) -> Result<R, Error>
224	where
225		F: FnOnce(
226			&mut dyn WasmInstance,
227			Option<&RuntimeVersion>,
228			&mut dyn Externalities,
229		) -> Result<R, Error>,
230	{
231		let mut instance = self.interruptible_module.get()?.new_instance(heap_alloc_strategy)?;
232
233		f(&mut *instance, self.version.as_ref(), ext)
234	}
235}
236
237/// Cache for the runtimes.
238///
239/// When an instance is requested for the first time it is added to this cache. Metadata is kept
240/// with the instance so that it can be efficiently reinitialized.
241///
242/// When using the Wasmi interpreter execution method, the metadata includes the initial memory and
243/// values of mutable globals. Follow-up requests to fetch a runtime return this one instance with
244/// the memory reset to the initial memory. So, one runtime instance is reused for every fetch
245/// request.
246///
247/// The size of cache is configurable via the cli option `--runtime-cache-size`.
248pub struct RuntimeCache {
249	/// A cache of runtimes along with metadata.
250	///
251	/// Runtimes sorted by recent usage. The most recently used is at the front.
252	runtimes: Mutex<LruMap<VersionedRuntimeId, Arc<VersionedRuntime>>>,
253	/// The size of the instances cache for each runtime.
254	max_runtime_instances: usize,
255	cache_path: Option<PathBuf>,
256}
257
258impl RuntimeCache {
259	/// Creates a new instance of a runtimes cache.
260	///
261	/// `max_runtime_instances` specifies the number of instances per runtime preserved in an
262	/// in-memory cache.
263	///
264	/// `cache_path` allows to specify an optional directory where the executor can store files
265	/// for caching.
266	///
267	/// `runtime_cache_size` specifies the number of different runtimes versions preserved in an
268	/// in-memory cache, must always be at least 1.
269	pub fn new(
270		max_runtime_instances: usize,
271		cache_path: Option<PathBuf>,
272		runtime_cache_size: u8,
273	) -> RuntimeCache {
274		let cap = ByLength::new(runtime_cache_size.max(1) as u32);
275		RuntimeCache { runtimes: Mutex::new(LruMap::new(cap)), max_runtime_instances, cache_path }
276	}
277
278	/// Prepares a WASM module instance and executes given function for it.
279	///
280	/// This uses internal cache to find available instance or create a new one.
281	/// # Parameters
282	///
283	/// `runtime_code` - The runtime wasm code used setup the runtime.
284	///
285	/// `ext` - The externalities to access the state.
286	///
287	/// `wasm_method` - Type of WASM backend to use.
288	///
289	/// `heap_alloc_strategy` - The heap allocation strategy to use.
290	///
291	/// `allow_missing_func_imports` - Ignore missing function imports.
292	///
293	/// `f` - Function to execute.
294	///
295	/// `H` - A compile-time list of host functions to expose to the runtime.
296	///
297	/// # Returns result of `f` wrapped in an additional result.
298	/// In case of failure one of two errors can be returned:
299	///
300	/// `Err::RuntimeConstruction` is returned for runtime construction issues.
301	///
302	/// `Error::InvalidMemoryReference` is returned if no memory export with the
303	/// identifier `memory` can be found in the runtime.
304	pub fn with_instance<'c, H, R, F>(
305		&self,
306		runtime_code: &'c RuntimeCode<'c>,
307		ext: &mut dyn Externalities,
308		wasm_method: WasmExecutionMethod,
309		heap_alloc_strategy: HeapAllocStrategy,
310		allow_missing_func_imports: bool,
311		f: F,
312	) -> Result<Result<R, Error>, Error>
313	where
314		H: HostFunctions,
315		F: FnOnce(
316			&dyn WasmModule,
317			&mut dyn WasmInstance,
318			Option<&RuntimeVersion>,
319			&mut dyn Externalities,
320		) -> Result<R, Error>,
321	{
322		let versioned_runtime = self.versioned_runtime::<H>(
323			runtime_code,
324			ext,
325			wasm_method,
326			heap_alloc_strategy,
327			allow_missing_func_imports,
328		)?;
329
330		Ok(versioned_runtime.with_instance(ext, heap_alloc_strategy, f))
331	}
332
333	/// Prepares a fresh WASM module instance supporting execution timeouts and executes given
334	/// function for it.
335	///
336	/// Same as [`Self::with_instance`], but `f` gets an instance on which
337	/// [`WasmInstance::call_with_timeout`] works. May block until the interruptible module is
338	/// available, compiling it if needed.
339	///
340	/// NOTE: engines without an execution-interruption mechanism (PolkaVM) ignore the timeout
341	/// and run uncapped.
342	pub fn with_interruptible_instance<'c, H, R, F>(
343		&self,
344		runtime_code: &'c RuntimeCode<'c>,
345		ext: &mut dyn Externalities,
346		wasm_method: WasmExecutionMethod,
347		heap_alloc_strategy: HeapAllocStrategy,
348		allow_missing_func_imports: bool,
349		f: F,
350	) -> Result<Result<R, Error>, Error>
351	where
352		H: HostFunctions,
353		F: FnOnce(
354			&mut dyn WasmInstance,
355			Option<&RuntimeVersion>,
356			&mut dyn Externalities,
357		) -> Result<R, Error>,
358	{
359		let versioned_runtime = self.versioned_runtime::<H>(
360			runtime_code,
361			ext,
362			wasm_method,
363			heap_alloc_strategy,
364			allow_missing_func_imports,
365		)?;
366
367		Ok(versioned_runtime.with_interruptible_instance(ext, heap_alloc_strategy, f))
368	}
369
370	/// Get the cached [`VersionedRuntime`] for the given code, creating and caching it first if
371	/// needed.
372	fn versioned_runtime<H>(
373		&self,
374		runtime_code: &RuntimeCode,
375		ext: &mut dyn Externalities,
376		wasm_method: WasmExecutionMethod,
377		heap_alloc_strategy: HeapAllocStrategy,
378		allow_missing_func_imports: bool,
379	) -> Result<Arc<VersionedRuntime>, WasmError>
380	where
381		H: HostFunctions,
382	{
383		let code_hash = &runtime_code.hash;
384
385		let versioned_runtime_id = VersionedRuntimeId { code_hash: code_hash.clone(), wasm_method };
386
387		// The lock is released when the guard goes out of scope, prior to calling any
388		// instance method.
389		let mut runtimes = self.runtimes.lock();
390		if let Some(versioned_runtime) = runtimes.get(&versioned_runtime_id) {
391			return Ok(versioned_runtime.clone());
392		}
393
394		let code = runtime_code.fetch_runtime_code().ok_or(WasmError::CodeNotFound)?;
395
396		let time = std::time::Instant::now();
397
398		let result = create_versioned_wasm_runtime::<H>(
399			&code,
400			ext,
401			wasm_method,
402			heap_alloc_strategy,
403			allow_missing_func_imports,
404			self.max_runtime_instances,
405			self.cache_path.as_deref(),
406		);
407
408		match result {
409			Ok(ref result) => {
410				tracing::debug!(
411					target: "wasm-runtime",
412					"Prepared new runtime version {:?} in {} ms.",
413					result.version,
414					time.elapsed().as_millis(),
415				);
416			},
417			Err(ref err) => {
418				tracing::warn!(target: "wasm-runtime", error = ?err, "Cannot create a runtime");
419			},
420		}
421
422		let versioned_runtime = Arc::new(result?);
423
424		// Save new versioned wasm runtime in cache
425		runtimes.insert(versioned_runtime_id, versioned_runtime.clone());
426
427		Ok(versioned_runtime)
428	}
429}
430
431fn wasmtime_config(
432	heap_alloc_strategy: HeapAllocStrategy,
433	instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy,
434	allow_missing_func_imports: bool,
435	cache_path: Option<&Path>,
436) -> sc_executor_wasmtime::Config {
437	sc_executor_wasmtime::Config {
438		allow_missing_func_imports,
439		cache_path: cache_path.map(ToOwned::to_owned),
440		semantics: sc_executor_wasmtime::Semantics {
441			heap_alloc_strategy,
442			instantiation_strategy,
443			deterministic_stack_limit: None,
444			canonicalize_nans: false,
445			parallel_compilation: true,
446			wasm_multi_value: false,
447			wasm_bulk_memory: false,
448			wasm_reference_types: false,
449			wasm_simd: false,
450			epoch_interruption: false,
451		},
452	}
453}
454
455/// Create a wasm runtime with the given `code`.
456pub fn create_wasm_runtime_with_code<H>(
457	wasm_method: WasmExecutionMethod,
458	heap_alloc_strategy: HeapAllocStrategy,
459	blob: RuntimeBlob,
460	allow_missing_func_imports: bool,
461	cache_path: Option<&Path>,
462) -> Result<Box<dyn WasmModule>, WasmError>
463where
464	H: HostFunctions,
465{
466	if let Some(blob) = blob.as_polkavm_blob() {
467		return sc_executor_polkavm::create_runtime::<H>(blob)
468			.map(|pre| -> Box<dyn WasmModule> { Box::new(pre) });
469	}
470
471	match wasm_method {
472		WasmExecutionMethod::Compiled { instantiation_strategy } => {
473			sc_executor_wasmtime::create_runtime::<H>(
474				blob,
475				wasmtime_config(
476					heap_alloc_strategy,
477					instantiation_strategy,
478					allow_missing_func_imports,
479					cache_path,
480				),
481			)
482			.map(|runtime| -> Box<dyn WasmModule> { Box::new(runtime) })
483		},
484	}
485}
486
487/// Like [`create_wasm_runtime_with_code`], but also provides the runtime compiled with support
488/// for execution timeouts. The latter is not compiled yet, see [`InterruptibleModule`].
489fn create_wasm_runtimes<H>(
490	wasm_method: WasmExecutionMethod,
491	heap_alloc_strategy: HeapAllocStrategy,
492	blob: RuntimeBlob,
493	allow_missing_func_imports: bool,
494	cache_path: Option<&Path>,
495) -> Result<(Box<dyn WasmModule>, InterruptibleModule), WasmError>
496where
497	H: HostFunctions,
498{
499	if let Some(program_blob) = blob.as_polkavm_blob() {
500		static POLKAVM_TIMEOUT_WARN: std::sync::Once = std::sync::Once::new();
501		POLKAVM_TIMEOUT_WARN.call_once(|| {
502			tracing::warn!(
503				target: "wasm-runtime",
504				"PolkaVM does not support execution timeouts; runtime calls with a timeout will \
505				 run uncapped",
506			);
507		});
508
509		// One compile, two handles: `InstancePre` clones via `Arc`.
510		let pre = sc_executor_polkavm::create_runtime::<H>(program_blob)?;
511		let module: Box<dyn WasmModule> = Box::new(pre.clone());
512		let interruptible_module =
513			InterruptibleModule::new(move || Ok(Box::new(pre) as Box<dyn WasmModule>));
514
515		return Ok((module, interruptible_module));
516	}
517
518	let interruptible_blob = blob.clone();
519	let runtime = create_wasm_runtime_with_code::<H>(
520		wasm_method,
521		heap_alloc_strategy,
522		blob,
523		allow_missing_func_imports,
524		cache_path,
525	)?;
526
527	let WasmExecutionMethod::Compiled { instantiation_strategy } = wasm_method;
528
529	// Reusing `cache_path` is safe: wasmtime keys on-disk artifacts on the full config,
530	// including the epoch-interruption flag.
531	let mut config = wasmtime_config(
532		heap_alloc_strategy,
533		instantiation_strategy,
534		allow_missing_func_imports,
535		cache_path,
536	);
537	config.semantics.epoch_interruption = true;
538
539	let interruptible_module = InterruptibleModule::new(move || {
540		let time = std::time::Instant::now();
541
542		let result = sc_executor_wasmtime::create_runtime::<H>(interruptible_blob, config)
543			.map(|runtime| -> Box<dyn WasmModule> { Box::new(runtime) });
544
545		match result {
546			Ok(_) => {
547				tracing::debug!(
548					target: "wasm-runtime",
549					"Prepared new interruptible runtime in {} ms.",
550					time.elapsed().as_millis(),
551				);
552			},
553			Err(ref err) => {
554				tracing::warn!(
555					target: "wasm-runtime",
556					error = ?err,
557					"Cannot create an interruptible runtime",
558				);
559			},
560		}
561
562		result
563	});
564
565	Ok((runtime, interruptible_module))
566}
567
568fn decode_version(mut version: &[u8]) -> Result<RuntimeVersion, WasmError> {
569	Decode::decode(&mut version).map_err(|_| {
570		WasmError::Instantiation(
571			"failed to decode \"Core_version\" result using old runtime version".into(),
572		)
573	})
574}
575
576fn decode_runtime_apis(apis: &[u8]) -> Result<Vec<([u8; 8], u32)>, WasmError> {
577	use sp_api::RUNTIME_API_INFO_SIZE;
578
579	apis.chunks(RUNTIME_API_INFO_SIZE)
580		.map(|chunk| {
581			// `chunk` can be less than `RUNTIME_API_INFO_SIZE` if the total length of `apis`
582			// doesn't completely divide by `RUNTIME_API_INFO_SIZE`.
583			<[u8; RUNTIME_API_INFO_SIZE]>::try_from(chunk)
584				.map(sp_api::deserialize_runtime_api_info)
585				.map_err(|_| WasmError::Other("a clipped runtime api info declaration".to_owned()))
586		})
587		.collect::<Result<Vec<_>, WasmError>>()
588}
589
590/// Take the runtime blob and scan it for the custom wasm sections containing the version
591/// information and construct the `RuntimeVersion` from them.
592///
593/// If there are no such sections, it returns `None`. If there is an error during decoding those
594/// sections, `Err` will be returned.
595pub fn read_embedded_version(blob: &RuntimeBlob) -> Result<Option<RuntimeVersion>, WasmError> {
596	if let Some(mut version_section) = blob.custom_section_contents("runtime_version") {
597		let apis = blob
598			.custom_section_contents("runtime_apis")
599			.map(decode_runtime_apis)
600			.transpose()?
601			.map(Into::into);
602
603		let core_version = apis.as_ref().and_then(sp_version::core_version_from_apis);
604		// We do not use `RuntimeVersion::decode` here because that `decode_version` relies on
605		// presence of a special API in the `apis` field to treat the input as a non-legacy version.
606		// However the structure found in the `runtime_version` always contain an empty `apis`
607		// field. Therefore the version read will be mistakenly treated as an legacy one.
608		let mut decoded_version = sp_version::RuntimeVersion::decode_with_version_hint(
609			&mut version_section,
610			core_version,
611		)
612		.map_err(|_| WasmError::Instantiation("failed to decode version section".into()))?;
613
614		if let Some(apis) = apis {
615			decoded_version.apis = apis;
616		}
617
618		Ok(Some(decoded_version))
619	} else {
620		Ok(None)
621	}
622}
623
624fn create_versioned_wasm_runtime<H>(
625	code: &[u8],
626	ext: &mut dyn Externalities,
627	wasm_method: WasmExecutionMethod,
628	heap_alloc_strategy: HeapAllocStrategy,
629	allow_missing_func_imports: bool,
630	max_instances: usize,
631	cache_path: Option<&Path>,
632) -> Result<VersionedRuntime, WasmError>
633where
634	H: HostFunctions,
635{
636	// The incoming code may be actually compressed. We decompress it here and then work with
637	// the uncompressed code from now on.
638	let blob = sc_executor_common::runtime_blob::RuntimeBlob::uncompress_if_needed(code)?;
639
640	// Use the runtime blob to scan if there is any metadata embedded into the wasm binary
641	// pertaining to runtime version. We do it before consuming the runtime blob for creating the
642	// runtime.
643	let mut version = read_embedded_version(&blob)?;
644
645	let (runtime, interruptible_module) = create_wasm_runtimes::<H>(
646		wasm_method,
647		heap_alloc_strategy,
648		blob,
649		allow_missing_func_imports,
650		cache_path,
651	)?;
652
653	// If the runtime blob doesn't embed the runtime version then use the legacy version query
654	// mechanism: call the runtime.
655	if version.is_none() {
656		// Call to determine runtime version.
657		let version_result = {
658			// `ext` is already implicitly handled as unwind safe, as we store it in a global
659			// variable.
660			let mut ext = AssertUnwindSafe(ext);
661
662			// The following unwind safety assertion is OK because if the method call panics, the
663			// runtime will be dropped.
664			let runtime = AssertUnwindSafe(runtime.as_ref());
665			crate::executor::with_externalities_safe(&mut **ext, move || {
666				runtime.new_instance(heap_alloc_strategy)?.call("Core_version".into(), &[])
667			})
668			.map_err(|_| WasmError::Instantiation("panic in call to get runtime version".into()))?
669		};
670
671		if let Ok(version_buf) = version_result {
672			version = Some(decode_version(&version_buf)?)
673		}
674	}
675
676	// The runtime is usable, so compile its interruptible variant ahead of the first call with a
677	// timeout.
678	interruptible_module.compile_in_background();
679
680	let mut instances = Vec::with_capacity(max_instances);
681	instances.resize_with(max_instances, || Mutex::new(None));
682
683	Ok(VersionedRuntime { module: runtime, interruptible_module, version, instances })
684}
685
686#[cfg(test)]
687mod tests {
688	extern crate alloc;
689
690	use super::*;
691	use alloc::borrow::Cow;
692	use codec::Encode;
693	use sp_api::{Core, RuntimeApiInfo};
694	use sp_version::{create_apis_vec, RuntimeVersion};
695	use sp_wasm_interface::HostFunctions;
696	use substrate_test_runtime::Block;
697
698	#[derive(Encode)]
699	pub struct OldRuntimeVersion {
700		pub spec_name: Cow<'static, str>,
701		pub impl_name: Cow<'static, str>,
702		pub authoring_version: u32,
703		pub spec_version: u32,
704		pub impl_version: u32,
705		pub apis: sp_version::ApisVec,
706	}
707
708	#[test]
709	fn host_functions_are_equal() {
710		let host_functions = sp_io::SubstrateHostFunctions::host_functions();
711
712		let equal = &host_functions[..] == &host_functions[..];
713		assert!(equal, "Host functions are not equal");
714	}
715
716	#[test]
717	fn old_runtime_version_decodes() {
718		let old_runtime_version = OldRuntimeVersion {
719			spec_name: "test".into(),
720			impl_name: "test".into(),
721			authoring_version: 1,
722			spec_version: 1,
723			impl_version: 1,
724			apis: create_apis_vec!([(<dyn Core::<Block>>::ID, 1)]),
725		};
726
727		let version = decode_version(&old_runtime_version.encode()).unwrap();
728		assert_eq!(1, version.transaction_version);
729		assert_eq!(0, version.system_version);
730	}
731
732	#[test]
733	fn old_runtime_version_decodes_fails_with_version_3() {
734		let old_runtime_version = OldRuntimeVersion {
735			spec_name: "test".into(),
736			impl_name: "test".into(),
737			authoring_version: 1,
738			spec_version: 1,
739			impl_version: 1,
740			apis: create_apis_vec!([(<dyn Core::<Block>>::ID, 3)]),
741		};
742
743		decode_version(&old_runtime_version.encode()).unwrap_err();
744	}
745
746	#[test]
747	fn new_runtime_version_decodes() {
748		let old_runtime_version = RuntimeVersion {
749			spec_name: "test".into(),
750			impl_name: "test".into(),
751			authoring_version: 1,
752			spec_version: 1,
753			impl_version: 1,
754			apis: create_apis_vec!([(<dyn Core::<Block>>::ID, 3)]),
755			transaction_version: 3,
756			system_version: 4,
757		};
758
759		let version = decode_version(&old_runtime_version.encode()).unwrap();
760		assert_eq!(3, version.transaction_version);
761		assert_eq!(0, version.system_version);
762
763		let old_runtime_version = RuntimeVersion {
764			spec_name: "test".into(),
765			impl_name: "test".into(),
766			authoring_version: 1,
767			spec_version: 1,
768			impl_version: 1,
769			apis: create_apis_vec!([(<dyn Core::<Block>>::ID, 4)]),
770			transaction_version: 3,
771			system_version: 4,
772		};
773
774		let version = decode_version(&old_runtime_version.encode()).unwrap();
775		assert_eq!(3, version.transaction_version);
776		assert_eq!(4, version.system_version);
777	}
778
779	#[test]
780	fn embed_runtime_version_works() {
781		let wasm = sp_maybe_compressed_blob::decompress(
782			substrate_test_runtime::wasm_binary_unwrap(),
783			sp_maybe_compressed_blob::CODE_BLOB_BOMB_LIMIT,
784		)
785		.expect("Decompressing works");
786		let runtime_version = RuntimeVersion {
787			spec_name: "test_replace".into(),
788			impl_name: "test_replace".into(),
789			authoring_version: 100,
790			spec_version: 100,
791			impl_version: 100,
792			apis: create_apis_vec!([(<dyn Core::<Block>>::ID, 4)]),
793			transaction_version: 100,
794			system_version: 1,
795		};
796
797		let embedded = sp_version::embed::embed_runtime_version(&wasm, runtime_version.clone())
798			.expect("Embedding works");
799
800		let blob = RuntimeBlob::new(&embedded).expect("Embedded blob is valid");
801		let read_version = read_embedded_version(&blob)
802			.ok()
803			.flatten()
804			.expect("Reading embedded version works");
805
806		assert_eq!(runtime_version, read_version);
807	}
808}