1use crate::executor::{
8 error::{Error, Result},
9 wasm_runtime::{RuntimeCache, WasmExecutionMethod},
10 RuntimeVersionOf,
11};
12
13use std::{
14 marker::PhantomData,
15 panic::{AssertUnwindSafe, UnwindSafe},
16 path::PathBuf,
17 sync::Arc,
18};
19
20use crate::executor::common::{
21 runtime_blob::RuntimeBlob,
22 wasm_runtime::{
23 AllocationStats, HeapAllocStrategy, WasmInstance, WasmModule, DEFAULT_HEAP_ALLOC_STRATEGY,
24 },
25};
26use codec::Encode;
27use subsoil::core::traits::{CallContext, CodeExecutor, Externalities, RuntimeCode};
28use subsoil::version::{GetNativeVersion, NativeVersion, RuntimeVersion};
29use subsoil::wasm_interface::{ExtendedHostFunctions, HostFunctions};
30
31pub fn with_externalities_safe<F, U>(ext: &mut dyn Externalities, f: F) -> Result<U>
35where
36 F: UnwindSafe + FnOnce() -> U,
37{
38 subsoil::externalities::set_and_run_with_externalities(ext, move || {
39 let _guard = subsoil::panic_handler::AbortGuard::force_unwind();
42 std::panic::catch_unwind(f).map_err(|e| {
43 if let Some(err) = e.downcast_ref::<String>() {
44 Error::RuntimePanicked(err.clone())
45 } else if let Some(err) = e.downcast_ref::<&'static str>() {
46 Error::RuntimePanicked(err.to_string())
47 } else {
48 Error::RuntimePanicked("Unknown panic".into())
49 }
50 })
51 })
52}
53
54pub trait NativeExecutionDispatch: Send + Sync {
58 type ExtendHostFunctions: HostFunctions;
61
62 fn dispatch(method: &str, data: &[u8]) -> Option<Vec<u8>>;
64
65 fn native_version() -> NativeVersion;
67}
68
69fn unwrap_heap_pages(pages: Option<HeapAllocStrategy>) -> HeapAllocStrategy {
70 pages.unwrap_or_else(|| DEFAULT_HEAP_ALLOC_STRATEGY)
71}
72
73pub struct WasmExecutorBuilder<H = subsoil::io::SubstrateHostFunctions> {
75 _phantom: PhantomData<H>,
76 method: WasmExecutionMethod,
77 onchain_heap_alloc_strategy: Option<HeapAllocStrategy>,
78 offchain_heap_alloc_strategy: Option<HeapAllocStrategy>,
79 ignore_onchain_heap_pages: bool,
80 max_runtime_instances: usize,
81 cache_path: Option<PathBuf>,
82 allow_missing_host_functions: bool,
83 runtime_cache_size: u8,
84}
85
86impl<H> WasmExecutorBuilder<H> {
87 pub fn new() -> Self {
91 Self {
92 _phantom: PhantomData,
93 method: WasmExecutionMethod::default(),
94 onchain_heap_alloc_strategy: None,
95 offchain_heap_alloc_strategy: None,
96 ignore_onchain_heap_pages: false,
97 max_runtime_instances: 2,
98 runtime_cache_size: 4,
99 allow_missing_host_functions: false,
100 cache_path: None,
101 }
102 }
103
104 pub fn with_execution_method(mut self, method: WasmExecutionMethod) -> Self {
106 self.method = method;
107 self
108 }
109
110 pub fn with_onchain_heap_alloc_strategy(
113 mut self,
114 heap_alloc_strategy: HeapAllocStrategy,
115 ) -> Self {
116 self.onchain_heap_alloc_strategy = Some(heap_alloc_strategy);
117 self
118 }
119
120 pub fn with_offchain_heap_alloc_strategy(
123 mut self,
124 heap_alloc_strategy: HeapAllocStrategy,
125 ) -> Self {
126 self.offchain_heap_alloc_strategy = Some(heap_alloc_strategy);
127 self
128 }
129
130 pub fn with_ignore_onchain_heap_pages(mut self, ignore_onchain_heap_pages: bool) -> Self {
134 self.ignore_onchain_heap_pages = ignore_onchain_heap_pages;
135 self
136 }
137
138 pub fn with_max_runtime_instances(mut self, instances: usize) -> Self {
145 self.max_runtime_instances = instances;
146 self
147 }
148
149 pub fn with_cache_path(mut self, cache_path: impl Into<PathBuf>) -> Self {
157 self.cache_path = Some(cache_path.into());
158 self
159 }
160
161 pub fn with_allow_missing_host_functions(mut self, allow: bool) -> Self {
169 self.allow_missing_host_functions = allow;
170 self
171 }
172
173 pub fn with_runtime_cache_size(mut self, runtime_cache_size: u8) -> Self {
180 self.runtime_cache_size = runtime_cache_size;
181 self
182 }
183
184 pub fn build(self) -> WasmExecutor<H> {
186 WasmExecutor {
187 method: self.method,
188 default_offchain_heap_alloc_strategy: unwrap_heap_pages(
189 self.offchain_heap_alloc_strategy,
190 ),
191 default_onchain_heap_alloc_strategy: unwrap_heap_pages(
192 self.onchain_heap_alloc_strategy,
193 ),
194 ignore_onchain_heap_pages: self.ignore_onchain_heap_pages,
195 cache: Arc::new(RuntimeCache::new(
196 self.max_runtime_instances,
197 self.cache_path.clone(),
198 self.runtime_cache_size,
199 )),
200 cache_path: self.cache_path,
201 allow_missing_host_functions: self.allow_missing_host_functions,
202 phantom: PhantomData,
203 }
204 }
205}
206
207pub struct WasmExecutor<H = subsoil::io::SubstrateHostFunctions> {
210 method: WasmExecutionMethod,
212 default_onchain_heap_alloc_strategy: HeapAllocStrategy,
214 default_offchain_heap_alloc_strategy: HeapAllocStrategy,
216 ignore_onchain_heap_pages: bool,
218 cache: Arc<RuntimeCache>,
220 cache_path: Option<PathBuf>,
223 allow_missing_host_functions: bool,
225 phantom: PhantomData<H>,
226}
227
228impl<H> Clone for WasmExecutor<H> {
229 fn clone(&self) -> Self {
230 Self {
231 method: self.method,
232 default_onchain_heap_alloc_strategy: self.default_onchain_heap_alloc_strategy,
233 default_offchain_heap_alloc_strategy: self.default_offchain_heap_alloc_strategy,
234 ignore_onchain_heap_pages: self.ignore_onchain_heap_pages,
235 cache: self.cache.clone(),
236 cache_path: self.cache_path.clone(),
237 allow_missing_host_functions: self.allow_missing_host_functions,
238 phantom: self.phantom,
239 }
240 }
241}
242
243impl Default for WasmExecutor<subsoil::io::SubstrateHostFunctions> {
244 fn default() -> Self {
245 WasmExecutorBuilder::new().build()
246 }
247}
248
249impl<H> WasmExecutor<H> {
250 #[deprecated(note = "use `Self::builder` method instead of it")]
269 pub fn new(
270 method: WasmExecutionMethod,
271 default_heap_pages: Option<u64>,
272 max_runtime_instances: usize,
273 cache_path: Option<PathBuf>,
274 runtime_cache_size: u8,
275 ) -> Self {
276 WasmExecutor {
277 method,
278 default_onchain_heap_alloc_strategy: unwrap_heap_pages(
279 default_heap_pages.map(|h| HeapAllocStrategy::Static { extra_pages: h as _ }),
280 ),
281 default_offchain_heap_alloc_strategy: unwrap_heap_pages(
282 default_heap_pages.map(|h| HeapAllocStrategy::Static { extra_pages: h as _ }),
283 ),
284 ignore_onchain_heap_pages: false,
285 cache: Arc::new(RuntimeCache::new(
286 max_runtime_instances,
287 cache_path.clone(),
288 runtime_cache_size,
289 )),
290 cache_path,
291 allow_missing_host_functions: false,
292 phantom: PhantomData,
293 }
294 }
295
296 pub fn builder() -> WasmExecutorBuilder<H> {
298 WasmExecutorBuilder::new()
299 }
300
301 #[deprecated(note = "use `Self::builder` method instead of it")]
303 pub fn allow_missing_host_functions(&mut self, allow_missing_host_functions: bool) {
304 self.allow_missing_host_functions = allow_missing_host_functions
305 }
306}
307
308impl<H> WasmExecutor<H>
309where
310 H: HostFunctions,
311{
312 pub fn with_instance<R, F>(
326 &self,
327 runtime_code: &RuntimeCode,
328 ext: &mut dyn Externalities,
329 heap_alloc_strategy: HeapAllocStrategy,
330 f: F,
331 ) -> Result<R>
332 where
333 F: FnOnce(
334 AssertUnwindSafe<&dyn WasmModule>,
335 AssertUnwindSafe<&mut dyn WasmInstance>,
336 Option<&RuntimeVersion>,
337 AssertUnwindSafe<&mut dyn Externalities>,
338 ) -> Result<Result<R>>,
339 {
340 match self.cache.with_instance::<H, _, _>(
341 runtime_code,
342 ext,
343 self.method,
344 heap_alloc_strategy,
345 self.allow_missing_host_functions,
346 |module, instance, version, ext| {
347 let module = AssertUnwindSafe(module);
348 let instance = AssertUnwindSafe(instance);
349 let ext = AssertUnwindSafe(ext);
350 f(module, instance, version, ext)
351 },
352 )? {
353 Ok(r) => r,
354 Err(e) => Err(e),
355 }
356 }
357
358 #[doc(hidden)] pub fn uncached_call(
367 &self,
368 runtime_blob: RuntimeBlob,
369 ext: &mut dyn Externalities,
370 allow_missing_host_functions: bool,
371 export_name: &str,
372 call_data: &[u8],
373 ) -> std::result::Result<Vec<u8>, Error> {
374 self.uncached_call_impl(
375 runtime_blob,
376 ext,
377 allow_missing_host_functions,
378 export_name,
379 call_data,
380 &mut None,
381 )
382 }
383
384 #[doc(hidden)] pub fn uncached_call_with_allocation_stats(
387 &self,
388 runtime_blob: RuntimeBlob,
389 ext: &mut dyn Externalities,
390 allow_missing_host_functions: bool,
391 export_name: &str,
392 call_data: &[u8],
393 ) -> (std::result::Result<Vec<u8>, Error>, Option<AllocationStats>) {
394 let mut allocation_stats = None;
395 let result = self.uncached_call_impl(
396 runtime_blob,
397 ext,
398 allow_missing_host_functions,
399 export_name,
400 call_data,
401 &mut allocation_stats,
402 );
403 (result, allocation_stats)
404 }
405
406 fn uncached_call_impl(
407 &self,
408 runtime_blob: RuntimeBlob,
409 ext: &mut dyn Externalities,
410 allow_missing_host_functions: bool,
411 export_name: &str,
412 call_data: &[u8],
413 allocation_stats_out: &mut Option<AllocationStats>,
414 ) -> std::result::Result<Vec<u8>, Error> {
415 let module = crate::executor::wasm_runtime::create_wasm_runtime_with_code::<H>(
416 self.method,
417 self.default_onchain_heap_alloc_strategy,
418 runtime_blob,
419 allow_missing_host_functions,
420 self.cache_path.as_deref(),
421 )
422 .map_err(|e| format!("Failed to create module: {}", e))?;
423
424 let instance =
425 module.new_instance().map_err(|e| format!("Failed to create instance: {}", e))?;
426
427 let mut instance = AssertUnwindSafe(instance);
428 let mut ext = AssertUnwindSafe(ext);
429 let mut allocation_stats_out = AssertUnwindSafe(allocation_stats_out);
430
431 with_externalities_safe(&mut **ext, move || {
432 let (result, allocation_stats) =
433 instance.call_with_allocation_stats(export_name.into(), call_data);
434 **allocation_stats_out = allocation_stats;
435 result
436 })
437 .and_then(|r| r)
438 }
439}
440
441impl<H> subsoil::core::traits::ReadRuntimeVersion for WasmExecutor<H>
442where
443 H: HostFunctions,
444{
445 fn read_runtime_version(
446 &self,
447 wasm_code: &[u8],
448 ext: &mut dyn Externalities,
449 ) -> std::result::Result<Vec<u8>, String> {
450 let runtime_blob = RuntimeBlob::uncompress_if_needed(wasm_code)
451 .map_err(|e| format!("Failed to create runtime blob: {:?}", e))?;
452
453 if let Some(version) = crate::executor::wasm_runtime::read_embedded_version(&runtime_blob)
454 .map_err(|e| format!("Failed to read the static section: {:?}", e))
455 .map(|v| v.map(|v| v.encode()))?
456 {
457 return Ok(version);
458 }
459
460 self.uncached_call(
465 runtime_blob,
466 ext,
467 true,
472 "Core_version",
473 &[],
474 )
475 .map_err(|e| e.to_string())
476 }
477}
478
479impl<H> CodeExecutor for WasmExecutor<H>
480where
481 H: HostFunctions,
482{
483 type Error = Error;
484
485 fn call(
486 &self,
487 ext: &mut dyn Externalities,
488 runtime_code: &RuntimeCode,
489 method: &str,
490 data: &[u8],
491 context: CallContext,
492 ) -> (Result<Vec<u8>>, bool) {
493 tracing::trace!(
494 target: "executor",
495 %method,
496 "Executing function",
497 );
498
499 let on_chain_heap_alloc_strategy = if self.ignore_onchain_heap_pages {
500 self.default_onchain_heap_alloc_strategy
501 } else {
502 runtime_code
503 .heap_pages
504 .map(|h| HeapAllocStrategy::Static { extra_pages: h as _ })
505 .unwrap_or_else(|| self.default_onchain_heap_alloc_strategy)
506 };
507
508 let heap_alloc_strategy = match context {
509 CallContext::Offchain => self.default_offchain_heap_alloc_strategy,
510 CallContext::Onchain => on_chain_heap_alloc_strategy,
511 };
512
513 let result = self.with_instance(
514 runtime_code,
515 ext,
516 heap_alloc_strategy,
517 |_, mut instance, _on_chain_version, mut ext| {
518 with_externalities_safe(&mut **ext, move || instance.call_export(method, data))
519 },
520 );
521
522 (result, false)
523 }
524}
525
526impl<H> RuntimeVersionOf for WasmExecutor<H>
527where
528 H: HostFunctions,
529{
530 fn runtime_version(
531 &self,
532 ext: &mut dyn Externalities,
533 runtime_code: &RuntimeCode,
534 ) -> Result<RuntimeVersion> {
535 let on_chain_heap_pages = if self.ignore_onchain_heap_pages {
536 self.default_onchain_heap_alloc_strategy
537 } else {
538 runtime_code
539 .heap_pages
540 .map(|h| HeapAllocStrategy::Static { extra_pages: h as _ })
541 .unwrap_or_else(|| self.default_onchain_heap_alloc_strategy)
542 };
543
544 self.with_instance(
545 runtime_code,
546 ext,
547 on_chain_heap_pages,
548 |_module, _instance, version, _ext| {
549 Ok(version.cloned().ok_or_else(|| Error::ApiError("Unknown version".into())))
550 },
551 )
552 }
553}
554
555#[deprecated(
558 note = "Native execution will be deprecated, please replace with `WasmExecutor`. Will be removed at end of 2024."
559)]
560pub struct NativeElseWasmExecutor<D: NativeExecutionDispatch> {
561 native_version: NativeVersion,
563 wasm: WasmExecutor<
565 ExtendedHostFunctions<subsoil::io::SubstrateHostFunctions, D::ExtendHostFunctions>,
566 >,
567
568 use_native: bool,
569}
570
571#[allow(deprecated)]
572impl<D: NativeExecutionDispatch> NativeElseWasmExecutor<D> {
573 #[deprecated(note = "use `Self::new_with_wasm_executor` method instead of it")]
588 pub fn new(
589 fallback_method: WasmExecutionMethod,
590 default_heap_pages: Option<u64>,
591 max_runtime_instances: usize,
592 runtime_cache_size: u8,
593 ) -> Self {
594 let heap_pages = default_heap_pages.map_or(DEFAULT_HEAP_ALLOC_STRATEGY, |h| {
595 HeapAllocStrategy::Static { extra_pages: h as _ }
596 });
597 let wasm = WasmExecutor::builder()
598 .with_execution_method(fallback_method)
599 .with_onchain_heap_alloc_strategy(heap_pages)
600 .with_offchain_heap_alloc_strategy(heap_pages)
601 .with_max_runtime_instances(max_runtime_instances)
602 .with_runtime_cache_size(runtime_cache_size)
603 .build();
604
605 NativeElseWasmExecutor { native_version: D::native_version(), wasm, use_native: true }
606 }
607
608 pub fn new_with_wasm_executor(
610 executor: WasmExecutor<
611 ExtendedHostFunctions<subsoil::io::SubstrateHostFunctions, D::ExtendHostFunctions>,
612 >,
613 ) -> Self {
614 Self { native_version: D::native_version(), wasm: executor, use_native: true }
615 }
616
617 pub fn disable_use_native(&mut self) {
621 self.use_native = false;
622 }
623
624 #[deprecated(note = "use `Self::new_with_wasm_executor` method instead of it")]
626 pub fn allow_missing_host_functions(&mut self, allow_missing_host_functions: bool) {
627 self.wasm.allow_missing_host_functions = allow_missing_host_functions
628 }
629}
630
631#[allow(deprecated)]
632impl<D: NativeExecutionDispatch> RuntimeVersionOf for NativeElseWasmExecutor<D> {
633 fn runtime_version(
634 &self,
635 ext: &mut dyn Externalities,
636 runtime_code: &RuntimeCode,
637 ) -> Result<RuntimeVersion> {
638 self.wasm.runtime_version(ext, runtime_code)
639 }
640}
641
642#[allow(deprecated)]
643impl<D: NativeExecutionDispatch> GetNativeVersion for NativeElseWasmExecutor<D> {
644 fn native_version(&self) -> &NativeVersion {
645 &self.native_version
646 }
647}
648
649#[allow(deprecated)]
650impl<D: NativeExecutionDispatch + 'static> CodeExecutor for NativeElseWasmExecutor<D> {
651 type Error = Error;
652
653 fn call(
654 &self,
655 ext: &mut dyn Externalities,
656 runtime_code: &RuntimeCode,
657 method: &str,
658 data: &[u8],
659 context: CallContext,
660 ) -> (Result<Vec<u8>>, bool) {
661 let use_native = self.use_native;
662
663 tracing::trace!(
664 target: "executor",
665 function = %method,
666 "Executing function",
667 );
668
669 let on_chain_heap_alloc_strategy = if self.wasm.ignore_onchain_heap_pages {
670 self.wasm.default_onchain_heap_alloc_strategy
671 } else {
672 runtime_code
673 .heap_pages
674 .map(|h| HeapAllocStrategy::Static { extra_pages: h as _ })
675 .unwrap_or_else(|| self.wasm.default_onchain_heap_alloc_strategy)
676 };
677
678 let heap_alloc_strategy = match context {
679 CallContext::Offchain => self.wasm.default_offchain_heap_alloc_strategy,
680 CallContext::Onchain => on_chain_heap_alloc_strategy,
681 };
682
683 let mut used_native = false;
684 let result = self.wasm.with_instance(
685 runtime_code,
686 ext,
687 heap_alloc_strategy,
688 |_, mut instance, on_chain_version, mut ext| {
689 let on_chain_version =
690 on_chain_version.ok_or_else(|| Error::ApiError("Unknown version".into()))?;
691
692 let can_call_with =
693 on_chain_version.can_call_with(&self.native_version.runtime_version);
694
695 if use_native && can_call_with {
696 tracing::trace!(
697 target: "executor",
698 native = %self.native_version.runtime_version,
699 chain = %on_chain_version,
700 "Request for native execution succeeded",
701 );
702
703 used_native = true;
704 Ok(with_externalities_safe(&mut **ext, move || D::dispatch(method, data))?
705 .ok_or_else(|| Error::MethodNotFound(method.to_owned())))
706 } else {
707 if !can_call_with {
708 tracing::trace!(
709 target: "executor",
710 native = %self.native_version.runtime_version,
711 chain = %on_chain_version,
712 "Request for native execution failed",
713 );
714 }
715
716 with_externalities_safe(&mut **ext, move || instance.call_export(method, data))
717 }
718 },
719 );
720 (result, used_native)
721 }
722}
723
724#[allow(deprecated)]
725impl<D: NativeExecutionDispatch> Clone for NativeElseWasmExecutor<D> {
726 fn clone(&self) -> Self {
727 NativeElseWasmExecutor {
728 native_version: D::native_version(),
729 wasm: self.wasm.clone(),
730 use_native: self.use_native,
731 }
732 }
733}
734
735#[allow(deprecated)]
736impl<D: NativeExecutionDispatch> subsoil::core::traits::ReadRuntimeVersion
737 for NativeElseWasmExecutor<D>
738{
739 fn read_runtime_version(
740 &self,
741 wasm_code: &[u8],
742 ext: &mut dyn Externalities,
743 ) -> std::result::Result<Vec<u8>, String> {
744 self.wasm.read_runtime_version(wasm_code, ext)
745 }
746}
747
748#[cfg(test)]
749mod tests {
750 use super::*;
751 use std::borrow::Cow;
752 use subsoil::runtime_interface::{pass_by::PassFatPointerAndRead, runtime_interface};
753 use subsoil::version::RuntimeVersion;
754
755 #[runtime_interface]
756 trait MyInterface {
757 fn say_hello_world(data: PassFatPointerAndRead<&str>) {
758 println!("Hello world from: {}", data);
759 }
760 }
761
762 pub struct MyExecutorDispatch;
763
764 impl NativeExecutionDispatch for MyExecutorDispatch {
765 type ExtendHostFunctions = (my_interface::HostFunctions, my_interface::HostFunctions);
766
767 fn dispatch(method: &str, data: &[u8]) -> Option<Vec<u8>> {
768 let _ = (method, data);
769 None
770 }
771
772 fn native_version() -> NativeVersion {
773 NativeVersion {
774 runtime_version: RuntimeVersion {
775 spec_name: Cow::Borrowed("executor-test"),
776 impl_name: Cow::Borrowed("executor-test"),
777 authoring_version: 1,
778 spec_version: 1,
779 impl_version: 1,
780 apis: Default::default(),
781 transaction_version: 1,
782 system_version: 1,
783 },
784 can_author_with: Default::default(),
785 }
786 }
787 }
788
789 #[test]
790 #[allow(deprecated)]
791 fn native_executor_registers_custom_interface() {
792 let executor = NativeElseWasmExecutor::<MyExecutorDispatch>::new_with_wasm_executor(
793 WasmExecutor::builder().build(),
794 );
795
796 fn extract_host_functions<H>(
797 _: &WasmExecutor<H>,
798 ) -> Vec<&'static dyn subsoil::wasm_interface::Function>
799 where
800 H: HostFunctions,
801 {
802 H::host_functions()
803 }
804
805 my_interface::HostFunctions::host_functions().iter().for_each(|function| {
806 assert_eq!(
807 extract_host_functions(&executor.wasm).iter().filter(|f| f == &function).count(),
808 2
809 );
810 });
811
812 my_interface::say_hello_world("hey");
813 }
814}