1use 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,
41};
42
43#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
45pub enum WasmExecutionMethod {
46 Compiled {
48 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 code_hash: Vec<u8>,
65 wasm_method: WasmExecutionMethod,
67}
68
69struct VersionedRuntime {
71 module: Box<dyn WasmModule>,
73 version: Option<RuntimeVersion>,
75
76 instances: Vec<Mutex<Option<Box<dyn WasmInstance>>>>,
80}
81
82impl VersionedRuntime {
83 fn with_instance<R, F>(
85 &self,
86 ext: &mut dyn Externalities,
87 heap_alloc_strategy: HeapAllocStrategy,
88 f: F,
89 ) -> Result<R, Error>
90 where
91 F: FnOnce(
92 &dyn WasmModule,
93 &mut dyn WasmInstance,
94 Option<&RuntimeVersion>,
95 &mut dyn Externalities,
96 ) -> Result<R, Error>,
97 {
98 let instance = self
100 .instances
101 .iter()
102 .enumerate()
103 .find_map(|(index, i)| i.try_lock().map(|i| (index, i)));
104
105 match instance {
106 Some((index, mut locked)) => {
107 let (mut instance, new_inst) =
108 locked.take().map(|r| Ok((r, false))).unwrap_or_else(|| {
109 self.module.new_instance(heap_alloc_strategy).map(|i| (i, true))
110 })?;
111
112 if !new_inst {
116 instance.set_heap_alloc_strategy(heap_alloc_strategy);
117 }
118
119 let result = f(&*self.module, &mut *instance, self.version.as_ref(), ext);
120 if let Err(e) = &result {
121 if new_inst {
122 tracing::warn!(
123 target: "wasm-runtime",
124 error = %e,
125 "Fresh runtime instance failed",
126 )
127 } else {
128 tracing::warn!(
129 target: "wasm-runtime",
130 error = %e,
131 "Evicting failed runtime instance",
132 );
133 }
134 } else {
135 *locked = Some(instance);
136
137 if new_inst {
138 tracing::debug!(
139 target: "wasm-runtime",
140 "Allocated WASM instance {}/{}",
141 index + 1,
142 self.instances.len(),
143 );
144 }
145 }
146
147 result
148 },
149 None => {
150 tracing::warn!(target: "wasm-runtime", "Ran out of free WASM instances");
151
152 let mut instance = self.module.new_instance(heap_alloc_strategy)?;
154
155 f(&*self.module, &mut *instance, self.version.as_ref(), ext)
156 },
157 }
158 }
159}
160
161pub struct RuntimeCache {
173 runtimes: Mutex<LruMap<VersionedRuntimeId, Arc<VersionedRuntime>>>,
177 max_runtime_instances: usize,
179 cache_path: Option<PathBuf>,
180}
181
182impl RuntimeCache {
183 pub fn new(
194 max_runtime_instances: usize,
195 cache_path: Option<PathBuf>,
196 runtime_cache_size: u8,
197 ) -> RuntimeCache {
198 let cap = ByLength::new(runtime_cache_size.max(1) as u32);
199 RuntimeCache { runtimes: Mutex::new(LruMap::new(cap)), max_runtime_instances, cache_path }
200 }
201
202 pub fn with_instance<'c, H, R, F>(
229 &self,
230 runtime_code: &'c RuntimeCode<'c>,
231 ext: &mut dyn Externalities,
232 wasm_method: WasmExecutionMethod,
233 heap_alloc_strategy: HeapAllocStrategy,
234 allow_missing_func_imports: bool,
235 f: F,
236 ) -> Result<Result<R, Error>, Error>
237 where
238 H: HostFunctions,
239 F: FnOnce(
240 &dyn WasmModule,
241 &mut dyn WasmInstance,
242 Option<&RuntimeVersion>,
243 &mut dyn Externalities,
244 ) -> Result<R, Error>,
245 {
246 let code_hash = &runtime_code.hash;
247
248 let versioned_runtime_id = VersionedRuntimeId { code_hash: code_hash.clone(), wasm_method };
249
250 let mut runtimes = self.runtimes.lock(); let versioned_runtime = if let Some(versioned_runtime) = runtimes.get(&versioned_runtime_id)
252 {
253 versioned_runtime.clone()
254 } else {
255 let code = runtime_code.fetch_runtime_code().ok_or(WasmError::CodeNotFound)?;
256
257 let time = std::time::Instant::now();
258
259 let result = create_versioned_wasm_runtime::<H>(
260 &code,
261 ext,
262 wasm_method,
263 heap_alloc_strategy,
264 allow_missing_func_imports,
265 self.max_runtime_instances,
266 self.cache_path.as_deref(),
267 );
268
269 match result {
270 Ok(ref result) => {
271 tracing::debug!(
272 target: "wasm-runtime",
273 "Prepared new runtime version {:?} in {} ms.",
274 result.version,
275 time.elapsed().as_millis(),
276 );
277 },
278 Err(ref err) => {
279 tracing::warn!(target: "wasm-runtime", error = ?err, "Cannot create a runtime");
280 },
281 }
282
283 let versioned_runtime = Arc::new(result?);
284
285 runtimes.insert(versioned_runtime_id, versioned_runtime.clone());
287
288 versioned_runtime
289 };
290
291 drop(runtimes);
293
294 Ok(versioned_runtime.with_instance(ext, heap_alloc_strategy, f))
295 }
296}
297
298pub fn create_wasm_runtime_with_code<H>(
300 wasm_method: WasmExecutionMethod,
301 heap_alloc_strategy: HeapAllocStrategy,
302 blob: RuntimeBlob,
303 allow_missing_func_imports: bool,
304 cache_path: Option<&Path>,
305) -> Result<Box<dyn WasmModule>, WasmError>
306where
307 H: HostFunctions,
308{
309 if let Some(blob) = blob.as_polkavm_blob() {
310 return sc_executor_polkavm::create_runtime::<H>(blob);
311 }
312
313 match wasm_method {
314 WasmExecutionMethod::Compiled { instantiation_strategy } => {
315 sc_executor_wasmtime::create_runtime::<H>(
316 blob,
317 sc_executor_wasmtime::Config {
318 allow_missing_func_imports,
319 cache_path: cache_path.map(ToOwned::to_owned),
320 semantics: sc_executor_wasmtime::Semantics {
321 heap_alloc_strategy,
322 instantiation_strategy,
323 deterministic_stack_limit: None,
324 canonicalize_nans: false,
325 parallel_compilation: true,
326 wasm_multi_value: false,
327 wasm_bulk_memory: false,
328 wasm_reference_types: false,
329 wasm_simd: false,
330 },
331 },
332 )
333 .map(|runtime| -> Box<dyn WasmModule> { Box::new(runtime) })
334 },
335 }
336}
337
338fn decode_version(mut version: &[u8]) -> Result<RuntimeVersion, WasmError> {
339 Decode::decode(&mut version).map_err(|_| {
340 WasmError::Instantiation(
341 "failed to decode \"Core_version\" result using old runtime version".into(),
342 )
343 })
344}
345
346fn decode_runtime_apis(apis: &[u8]) -> Result<Vec<([u8; 8], u32)>, WasmError> {
347 use sp_api::RUNTIME_API_INFO_SIZE;
348
349 apis.chunks(RUNTIME_API_INFO_SIZE)
350 .map(|chunk| {
351 <[u8; RUNTIME_API_INFO_SIZE]>::try_from(chunk)
354 .map(sp_api::deserialize_runtime_api_info)
355 .map_err(|_| WasmError::Other("a clipped runtime api info declaration".to_owned()))
356 })
357 .collect::<Result<Vec<_>, WasmError>>()
358}
359
360pub fn read_embedded_version(blob: &RuntimeBlob) -> Result<Option<RuntimeVersion>, WasmError> {
366 if let Some(mut version_section) = blob.custom_section_contents("runtime_version") {
367 let apis = blob
368 .custom_section_contents("runtime_apis")
369 .map(decode_runtime_apis)
370 .transpose()?
371 .map(Into::into);
372
373 let core_version = apis.as_ref().and_then(sp_version::core_version_from_apis);
374 let mut decoded_version = sp_version::RuntimeVersion::decode_with_version_hint(
379 &mut version_section,
380 core_version,
381 )
382 .map_err(|_| WasmError::Instantiation("failed to decode version section".into()))?;
383
384 if let Some(apis) = apis {
385 decoded_version.apis = apis;
386 }
387
388 Ok(Some(decoded_version))
389 } else {
390 Ok(None)
391 }
392}
393
394fn create_versioned_wasm_runtime<H>(
395 code: &[u8],
396 ext: &mut dyn Externalities,
397 wasm_method: WasmExecutionMethod,
398 heap_alloc_strategy: HeapAllocStrategy,
399 allow_missing_func_imports: bool,
400 max_instances: usize,
401 cache_path: Option<&Path>,
402) -> Result<VersionedRuntime, WasmError>
403where
404 H: HostFunctions,
405{
406 let blob = sc_executor_common::runtime_blob::RuntimeBlob::uncompress_if_needed(code)?;
409
410 let mut version = read_embedded_version(&blob)?;
414
415 let runtime = create_wasm_runtime_with_code::<H>(
416 wasm_method,
417 heap_alloc_strategy,
418 blob,
419 allow_missing_func_imports,
420 cache_path,
421 )?;
422
423 if version.is_none() {
426 let version_result = {
428 let mut ext = AssertUnwindSafe(ext);
431
432 let runtime = AssertUnwindSafe(runtime.as_ref());
435 crate::executor::with_externalities_safe(&mut **ext, move || {
436 runtime.new_instance(heap_alloc_strategy)?.call("Core_version".into(), &[])
437 })
438 .map_err(|_| WasmError::Instantiation("panic in call to get runtime version".into()))?
439 };
440
441 if let Ok(version_buf) = version_result {
442 version = Some(decode_version(&version_buf)?)
443 }
444 }
445
446 let mut instances = Vec::with_capacity(max_instances);
447 instances.resize_with(max_instances, || Mutex::new(None));
448
449 Ok(VersionedRuntime { module: runtime, version, instances })
450}
451
452#[cfg(test)]
453mod tests {
454 extern crate alloc;
455
456 use super::*;
457 use alloc::borrow::Cow;
458 use codec::Encode;
459 use sp_api::{Core, RuntimeApiInfo};
460 use sp_version::{create_apis_vec, RuntimeVersion};
461 use sp_wasm_interface::HostFunctions;
462 use substrate_test_runtime::Block;
463
464 #[derive(Encode)]
465 pub struct OldRuntimeVersion {
466 pub spec_name: Cow<'static, str>,
467 pub impl_name: Cow<'static, str>,
468 pub authoring_version: u32,
469 pub spec_version: u32,
470 pub impl_version: u32,
471 pub apis: sp_version::ApisVec,
472 }
473
474 #[test]
475 fn host_functions_are_equal() {
476 let host_functions = sp_io::SubstrateHostFunctions::host_functions();
477
478 let equal = &host_functions[..] == &host_functions[..];
479 assert!(equal, "Host functions are not equal");
480 }
481
482 #[test]
483 fn old_runtime_version_decodes() {
484 let old_runtime_version = OldRuntimeVersion {
485 spec_name: "test".into(),
486 impl_name: "test".into(),
487 authoring_version: 1,
488 spec_version: 1,
489 impl_version: 1,
490 apis: create_apis_vec!([(<dyn Core::<Block>>::ID, 1)]),
491 };
492
493 let version = decode_version(&old_runtime_version.encode()).unwrap();
494 assert_eq!(1, version.transaction_version);
495 assert_eq!(0, version.system_version);
496 }
497
498 #[test]
499 fn old_runtime_version_decodes_fails_with_version_3() {
500 let old_runtime_version = OldRuntimeVersion {
501 spec_name: "test".into(),
502 impl_name: "test".into(),
503 authoring_version: 1,
504 spec_version: 1,
505 impl_version: 1,
506 apis: create_apis_vec!([(<dyn Core::<Block>>::ID, 3)]),
507 };
508
509 decode_version(&old_runtime_version.encode()).unwrap_err();
510 }
511
512 #[test]
513 fn new_runtime_version_decodes() {
514 let old_runtime_version = RuntimeVersion {
515 spec_name: "test".into(),
516 impl_name: "test".into(),
517 authoring_version: 1,
518 spec_version: 1,
519 impl_version: 1,
520 apis: create_apis_vec!([(<dyn Core::<Block>>::ID, 3)]),
521 transaction_version: 3,
522 system_version: 4,
523 };
524
525 let version = decode_version(&old_runtime_version.encode()).unwrap();
526 assert_eq!(3, version.transaction_version);
527 assert_eq!(0, version.system_version);
528
529 let old_runtime_version = RuntimeVersion {
530 spec_name: "test".into(),
531 impl_name: "test".into(),
532 authoring_version: 1,
533 spec_version: 1,
534 impl_version: 1,
535 apis: create_apis_vec!([(<dyn Core::<Block>>::ID, 4)]),
536 transaction_version: 3,
537 system_version: 4,
538 };
539
540 let version = decode_version(&old_runtime_version.encode()).unwrap();
541 assert_eq!(3, version.transaction_version);
542 assert_eq!(4, version.system_version);
543 }
544
545 #[test]
546 fn embed_runtime_version_works() {
547 let wasm = sp_maybe_compressed_blob::decompress(
548 substrate_test_runtime::wasm_binary_unwrap(),
549 sp_maybe_compressed_blob::CODE_BLOB_BOMB_LIMIT,
550 )
551 .expect("Decompressing works");
552 let runtime_version = RuntimeVersion {
553 spec_name: "test_replace".into(),
554 impl_name: "test_replace".into(),
555 authoring_version: 100,
556 spec_version: 100,
557 impl_version: 100,
558 apis: create_apis_vec!([(<dyn Core::<Block>>::ID, 4)]),
559 transaction_version: 100,
560 system_version: 1,
561 };
562
563 let embedded = sp_version::embed::embed_runtime_version(&wasm, runtime_version.clone())
564 .expect("Embedding works");
565
566 let blob = RuntimeBlob::new(&embedded).expect("Embedded blob is valid");
567 let read_version = read_embedded_version(&blob)
568 .ok()
569 .flatten()
570 .expect("Reading embedded version works");
571
572 assert_eq!(runtime_version, read_version);
573 }
574}