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 time::Duration,
31};
32
33use codec::Encode;
34use sc_executor_common::{
35 runtime_blob::RuntimeBlob,
36 wasm_runtime::{
37 AllocationStats, HeapAllocStrategy, WasmInstance, WasmModule, DEFAULT_HEAP_ALLOC_STRATEGY,
38 },
39};
40use sp_core::traits::{CallContext, CodeExecutor, Externalities, RuntimeCode};
41use sp_version::RuntimeVersion;
42use sp_wasm_interface::HostFunctions;
43
44pub fn with_externalities_safe<F, U>(ext: &mut dyn Externalities, f: F) -> Result<U>
48where
49 F: UnwindSafe + FnOnce() -> U,
50{
51 sp_externalities::set_and_run_with_externalities(ext, move || {
52 let _guard = sp_panic_handler::AbortGuard::force_unwind();
55 std::panic::catch_unwind(f).map_err(|e| {
56 if let Some(err) = e.downcast_ref::<String>() {
57 Error::RuntimePanicked(err.clone())
58 } else if let Some(err) = e.downcast_ref::<&'static str>() {
59 Error::RuntimePanicked(err.to_string())
60 } else {
61 Error::RuntimePanicked("Unknown panic".into())
62 }
63 })
64 })
65}
66
67fn unwrap_heap_pages(pages: Option<HeapAllocStrategy>) -> HeapAllocStrategy {
68 pages.unwrap_or_else(|| DEFAULT_HEAP_ALLOC_STRATEGY)
69}
70
71pub struct WasmExecutorBuilder<H = sp_io::SubstrateHostFunctions> {
73 _phantom: PhantomData<H>,
74 method: WasmExecutionMethod,
75 onchain_heap_alloc_strategy: Option<HeapAllocStrategy>,
76 offchain_heap_alloc_strategy: Option<HeapAllocStrategy>,
77 ignore_onchain_heap_pages: bool,
78 max_runtime_instances: usize,
79 cache_path: Option<PathBuf>,
80 allow_missing_host_functions: bool,
81 runtime_cache_size: u8,
82}
83
84impl<H> WasmExecutorBuilder<H> {
85 pub fn new() -> Self {
89 Self {
90 _phantom: PhantomData,
91 method: WasmExecutionMethod::default(),
92 onchain_heap_alloc_strategy: None,
93 offchain_heap_alloc_strategy: None,
94 ignore_onchain_heap_pages: false,
95 max_runtime_instances: 2,
96 runtime_cache_size: 4,
97 allow_missing_host_functions: false,
98 cache_path: None,
99 }
100 }
101
102 pub fn with_execution_method(mut self, method: WasmExecutionMethod) -> Self {
104 self.method = method;
105 self
106 }
107
108 pub fn with_onchain_heap_alloc_strategy(
111 mut self,
112 heap_alloc_strategy: HeapAllocStrategy,
113 ) -> Self {
114 self.onchain_heap_alloc_strategy = Some(heap_alloc_strategy);
115 self
116 }
117
118 pub fn with_offchain_heap_alloc_strategy(
121 mut self,
122 heap_alloc_strategy: HeapAllocStrategy,
123 ) -> Self {
124 self.offchain_heap_alloc_strategy = Some(heap_alloc_strategy);
125 self
126 }
127
128 pub fn with_ignore_onchain_heap_pages(mut self, ignore_onchain_heap_pages: bool) -> Self {
132 self.ignore_onchain_heap_pages = ignore_onchain_heap_pages;
133 self
134 }
135
136 pub fn with_max_runtime_instances(mut self, instances: usize) -> Self {
143 self.max_runtime_instances = instances;
144 self
145 }
146
147 pub fn with_cache_path(mut self, cache_path: impl Into<PathBuf>) -> Self {
155 self.cache_path = Some(cache_path.into());
156 self
157 }
158
159 pub fn with_allow_missing_host_functions(mut self, allow: bool) -> Self {
167 self.allow_missing_host_functions = allow;
168 self
169 }
170
171 pub fn with_runtime_cache_size(mut self, runtime_cache_size: u8) -> Self {
178 self.runtime_cache_size = runtime_cache_size;
179 self
180 }
181
182 pub fn build(self) -> WasmExecutor<H> {
184 WasmExecutor {
185 method: self.method,
186 default_offchain_heap_alloc_strategy: unwrap_heap_pages(
187 self.offchain_heap_alloc_strategy,
188 ),
189 default_onchain_heap_alloc_strategy: unwrap_heap_pages(
190 self.onchain_heap_alloc_strategy,
191 ),
192 ignore_onchain_heap_pages: self.ignore_onchain_heap_pages,
193 cache: Arc::new(RuntimeCache::new(
194 self.max_runtime_instances,
195 self.cache_path.clone(),
196 self.runtime_cache_size,
197 )),
198 cache_path: self.cache_path,
199 allow_missing_host_functions: self.allow_missing_host_functions,
200 phantom: PhantomData,
201 }
202 }
203}
204
205pub struct WasmExecutor<H = sp_io::SubstrateHostFunctions> {
208 method: WasmExecutionMethod,
210 default_onchain_heap_alloc_strategy: HeapAllocStrategy,
212 default_offchain_heap_alloc_strategy: HeapAllocStrategy,
214 ignore_onchain_heap_pages: bool,
216 cache: Arc<RuntimeCache>,
218 cache_path: Option<PathBuf>,
221 allow_missing_host_functions: bool,
223 phantom: PhantomData<H>,
224}
225
226impl<H> Clone for WasmExecutor<H> {
227 fn clone(&self) -> Self {
228 Self {
229 method: self.method,
230 default_onchain_heap_alloc_strategy: self.default_onchain_heap_alloc_strategy,
231 default_offchain_heap_alloc_strategy: self.default_offchain_heap_alloc_strategy,
232 ignore_onchain_heap_pages: self.ignore_onchain_heap_pages,
233 cache: self.cache.clone(),
234 cache_path: self.cache_path.clone(),
235 allow_missing_host_functions: self.allow_missing_host_functions,
236 phantom: self.phantom,
237 }
238 }
239}
240
241impl Default for WasmExecutor<sp_io::SubstrateHostFunctions> {
242 fn default() -> Self {
243 WasmExecutorBuilder::new().build()
244 }
245}
246
247impl<H> WasmExecutor<H> {
248 #[deprecated(note = "use `Self::builder` method instead of it")]
267 pub fn new(
268 method: WasmExecutionMethod,
269 default_heap_pages: Option<u64>,
270 max_runtime_instances: usize,
271 cache_path: Option<PathBuf>,
272 runtime_cache_size: u8,
273 ) -> Self {
274 WasmExecutor {
275 method,
276 default_onchain_heap_alloc_strategy: unwrap_heap_pages(
277 default_heap_pages.map(|h| HeapAllocStrategy::Static { extra_pages: h as _ }),
278 ),
279 default_offchain_heap_alloc_strategy: unwrap_heap_pages(
280 default_heap_pages.map(|h| HeapAllocStrategy::Static { extra_pages: h as _ }),
281 ),
282 ignore_onchain_heap_pages: false,
283 cache: Arc::new(RuntimeCache::new(
284 max_runtime_instances,
285 cache_path.clone(),
286 runtime_cache_size,
287 )),
288 cache_path,
289 allow_missing_host_functions: false,
290 phantom: PhantomData,
291 }
292 }
293
294 pub fn builder() -> WasmExecutorBuilder<H> {
296 WasmExecutorBuilder::new()
297 }
298
299 #[deprecated(note = "use `Self::builder` method instead of it")]
301 pub fn allow_missing_host_functions(&mut self, allow_missing_host_functions: bool) {
302 self.allow_missing_host_functions = allow_missing_host_functions
303 }
304}
305
306impl<H> WasmExecutor<H>
307where
308 H: HostFunctions,
309{
310 fn on_chain_heap_alloc_strategy(&self, runtime_code: &RuntimeCode) -> HeapAllocStrategy {
312 if self.ignore_onchain_heap_pages {
313 return self.default_onchain_heap_alloc_strategy;
314 }
315
316 runtime_code
317 .heap_pages
318 .map(|h| HeapAllocStrategy::Static { extra_pages: h as _ })
319 .unwrap_or(self.default_onchain_heap_alloc_strategy)
320 }
321
322 fn heap_alloc_strategy(
324 &self,
325 runtime_code: &RuntimeCode,
326 context: CallContext,
327 ) -> HeapAllocStrategy {
328 match context {
329 CallContext::Offchain => self.default_offchain_heap_alloc_strategy,
330 CallContext::Onchain { import: false } => {
331 self.on_chain_heap_alloc_strategy(runtime_code)
332 },
333 CallContext::Onchain { import: true } => {
334 self.on_chain_heap_alloc_strategy(runtime_code).double()
335 },
336 }
337 }
338
339 pub fn with_instance<R, F>(
353 &self,
354 runtime_code: &RuntimeCode,
355 ext: &mut dyn Externalities,
356 heap_alloc_strategy: HeapAllocStrategy,
357 f: F,
358 ) -> Result<R>
359 where
360 F: FnOnce(
361 AssertUnwindSafe<&dyn WasmModule>,
362 AssertUnwindSafe<&mut dyn WasmInstance>,
363 Option<&RuntimeVersion>,
364 AssertUnwindSafe<&mut dyn Externalities>,
365 ) -> Result<Result<R>>,
366 {
367 match self.cache.with_instance::<H, _, _>(
368 runtime_code,
369 ext,
370 self.method,
371 heap_alloc_strategy,
372 self.allow_missing_host_functions,
373 |module, instance, version, ext| {
374 let module = AssertUnwindSafe(module);
375 let instance = AssertUnwindSafe(instance);
376 let ext = AssertUnwindSafe(ext);
377 f(module, instance, version, ext)
378 },
379 )? {
380 Ok(r) => r,
381 Err(e) => Err(e),
382 }
383 }
384
385 fn with_interruptible_instance<R, F>(
401 &self,
402 runtime_code: &RuntimeCode,
403 ext: &mut dyn Externalities,
404 heap_alloc_strategy: HeapAllocStrategy,
405 f: F,
406 ) -> Result<R>
407 where
408 F: FnOnce(
409 AssertUnwindSafe<&mut dyn WasmInstance>,
410 Option<&RuntimeVersion>,
411 AssertUnwindSafe<&mut dyn Externalities>,
412 ) -> Result<Result<R>>,
413 {
414 match self.cache.with_interruptible_instance::<H, _, _>(
415 runtime_code,
416 ext,
417 self.method,
418 heap_alloc_strategy,
419 self.allow_missing_host_functions,
420 |instance, version, ext| {
421 let instance = AssertUnwindSafe(instance);
422 let ext = AssertUnwindSafe(ext);
423 f(instance, version, ext)
424 },
425 )? {
426 Ok(r) => r,
427 Err(e) => Err(e),
428 }
429 }
430
431 #[doc(hidden)] pub fn uncached_call(
440 &self,
441 runtime_blob: RuntimeBlob,
442 ext: &mut dyn Externalities,
443 allow_missing_host_functions: bool,
444 export_name: &str,
445 call_data: &[u8],
446 ) -> std::result::Result<Vec<u8>, Error> {
447 self.uncached_call_impl(
448 runtime_blob,
449 ext,
450 allow_missing_host_functions,
451 export_name,
452 call_data,
453 &mut None,
454 )
455 }
456
457 #[doc(hidden)] pub fn uncached_call_with_allocation_stats(
460 &self,
461 runtime_blob: RuntimeBlob,
462 ext: &mut dyn Externalities,
463 allow_missing_host_functions: bool,
464 export_name: &str,
465 call_data: &[u8],
466 ) -> (std::result::Result<Vec<u8>, Error>, Option<AllocationStats>) {
467 let mut allocation_stats = None;
468 let result = self.uncached_call_impl(
469 runtime_blob,
470 ext,
471 allow_missing_host_functions,
472 export_name,
473 call_data,
474 &mut allocation_stats,
475 );
476 (result, allocation_stats)
477 }
478
479 fn uncached_call_impl(
480 &self,
481 runtime_blob: RuntimeBlob,
482 ext: &mut dyn Externalities,
483 allow_missing_host_functions: bool,
484 export_name: &str,
485 call_data: &[u8],
486 allocation_stats_out: &mut Option<AllocationStats>,
487 ) -> std::result::Result<Vec<u8>, Error> {
488 let module = crate::wasm_runtime::create_wasm_runtime_with_code::<H>(
489 self.method,
490 self.default_onchain_heap_alloc_strategy,
491 runtime_blob,
492 allow_missing_host_functions,
493 self.cache_path.as_deref(),
494 )
495 .map_err(|e| format!("Failed to create module: {}", e))?;
496
497 let instance = module
498 .new_instance(self.default_onchain_heap_alloc_strategy)
499 .map_err(|e| format!("Failed to create instance: {}", e))?;
500
501 let mut instance = AssertUnwindSafe(instance);
502 let mut ext = AssertUnwindSafe(ext);
503 let mut allocation_stats_out = AssertUnwindSafe(allocation_stats_out);
504
505 with_externalities_safe(&mut **ext, move || {
506 let (result, allocation_stats) =
507 instance.call_with_allocation_stats(export_name.into(), call_data);
508 **allocation_stats_out = allocation_stats;
509 result
510 })
511 .and_then(|r| r)
512 }
513}
514
515impl<H> sp_core::traits::ReadRuntimeVersion for WasmExecutor<H>
516where
517 H: HostFunctions,
518{
519 fn read_runtime_version(
520 &self,
521 wasm_code: &[u8],
522 ext: &mut dyn Externalities,
523 ) -> std::result::Result<Vec<u8>, String> {
524 let runtime_blob = RuntimeBlob::uncompress_if_needed(wasm_code)
525 .map_err(|e| format!("Failed to create runtime blob: {:?}", e))?;
526
527 if let Some(version) = crate::wasm_runtime::read_embedded_version(&runtime_blob)
528 .map_err(|e| format!("Failed to read the static section: {:?}", e))
529 .map(|v| v.map(|v| v.encode()))?
530 {
531 return Ok(version);
532 }
533
534 self.uncached_call(
539 runtime_blob,
540 ext,
541 true,
546 "Core_version",
547 &[],
548 )
549 .map_err(|e| e.to_string())
550 }
551}
552
553impl<H> CodeExecutor for WasmExecutor<H>
554where
555 H: HostFunctions,
556{
557 type Error = Error;
558
559 fn call(
560 &self,
561 ext: &mut dyn Externalities,
562 runtime_code: &RuntimeCode,
563 method: &str,
564 data: &[u8],
565 context: CallContext,
566 ) -> (Result<Vec<u8>>, bool) {
567 tracing::trace!(
568 target: "executor",
569 %method,
570 "Executing function",
571 );
572
573 let heap_alloc_strategy = self.heap_alloc_strategy(runtime_code, context);
574
575 let result = self.with_instance(
576 runtime_code,
577 ext,
578 heap_alloc_strategy,
579 |_, mut instance, _on_chain_version, mut ext| {
580 with_externalities_safe(&mut **ext, move || instance.call_export(method, data))
581 },
582 );
583
584 (result, false)
585 }
586
587 fn call_with_execution_timeout(
592 &self,
593 ext: &mut dyn Externalities,
594 runtime_code: &RuntimeCode,
595 method: &str,
596 data: &[u8],
597 context: CallContext,
598 timeout: Duration,
599 ) -> Result<Vec<u8>> {
600 tracing::trace!(
601 target: "executor",
602 %method,
603 "Executing function with timeout",
604 );
605
606 let heap_alloc_strategy = self.heap_alloc_strategy(runtime_code, context);
607
608 self.with_interruptible_instance(
609 runtime_code,
610 ext,
611 heap_alloc_strategy,
612 |mut instance, _on_chain_version, mut ext| {
613 with_externalities_safe(&mut **ext, move || {
614 instance.call_with_timeout(method, data, timeout)
615 })
616 },
617 )
618 }
619}
620
621impl<H> RuntimeVersionOf for WasmExecutor<H>
622where
623 H: HostFunctions,
624{
625 fn runtime_version(
626 &self,
627 ext: &mut dyn Externalities,
628 runtime_code: &RuntimeCode,
629 ) -> Result<RuntimeVersion> {
630 self.with_instance(
631 runtime_code,
632 ext,
633 self.on_chain_heap_alloc_strategy(runtime_code),
634 |_module, _instance, version, _ext| {
635 Ok(version.cloned().ok_or_else(|| Error::ApiError("Unknown version".into())))
636 },
637 )
638 }
639}
640
641#[cfg(test)]
642mod tests {
643 use super::*;
644 use sp_runtime_interface::{pass_by::PassFatPointerAndRead, runtime_interface};
645 use sp_wasm_interface::ExtendedHostFunctions;
646
647 #[runtime_interface]
648 trait MyInterface {
649 fn say_hello_world(data: PassFatPointerAndRead<&str>) {
650 println!("Hello world from: {}", data);
651 }
652 }
653
654 #[test]
655 fn wasm_executor_registers_custom_interface() {
656 type Hosts = ExtendedHostFunctions<
657 sp_io::SubstrateHostFunctions,
658 (my_interface::HostFunctions, my_interface::HostFunctions),
659 >;
660
661 let executor = WasmExecutor::<Hosts>::builder().build();
662
663 fn extract_host_functions<H>(
664 _: &WasmExecutor<H>,
665 ) -> Vec<&'static dyn sp_wasm_interface::Function>
666 where
667 H: HostFunctions,
668 {
669 H::host_functions()
670 }
671
672 my_interface::HostFunctions::host_functions().iter().for_each(|function| {
673 assert_eq!(
674 extract_host_functions(&executor).iter().filter(|f| f == &function).count(),
675 2
676 );
677 });
678
679 my_interface::say_hello_world("hey");
680 }
681}