1use 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
43pub 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 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
70pub 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 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 pub fn with_execution_method(mut self, method: WasmExecutionMethod) -> Self {
103 self.method = method;
104 self
105 }
106
107 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 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 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 pub fn with_max_runtime_instances(mut self, instances: usize) -> Self {
142 self.max_runtime_instances = instances;
143 self
144 }
145
146 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 pub fn with_allow_missing_host_functions(mut self, allow: bool) -> Self {
166 self.allow_missing_host_functions = allow;
167 self
168 }
169
170 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 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
204pub struct WasmExecutor<H = sp_io::SubstrateHostFunctions> {
207 method: WasmExecutionMethod,
209 default_onchain_heap_alloc_strategy: HeapAllocStrategy,
211 default_offchain_heap_alloc_strategy: HeapAllocStrategy,
213 ignore_onchain_heap_pages: bool,
215 cache: Arc<RuntimeCache>,
217 cache_path: Option<PathBuf>,
220 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 #[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 pub fn builder() -> WasmExecutorBuilder<H> {
295 WasmExecutorBuilder::new()
296 }
297
298 #[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 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 #[doc(hidden)] 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 #[doc(hidden)] 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 self.uncached_call(
463 runtime_blob,
464 ext,
465 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}