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")]
600 pub fn new(
601 fallback_method: WasmExecutionMethod,
602 default_heap_pages: Option<u64>,
603 max_runtime_instances: usize,
604 runtime_cache_size: u8,
605 ) -> Self {
606 let heap_pages = default_heap_pages.map_or(DEFAULT_HEAP_ALLOC_STRATEGY, |h| {
607 HeapAllocStrategy::Static { extra_pages: h as _ }
608 });
609 let wasm = WasmExecutor::builder()
610 .with_execution_method(fallback_method)
611 .with_onchain_heap_alloc_strategy(heap_pages)
612 .with_offchain_heap_alloc_strategy(heap_pages)
613 .with_max_runtime_instances(max_runtime_instances)
614 .with_runtime_cache_size(runtime_cache_size)
615 .build();
616
617 NativeElseWasmExecutor { native_version: D::native_version(), wasm, use_native: true }
618 }
619
620 pub fn new_with_wasm_executor(
622 executor: WasmExecutor<
623 ExtendedHostFunctions<sp_io::SubstrateHostFunctions, D::ExtendHostFunctions>,
624 >,
625 ) -> Self {
626 Self { native_version: D::native_version(), wasm: executor, use_native: true }
627 }
628
629 pub fn disable_use_native(&mut self) {
633 self.use_native = false;
634 }
635
636 #[deprecated(note = "use `Self::new_with_wasm_executor` method instead of it")]
638 pub fn allow_missing_host_functions(&mut self, allow_missing_host_functions: bool) {
639 self.wasm.allow_missing_host_functions = allow_missing_host_functions
640 }
641}
642
643#[allow(deprecated)]
644impl<D: NativeExecutionDispatch> RuntimeVersionOf for NativeElseWasmExecutor<D> {
645 fn runtime_version(
646 &self,
647 ext: &mut dyn Externalities,
648 runtime_code: &RuntimeCode,
649 ) -> Result<RuntimeVersion> {
650 self.wasm.runtime_version(ext, runtime_code)
651 }
652}
653
654#[allow(deprecated)]
655impl<D: NativeExecutionDispatch> GetNativeVersion for NativeElseWasmExecutor<D> {
656 fn native_version(&self) -> &NativeVersion {
657 &self.native_version
658 }
659}
660
661#[allow(deprecated)]
662impl<D: NativeExecutionDispatch + 'static> CodeExecutor for NativeElseWasmExecutor<D> {
663 type Error = Error;
664
665 fn call(
666 &self,
667 ext: &mut dyn Externalities,
668 runtime_code: &RuntimeCode,
669 method: &str,
670 data: &[u8],
671 context: CallContext,
672 ) -> (Result<Vec<u8>>, bool) {
673 let use_native = self.use_native;
674
675 tracing::trace!(
676 target: "executor",
677 function = %method,
678 "Executing function",
679 );
680
681 let on_chain_heap_alloc_strategy = if self.wasm.ignore_onchain_heap_pages {
682 self.wasm.default_onchain_heap_alloc_strategy
683 } else {
684 runtime_code
685 .heap_pages
686 .map(|h| HeapAllocStrategy::Static { extra_pages: h as _ })
687 .unwrap_or_else(|| self.wasm.default_onchain_heap_alloc_strategy)
688 };
689
690 let heap_alloc_strategy = match context {
691 CallContext::Offchain => self.wasm.default_offchain_heap_alloc_strategy,
692 CallContext::Onchain => on_chain_heap_alloc_strategy,
693 };
694
695 let mut used_native = false;
696 let result = self.wasm.with_instance(
697 runtime_code,
698 ext,
699 heap_alloc_strategy,
700 |_, mut instance, on_chain_version, mut ext| {
701 let on_chain_version =
702 on_chain_version.ok_or_else(|| Error::ApiError("Unknown version".into()))?;
703
704 let can_call_with =
705 on_chain_version.can_call_with(&self.native_version.runtime_version);
706
707 if use_native && 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 succeeded",
713 );
714
715 used_native = true;
716 Ok(with_externalities_safe(&mut **ext, move || D::dispatch(method, data))?
717 .ok_or_else(|| Error::MethodNotFound(method.to_owned())))
718 } else {
719 if !can_call_with {
720 tracing::trace!(
721 target: "executor",
722 native = %self.native_version.runtime_version,
723 chain = %on_chain_version,
724 "Request for native execution failed",
725 );
726 }
727
728 with_externalities_safe(&mut **ext, move || instance.call_export(method, data))
729 }
730 },
731 );
732 (result, used_native)
733 }
734}
735
736#[allow(deprecated)]
737impl<D: NativeExecutionDispatch> Clone for NativeElseWasmExecutor<D> {
738 fn clone(&self) -> Self {
739 NativeElseWasmExecutor {
740 native_version: D::native_version(),
741 wasm: self.wasm.clone(),
742 use_native: self.use_native,
743 }
744 }
745}
746
747#[allow(deprecated)]
748impl<D: NativeExecutionDispatch> sp_core::traits::ReadRuntimeVersion for NativeElseWasmExecutor<D> {
749 fn read_runtime_version(
750 &self,
751 wasm_code: &[u8],
752 ext: &mut dyn Externalities,
753 ) -> std::result::Result<Vec<u8>, String> {
754 self.wasm.read_runtime_version(wasm_code, ext)
755 }
756}
757
758#[cfg(test)]
759mod tests {
760 use super::*;
761 use sp_runtime_interface::{pass_by::PassFatPointerAndRead, runtime_interface};
762
763 #[runtime_interface]
764 trait MyInterface {
765 fn say_hello_world(data: PassFatPointerAndRead<&str>) {
766 println!("Hello world from: {}", data);
767 }
768 }
769
770 pub struct MyExecutorDispatch;
771
772 impl NativeExecutionDispatch for MyExecutorDispatch {
773 type ExtendHostFunctions = (my_interface::HostFunctions, my_interface::HostFunctions);
774
775 fn dispatch(method: &str, data: &[u8]) -> Option<Vec<u8>> {
776 substrate_test_runtime::api::dispatch(method, data)
777 }
778
779 fn native_version() -> NativeVersion {
780 substrate_test_runtime::native_version()
781 }
782 }
783
784 #[test]
785 #[allow(deprecated)]
786 fn native_executor_registers_custom_interface() {
787 let executor = NativeElseWasmExecutor::<MyExecutorDispatch>::new_with_wasm_executor(
788 WasmExecutor::builder().build(),
789 );
790
791 fn extract_host_functions<H>(
792 _: &WasmExecutor<H>,
793 ) -> Vec<&'static dyn sp_wasm_interface::Function>
794 where
795 H: HostFunctions,
796 {
797 H::host_functions()
798 }
799
800 my_interface::HostFunctions::host_functions().iter().for_each(|function| {
801 assert_eq!(
802 extract_host_functions(&executor.wasm).iter().filter(|f| f == &function).count(),
803 2
804 );
805 });
806
807 my_interface::say_hello_world("hey");
808 }
809}