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::{GetNativeVersion, NativeVersion, RuntimeVersion};
41use sp_wasm_interface::{ExtendedHostFunctions, 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
66pub trait NativeExecutionDispatch: Send + Sync {
70 type ExtendHostFunctions: HostFunctions;
73
74 fn dispatch(method: &str, data: &[u8]) -> Option<Vec<u8>>;
76
77 fn native_version() -> NativeVersion;
79}
80
81fn unwrap_heap_pages(pages: Option<HeapAllocStrategy>) -> HeapAllocStrategy {
82 pages.unwrap_or_else(|| DEFAULT_HEAP_ALLOC_STRATEGY)
83}
84
85pub struct WasmExecutorBuilder<H = sp_io::SubstrateHostFunctions> {
87 _phantom: PhantomData<H>,
88 method: WasmExecutionMethod,
89 onchain_heap_alloc_strategy: Option<HeapAllocStrategy>,
90 offchain_heap_alloc_strategy: Option<HeapAllocStrategy>,
91 ignore_onchain_heap_pages: bool,
92 max_runtime_instances: usize,
93 cache_path: Option<PathBuf>,
94 allow_missing_host_functions: bool,
95 runtime_cache_size: u8,
96}
97
98impl<H> WasmExecutorBuilder<H> {
99 pub fn new() -> Self {
103 Self {
104 _phantom: PhantomData,
105 method: WasmExecutionMethod::default(),
106 onchain_heap_alloc_strategy: None,
107 offchain_heap_alloc_strategy: None,
108 ignore_onchain_heap_pages: false,
109 max_runtime_instances: 2,
110 runtime_cache_size: 4,
111 allow_missing_host_functions: false,
112 cache_path: None,
113 }
114 }
115
116 pub fn with_execution_method(mut self, method: WasmExecutionMethod) -> Self {
118 self.method = method;
119 self
120 }
121
122 pub fn with_onchain_heap_alloc_strategy(
125 mut self,
126 heap_alloc_strategy: HeapAllocStrategy,
127 ) -> Self {
128 self.onchain_heap_alloc_strategy = Some(heap_alloc_strategy);
129 self
130 }
131
132 pub fn with_offchain_heap_alloc_strategy(
135 mut self,
136 heap_alloc_strategy: HeapAllocStrategy,
137 ) -> Self {
138 self.offchain_heap_alloc_strategy = Some(heap_alloc_strategy);
139 self
140 }
141
142 pub fn with_ignore_onchain_heap_pages(mut self, ignore_onchain_heap_pages: bool) -> Self {
146 self.ignore_onchain_heap_pages = ignore_onchain_heap_pages;
147 self
148 }
149
150 pub fn with_max_runtime_instances(mut self, instances: usize) -> Self {
157 self.max_runtime_instances = instances;
158 self
159 }
160
161 pub fn with_cache_path(mut self, cache_path: impl Into<PathBuf>) -> Self {
169 self.cache_path = Some(cache_path.into());
170 self
171 }
172
173 pub fn with_allow_missing_host_functions(mut self, allow: bool) -> Self {
181 self.allow_missing_host_functions = allow;
182 self
183 }
184
185 pub fn with_runtime_cache_size(mut self, runtime_cache_size: u8) -> Self {
192 self.runtime_cache_size = runtime_cache_size;
193 self
194 }
195
196 pub fn build(self) -> WasmExecutor<H> {
198 WasmExecutor {
199 method: self.method,
200 default_offchain_heap_alloc_strategy: unwrap_heap_pages(
201 self.offchain_heap_alloc_strategy,
202 ),
203 default_onchain_heap_alloc_strategy: unwrap_heap_pages(
204 self.onchain_heap_alloc_strategy,
205 ),
206 ignore_onchain_heap_pages: self.ignore_onchain_heap_pages,
207 cache: Arc::new(RuntimeCache::new(
208 self.max_runtime_instances,
209 self.cache_path.clone(),
210 self.runtime_cache_size,
211 )),
212 cache_path: self.cache_path,
213 allow_missing_host_functions: self.allow_missing_host_functions,
214 phantom: PhantomData,
215 }
216 }
217}
218
219pub struct WasmExecutor<H = sp_io::SubstrateHostFunctions> {
222 method: WasmExecutionMethod,
224 default_onchain_heap_alloc_strategy: HeapAllocStrategy,
226 default_offchain_heap_alloc_strategy: HeapAllocStrategy,
228 ignore_onchain_heap_pages: bool,
230 cache: Arc<RuntimeCache>,
232 cache_path: Option<PathBuf>,
235 allow_missing_host_functions: bool,
237 phantom: PhantomData<H>,
238}
239
240impl<H> Clone for WasmExecutor<H> {
241 fn clone(&self) -> Self {
242 Self {
243 method: self.method,
244 default_onchain_heap_alloc_strategy: self.default_onchain_heap_alloc_strategy,
245 default_offchain_heap_alloc_strategy: self.default_offchain_heap_alloc_strategy,
246 ignore_onchain_heap_pages: self.ignore_onchain_heap_pages,
247 cache: self.cache.clone(),
248 cache_path: self.cache_path.clone(),
249 allow_missing_host_functions: self.allow_missing_host_functions,
250 phantom: self.phantom,
251 }
252 }
253}
254
255impl Default for WasmExecutor<sp_io::SubstrateHostFunctions> {
256 fn default() -> Self {
257 WasmExecutorBuilder::new().build()
258 }
259}
260
261impl<H> WasmExecutor<H> {
262 #[deprecated(note = "use `Self::builder` method instead of it")]
281 pub fn new(
282 method: WasmExecutionMethod,
283 default_heap_pages: Option<u64>,
284 max_runtime_instances: usize,
285 cache_path: Option<PathBuf>,
286 runtime_cache_size: u8,
287 ) -> Self {
288 WasmExecutor {
289 method,
290 default_onchain_heap_alloc_strategy: unwrap_heap_pages(
291 default_heap_pages.map(|h| HeapAllocStrategy::Static { extra_pages: h as _ }),
292 ),
293 default_offchain_heap_alloc_strategy: unwrap_heap_pages(
294 default_heap_pages.map(|h| HeapAllocStrategy::Static { extra_pages: h as _ }),
295 ),
296 ignore_onchain_heap_pages: false,
297 cache: Arc::new(RuntimeCache::new(
298 max_runtime_instances,
299 cache_path.clone(),
300 runtime_cache_size,
301 )),
302 cache_path,
303 allow_missing_host_functions: false,
304 phantom: PhantomData,
305 }
306 }
307
308 pub fn builder() -> WasmExecutorBuilder<H> {
310 WasmExecutorBuilder::new()
311 }
312
313 #[deprecated(note = "use `Self::builder` method instead of it")]
315 pub fn allow_missing_host_functions(&mut self, allow_missing_host_functions: bool) {
316 self.allow_missing_host_functions = allow_missing_host_functions
317 }
318}
319
320impl<H> WasmExecutor<H>
321where
322 H: HostFunctions,
323{
324 pub fn with_instance<R, F>(
338 &self,
339 runtime_code: &RuntimeCode,
340 ext: &mut dyn Externalities,
341 heap_alloc_strategy: HeapAllocStrategy,
342 f: F,
343 ) -> Result<R>
344 where
345 F: FnOnce(
346 AssertUnwindSafe<&dyn WasmModule>,
347 AssertUnwindSafe<&mut dyn WasmInstance>,
348 Option<&RuntimeVersion>,
349 AssertUnwindSafe<&mut dyn Externalities>,
350 ) -> Result<Result<R>>,
351 {
352 match self.cache.with_instance::<H, _, _>(
353 runtime_code,
354 ext,
355 self.method,
356 heap_alloc_strategy,
357 self.allow_missing_host_functions,
358 |module, instance, version, ext| {
359 let module = AssertUnwindSafe(module);
360 let instance = AssertUnwindSafe(instance);
361 let ext = AssertUnwindSafe(ext);
362 f(module, instance, version, ext)
363 },
364 )? {
365 Ok(r) => r,
366 Err(e) => Err(e),
367 }
368 }
369
370 #[doc(hidden)] pub fn uncached_call(
379 &self,
380 runtime_blob: RuntimeBlob,
381 ext: &mut dyn Externalities,
382 allow_missing_host_functions: bool,
383 export_name: &str,
384 call_data: &[u8],
385 ) -> std::result::Result<Vec<u8>, Error> {
386 self.uncached_call_impl(
387 runtime_blob,
388 ext,
389 allow_missing_host_functions,
390 export_name,
391 call_data,
392 &mut None,
393 )
394 }
395
396 #[doc(hidden)] pub fn uncached_call_with_allocation_stats(
399 &self,
400 runtime_blob: RuntimeBlob,
401 ext: &mut dyn Externalities,
402 allow_missing_host_functions: bool,
403 export_name: &str,
404 call_data: &[u8],
405 ) -> (std::result::Result<Vec<u8>, Error>, Option<AllocationStats>) {
406 let mut allocation_stats = None;
407 let result = self.uncached_call_impl(
408 runtime_blob,
409 ext,
410 allow_missing_host_functions,
411 export_name,
412 call_data,
413 &mut allocation_stats,
414 );
415 (result, allocation_stats)
416 }
417
418 fn uncached_call_impl(
419 &self,
420 runtime_blob: RuntimeBlob,
421 ext: &mut dyn Externalities,
422 allow_missing_host_functions: bool,
423 export_name: &str,
424 call_data: &[u8],
425 allocation_stats_out: &mut Option<AllocationStats>,
426 ) -> std::result::Result<Vec<u8>, Error> {
427 let module = crate::wasm_runtime::create_wasm_runtime_with_code::<H>(
428 self.method,
429 self.default_onchain_heap_alloc_strategy,
430 runtime_blob,
431 allow_missing_host_functions,
432 self.cache_path.as_deref(),
433 )
434 .map_err(|e| format!("Failed to create module: {}", e))?;
435
436 let instance = module
437 .new_instance(self.default_onchain_heap_alloc_strategy)
438 .map_err(|e| format!("Failed to create instance: {}", e))?;
439
440 let mut instance = AssertUnwindSafe(instance);
441 let mut ext = AssertUnwindSafe(ext);
442 let mut allocation_stats_out = AssertUnwindSafe(allocation_stats_out);
443
444 with_externalities_safe(&mut **ext, move || {
445 let (result, allocation_stats) =
446 instance.call_with_allocation_stats(export_name.into(), call_data);
447 **allocation_stats_out = allocation_stats;
448 result
449 })
450 .and_then(|r| r)
451 }
452}
453
454impl<H> sp_core::traits::ReadRuntimeVersion for WasmExecutor<H>
455where
456 H: HostFunctions,
457{
458 fn read_runtime_version(
459 &self,
460 wasm_code: &[u8],
461 ext: &mut dyn Externalities,
462 ) -> std::result::Result<Vec<u8>, String> {
463 let runtime_blob = RuntimeBlob::uncompress_if_needed(wasm_code)
464 .map_err(|e| format!("Failed to create runtime blob: {:?}", e))?;
465
466 if let Some(version) = crate::wasm_runtime::read_embedded_version(&runtime_blob)
467 .map_err(|e| format!("Failed to read the static section: {:?}", e))
468 .map(|v| v.map(|v| v.encode()))?
469 {
470 return Ok(version);
471 }
472
473 self.uncached_call(
478 runtime_blob,
479 ext,
480 true,
485 "Core_version",
486 &[],
487 )
488 .map_err(|e| e.to_string())
489 }
490}
491
492impl<H> CodeExecutor for WasmExecutor<H>
493where
494 H: HostFunctions,
495{
496 type Error = Error;
497
498 fn call(
499 &self,
500 ext: &mut dyn Externalities,
501 runtime_code: &RuntimeCode,
502 method: &str,
503 data: &[u8],
504 context: CallContext,
505 ) -> (Result<Vec<u8>>, bool) {
506 tracing::trace!(
507 target: "executor",
508 %method,
509 "Executing function",
510 );
511
512 let on_chain_heap_alloc_strategy = if self.ignore_onchain_heap_pages {
513 self.default_onchain_heap_alloc_strategy
514 } else {
515 runtime_code
516 .heap_pages
517 .map(|h| HeapAllocStrategy::Static { extra_pages: h as _ })
518 .unwrap_or_else(|| self.default_onchain_heap_alloc_strategy)
519 };
520
521 let heap_alloc_strategy = match context {
522 CallContext::Offchain => self.default_offchain_heap_alloc_strategy,
523 CallContext::Onchain { import: false } => on_chain_heap_alloc_strategy,
524 CallContext::Onchain { import: true } => on_chain_heap_alloc_strategy.double(),
525 };
526
527 let result = self.with_instance(
528 runtime_code,
529 ext,
530 heap_alloc_strategy,
531 |_, mut instance, _on_chain_version, mut ext| {
532 with_externalities_safe(&mut **ext, move || instance.call_export(method, data))
533 },
534 );
535
536 (result, false)
537 }
538}
539
540impl<H> RuntimeVersionOf for WasmExecutor<H>
541where
542 H: HostFunctions,
543{
544 fn runtime_version(
545 &self,
546 ext: &mut dyn Externalities,
547 runtime_code: &RuntimeCode,
548 ) -> Result<RuntimeVersion> {
549 let on_chain_heap_pages = if self.ignore_onchain_heap_pages {
550 self.default_onchain_heap_alloc_strategy
551 } else {
552 runtime_code
553 .heap_pages
554 .map(|h| HeapAllocStrategy::Static { extra_pages: h as _ })
555 .unwrap_or_else(|| self.default_onchain_heap_alloc_strategy)
556 };
557
558 self.with_instance(
559 runtime_code,
560 ext,
561 on_chain_heap_pages,
562 |_module, _instance, version, _ext| {
563 Ok(version.cloned().ok_or_else(|| Error::ApiError("Unknown version".into())))
564 },
565 )
566 }
567}
568
569#[deprecated(
572 note = "Native execution will be deprecated, please replace with `WasmExecutor`. Will be removed at end of 2024."
573)]
574pub struct NativeElseWasmExecutor<D: NativeExecutionDispatch> {
575 native_version: NativeVersion,
577 wasm:
579 WasmExecutor<ExtendedHostFunctions<sp_io::SubstrateHostFunctions, D::ExtendHostFunctions>>,
580
581 use_native: bool,
582}
583
584#[allow(deprecated)]
585impl<D: NativeExecutionDispatch> NativeElseWasmExecutor<D> {
586 #[deprecated(note = "use `Self::new_with_wasm_executor` method instead of it")]
601 pub fn new(
602 fallback_method: WasmExecutionMethod,
603 default_heap_pages: Option<u64>,
604 max_runtime_instances: usize,
605 runtime_cache_size: u8,
606 ) -> Self {
607 let heap_pages = default_heap_pages.map_or(DEFAULT_HEAP_ALLOC_STRATEGY, |h| {
608 HeapAllocStrategy::Static { extra_pages: h as _ }
609 });
610 let wasm = WasmExecutor::builder()
611 .with_execution_method(fallback_method)
612 .with_onchain_heap_alloc_strategy(heap_pages)
613 .with_offchain_heap_alloc_strategy(heap_pages)
614 .with_max_runtime_instances(max_runtime_instances)
615 .with_runtime_cache_size(runtime_cache_size)
616 .build();
617
618 NativeElseWasmExecutor { native_version: D::native_version(), wasm, use_native: true }
619 }
620
621 pub fn new_with_wasm_executor(
623 executor: WasmExecutor<
624 ExtendedHostFunctions<sp_io::SubstrateHostFunctions, D::ExtendHostFunctions>,
625 >,
626 ) -> Self {
627 Self { native_version: D::native_version(), wasm: executor, use_native: true }
628 }
629
630 pub fn disable_use_native(&mut self) {
634 self.use_native = false;
635 }
636
637 #[deprecated(note = "use `Self::new_with_wasm_executor` method instead of it")]
639 pub fn allow_missing_host_functions(&mut self, allow_missing_host_functions: bool) {
640 self.wasm.allow_missing_host_functions = allow_missing_host_functions
641 }
642}
643
644#[allow(deprecated)]
645impl<D: NativeExecutionDispatch> RuntimeVersionOf for NativeElseWasmExecutor<D> {
646 fn runtime_version(
647 &self,
648 ext: &mut dyn Externalities,
649 runtime_code: &RuntimeCode,
650 ) -> Result<RuntimeVersion> {
651 self.wasm.runtime_version(ext, runtime_code)
652 }
653}
654
655#[allow(deprecated)]
656impl<D: NativeExecutionDispatch> GetNativeVersion for NativeElseWasmExecutor<D> {
657 fn native_version(&self) -> &NativeVersion {
658 &self.native_version
659 }
660}
661
662#[allow(deprecated)]
663impl<D: NativeExecutionDispatch + 'static> CodeExecutor for NativeElseWasmExecutor<D> {
664 type Error = Error;
665
666 fn call(
667 &self,
668 ext: &mut dyn Externalities,
669 runtime_code: &RuntimeCode,
670 method: &str,
671 data: &[u8],
672 context: CallContext,
673 ) -> (Result<Vec<u8>>, bool) {
674 let use_native = self.use_native;
675
676 tracing::trace!(
677 target: "executor",
678 function = %method,
679 "Executing function",
680 );
681
682 let on_chain_heap_alloc_strategy = if self.wasm.ignore_onchain_heap_pages {
683 self.wasm.default_onchain_heap_alloc_strategy
684 } else {
685 runtime_code
686 .heap_pages
687 .map(|h| HeapAllocStrategy::Static { extra_pages: h as _ })
688 .unwrap_or_else(|| self.wasm.default_onchain_heap_alloc_strategy)
689 };
690
691 let heap_alloc_strategy = match context {
692 CallContext::Offchain => self.wasm.default_offchain_heap_alloc_strategy,
693 CallContext::Onchain { import: false } => on_chain_heap_alloc_strategy,
694 CallContext::Onchain { import: true } => on_chain_heap_alloc_strategy.double(),
695 };
696
697 let mut used_native = false;
698 let result = self.wasm.with_instance(
699 runtime_code,
700 ext,
701 heap_alloc_strategy,
702 |_, mut instance, on_chain_version, mut ext| {
703 let on_chain_version =
704 on_chain_version.ok_or_else(|| Error::ApiError("Unknown version".into()))?;
705
706 let can_call_with =
707 on_chain_version.can_call_with(&self.native_version.runtime_version);
708
709 if use_native && can_call_with {
710 tracing::trace!(
711 target: "executor",
712 native = %self.native_version.runtime_version,
713 chain = %on_chain_version,
714 "Request for native execution succeeded",
715 );
716
717 used_native = true;
718 Ok(with_externalities_safe(&mut **ext, move || D::dispatch(method, data))?
719 .ok_or_else(|| Error::MethodNotFound(method.to_owned())))
720 } else {
721 if !can_call_with {
722 tracing::trace!(
723 target: "executor",
724 native = %self.native_version.runtime_version,
725 chain = %on_chain_version,
726 "Request for native execution failed",
727 );
728 }
729
730 with_externalities_safe(&mut **ext, move || instance.call_export(method, data))
731 }
732 },
733 );
734 (result, used_native)
735 }
736}
737
738#[allow(deprecated)]
739impl<D: NativeExecutionDispatch> Clone for NativeElseWasmExecutor<D> {
740 fn clone(&self) -> Self {
741 NativeElseWasmExecutor {
742 native_version: D::native_version(),
743 wasm: self.wasm.clone(),
744 use_native: self.use_native,
745 }
746 }
747}
748
749#[allow(deprecated)]
750impl<D: NativeExecutionDispatch> sp_core::traits::ReadRuntimeVersion for NativeElseWasmExecutor<D> {
751 fn read_runtime_version(
752 &self,
753 wasm_code: &[u8],
754 ext: &mut dyn Externalities,
755 ) -> std::result::Result<Vec<u8>, String> {
756 self.wasm.read_runtime_version(wasm_code, ext)
757 }
758}
759
760#[cfg(test)]
761mod tests {
762 use super::*;
763 use sp_runtime_interface::{pass_by::PassFatPointerAndRead, runtime_interface};
764
765 #[runtime_interface]
766 trait MyInterface {
767 fn say_hello_world(data: PassFatPointerAndRead<&str>) {
768 println!("Hello world from: {}", data);
769 }
770 }
771
772 pub struct MyExecutorDispatch;
773
774 impl NativeExecutionDispatch for MyExecutorDispatch {
775 type ExtendHostFunctions = (my_interface::HostFunctions, my_interface::HostFunctions);
776
777 fn dispatch(method: &str, data: &[u8]) -> Option<Vec<u8>> {
778 substrate_test_runtime::api::dispatch(method, data)
779 }
780
781 fn native_version() -> NativeVersion {
782 substrate_test_runtime::native_version()
783 }
784 }
785
786 #[test]
787 #[allow(deprecated)]
788 fn native_executor_registers_custom_interface() {
789 let executor = NativeElseWasmExecutor::<MyExecutorDispatch>::new_with_wasm_executor(
790 WasmExecutor::builder().build(),
791 );
792
793 fn extract_host_functions<H>(
794 _: &WasmExecutor<H>,
795 ) -> Vec<&'static dyn sp_wasm_interface::Function>
796 where
797 H: HostFunctions,
798 {
799 H::host_functions()
800 }
801
802 my_interface::HostFunctions::host_functions().iter().for_each(|function| {
803 assert_eq!(
804 extract_host_functions(&executor.wasm).iter().filter(|f| f == &function).count(),
805 2
806 );
807 });
808
809 my_interface::say_hello_world("hey");
810 }
811}