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