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 =
437 module.new_instance().map_err(|e| format!("Failed to create instance: {}", e))?;
438
439 let mut instance = AssertUnwindSafe(instance);
440 let mut ext = AssertUnwindSafe(ext);
441 let mut allocation_stats_out = AssertUnwindSafe(allocation_stats_out);
442
443 with_externalities_safe(&mut **ext, move || {
444 let (result, allocation_stats) =
445 instance.call_with_allocation_stats(export_name.into(), call_data);
446 **allocation_stats_out = allocation_stats;
447 result
448 })
449 .and_then(|r| r)
450 }
451}
452
453impl<H> sp_core::traits::ReadRuntimeVersion for WasmExecutor<H>
454where
455 H: HostFunctions,
456{
457 fn read_runtime_version(
458 &self,
459 wasm_code: &[u8],
460 ext: &mut dyn Externalities,
461 ) -> std::result::Result<Vec<u8>, String> {
462 let runtime_blob = RuntimeBlob::uncompress_if_needed(wasm_code)
463 .map_err(|e| format!("Failed to create runtime blob: {:?}", e))?;
464
465 if let Some(version) = crate::wasm_runtime::read_embedded_version(&runtime_blob)
466 .map_err(|e| format!("Failed to read the static section: {:?}", e))
467 .map(|v| v.map(|v| v.encode()))?
468 {
469 return Ok(version);
470 }
471
472 self.uncached_call(
477 runtime_blob,
478 ext,
479 true,
484 "Core_version",
485 &[],
486 )
487 .map_err(|e| e.to_string())
488 }
489}
490
491impl<H> CodeExecutor for WasmExecutor<H>
492where
493 H: HostFunctions,
494{
495 type Error = Error;
496
497 fn call(
498 &self,
499 ext: &mut dyn Externalities,
500 runtime_code: &RuntimeCode,
501 method: &str,
502 data: &[u8],
503 context: CallContext,
504 ) -> (Result<Vec<u8>>, bool) {
505 tracing::trace!(
506 target: "executor",
507 %method,
508 "Executing function",
509 );
510
511 let on_chain_heap_alloc_strategy = if self.ignore_onchain_heap_pages {
512 self.default_onchain_heap_alloc_strategy
513 } else {
514 runtime_code
515 .heap_pages
516 .map(|h| HeapAllocStrategy::Static { extra_pages: h as _ })
517 .unwrap_or_else(|| self.default_onchain_heap_alloc_strategy)
518 };
519
520 let heap_alloc_strategy = match context {
521 CallContext::Offchain => self.default_offchain_heap_alloc_strategy,
522 CallContext::Onchain => on_chain_heap_alloc_strategy,
523 };
524
525 let result = self.with_instance(
526 runtime_code,
527 ext,
528 heap_alloc_strategy,
529 |_, mut instance, _on_chain_version, mut ext| {
530 with_externalities_safe(&mut **ext, move || instance.call_export(method, data))
531 },
532 );
533
534 (result, false)
535 }
536}
537
538impl<H> RuntimeVersionOf for WasmExecutor<H>
539where
540 H: HostFunctions,
541{
542 fn runtime_version(
543 &self,
544 ext: &mut dyn Externalities,
545 runtime_code: &RuntimeCode,
546 ) -> Result<RuntimeVersion> {
547 let on_chain_heap_pages = if self.ignore_onchain_heap_pages {
548 self.default_onchain_heap_alloc_strategy
549 } else {
550 runtime_code
551 .heap_pages
552 .map(|h| HeapAllocStrategy::Static { extra_pages: h as _ })
553 .unwrap_or_else(|| self.default_onchain_heap_alloc_strategy)
554 };
555
556 self.with_instance(
557 runtime_code,
558 ext,
559 on_chain_heap_pages,
560 |_module, _instance, version, _ext| {
561 Ok(version.cloned().ok_or_else(|| Error::ApiError("Unknown version".into())))
562 },
563 )
564 }
565}
566
567#[deprecated(
570 note = "Native execution will be deprecated, please replace with `WasmExecutor`. Will be removed at end of 2024."
571)]
572pub struct NativeElseWasmExecutor<D: NativeExecutionDispatch> {
573 native_version: NativeVersion,
575 wasm:
577 WasmExecutor<ExtendedHostFunctions<sp_io::SubstrateHostFunctions, D::ExtendHostFunctions>>,
578
579 use_native: bool,
580}
581
582#[allow(deprecated)]
583impl<D: NativeExecutionDispatch> NativeElseWasmExecutor<D> {
584 #[deprecated(note = "use `Self::new_with_wasm_executor` method instead of it")]
599 pub fn new(
600 fallback_method: WasmExecutionMethod,
601 default_heap_pages: Option<u64>,
602 max_runtime_instances: usize,
603 runtime_cache_size: u8,
604 ) -> Self {
605 let heap_pages = default_heap_pages.map_or(DEFAULT_HEAP_ALLOC_STRATEGY, |h| {
606 HeapAllocStrategy::Static { extra_pages: h as _ }
607 });
608 let wasm = WasmExecutor::builder()
609 .with_execution_method(fallback_method)
610 .with_onchain_heap_alloc_strategy(heap_pages)
611 .with_offchain_heap_alloc_strategy(heap_pages)
612 .with_max_runtime_instances(max_runtime_instances)
613 .with_runtime_cache_size(runtime_cache_size)
614 .build();
615
616 NativeElseWasmExecutor { native_version: D::native_version(), wasm, use_native: true }
617 }
618
619 pub fn new_with_wasm_executor(
621 executor: WasmExecutor<
622 ExtendedHostFunctions<sp_io::SubstrateHostFunctions, D::ExtendHostFunctions>,
623 >,
624 ) -> Self {
625 Self { native_version: D::native_version(), wasm: executor, use_native: true }
626 }
627
628 pub fn disable_use_native(&mut self) {
632 self.use_native = false;
633 }
634
635 #[deprecated(note = "use `Self::new_with_wasm_executor` method instead of it")]
637 pub fn allow_missing_host_functions(&mut self, allow_missing_host_functions: bool) {
638 self.wasm.allow_missing_host_functions = allow_missing_host_functions
639 }
640}
641
642#[allow(deprecated)]
643impl<D: NativeExecutionDispatch> RuntimeVersionOf for NativeElseWasmExecutor<D> {
644 fn runtime_version(
645 &self,
646 ext: &mut dyn Externalities,
647 runtime_code: &RuntimeCode,
648 ) -> Result<RuntimeVersion> {
649 self.wasm.runtime_version(ext, runtime_code)
650 }
651}
652
653#[allow(deprecated)]
654impl<D: NativeExecutionDispatch> GetNativeVersion for NativeElseWasmExecutor<D> {
655 fn native_version(&self) -> &NativeVersion {
656 &self.native_version
657 }
658}
659
660#[allow(deprecated)]
661impl<D: NativeExecutionDispatch + 'static> CodeExecutor for NativeElseWasmExecutor<D> {
662 type Error = Error;
663
664 fn call(
665 &self,
666 ext: &mut dyn Externalities,
667 runtime_code: &RuntimeCode,
668 method: &str,
669 data: &[u8],
670 context: CallContext,
671 ) -> (Result<Vec<u8>>, bool) {
672 let use_native = self.use_native;
673
674 tracing::trace!(
675 target: "executor",
676 function = %method,
677 "Executing function",
678 );
679
680 let on_chain_heap_alloc_strategy = if self.wasm.ignore_onchain_heap_pages {
681 self.wasm.default_onchain_heap_alloc_strategy
682 } else {
683 runtime_code
684 .heap_pages
685 .map(|h| HeapAllocStrategy::Static { extra_pages: h as _ })
686 .unwrap_or_else(|| self.wasm.default_onchain_heap_alloc_strategy)
687 };
688
689 let heap_alloc_strategy = match context {
690 CallContext::Offchain => self.wasm.default_offchain_heap_alloc_strategy,
691 CallContext::Onchain => on_chain_heap_alloc_strategy,
692 };
693
694 let mut used_native = false;
695 let result = self.wasm.with_instance(
696 runtime_code,
697 ext,
698 heap_alloc_strategy,
699 |_, mut instance, on_chain_version, mut ext| {
700 let on_chain_version =
701 on_chain_version.ok_or_else(|| Error::ApiError("Unknown version".into()))?;
702
703 let can_call_with =
704 on_chain_version.can_call_with(&self.native_version.runtime_version);
705
706 if use_native && can_call_with {
707 tracing::trace!(
708 target: "executor",
709 native = %self.native_version.runtime_version,
710 chain = %on_chain_version,
711 "Request for native execution succeeded",
712 );
713
714 used_native = true;
715 Ok(with_externalities_safe(&mut **ext, move || D::dispatch(method, data))?
716 .ok_or_else(|| Error::MethodNotFound(method.to_owned())))
717 } else {
718 if !can_call_with {
719 tracing::trace!(
720 target: "executor",
721 native = %self.native_version.runtime_version,
722 chain = %on_chain_version,
723 "Request for native execution failed",
724 );
725 }
726
727 with_externalities_safe(&mut **ext, move || instance.call_export(method, data))
728 }
729 },
730 );
731 (result, used_native)
732 }
733}
734
735#[allow(deprecated)]
736impl<D: NativeExecutionDispatch> Clone for NativeElseWasmExecutor<D> {
737 fn clone(&self) -> Self {
738 NativeElseWasmExecutor {
739 native_version: D::native_version(),
740 wasm: self.wasm.clone(),
741 use_native: self.use_native,
742 }
743 }
744}
745
746#[allow(deprecated)]
747impl<D: NativeExecutionDispatch> sp_core::traits::ReadRuntimeVersion for NativeElseWasmExecutor<D> {
748 fn read_runtime_version(
749 &self,
750 wasm_code: &[u8],
751 ext: &mut dyn Externalities,
752 ) -> std::result::Result<Vec<u8>, String> {
753 self.wasm.read_runtime_version(wasm_code, ext)
754 }
755}
756
757#[cfg(test)]
758mod tests {
759 use super::*;
760 use sp_runtime_interface::{pass_by::PassFatPointerAndRead, runtime_interface};
761
762 #[runtime_interface]
763 trait MyInterface {
764 fn say_hello_world(data: PassFatPointerAndRead<&str>) {
765 println!("Hello world from: {}", data);
766 }
767 }
768
769 pub struct MyExecutorDispatch;
770
771 impl NativeExecutionDispatch for MyExecutorDispatch {
772 type ExtendHostFunctions = (my_interface::HostFunctions, my_interface::HostFunctions);
773
774 fn dispatch(method: &str, data: &[u8]) -> Option<Vec<u8>> {
775 substrate_test_runtime::api::dispatch(method, data)
776 }
777
778 fn native_version() -> NativeVersion {
779 substrate_test_runtime::native_version()
780 }
781 }
782
783 #[test]
784 #[allow(deprecated)]
785 fn native_executor_registers_custom_interface() {
786 let executor = NativeElseWasmExecutor::<MyExecutorDispatch>::new_with_wasm_executor(
787 WasmExecutor::builder().build(),
788 );
789
790 fn extract_host_functions<H>(
791 _: &WasmExecutor<H>,
792 ) -> Vec<&'static dyn sp_wasm_interface::Function>
793 where
794 H: HostFunctions,
795 {
796 H::host_functions()
797 }
798
799 my_interface::HostFunctions::host_functions().iter().for_each(|function| {
800 assert_eq!(
801 extract_host_functions(&executor.wasm).iter().filter(|f| f == &function).count(),
802 2
803 );
804 });
805
806 my_interface::say_hello_world("hey");
807 }
808}