Skip to main content

gsc_executor_common/runtime_blob/
runtime_blob.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
3
4use crate::{error::WasmError, wasm_runtime::HeapAllocStrategy};
5use wasm_instrument::parity_wasm::elements::{
6    ExportEntry, External, Internal, MemorySection, MemoryType, Module, Section,
7    deserialize_buffer, serialize,
8};
9
10/// A program blob containing a Substrate runtime.
11#[derive(Clone)]
12pub struct RuntimeBlob(BlobKind);
13
14#[derive(Clone)]
15enum BlobKind {
16    WebAssembly(Module),
17    PolkaVM(polkavm::ProgramBlob<'static>),
18}
19
20impl RuntimeBlob {
21    /// Create `RuntimeBlob` from the given WASM or PolkaVM compressed program blob.
22    ///
23    /// See [`sp_maybe_compressed_blob`] for details about decompression.
24    pub fn uncompress_if_needed(wasm_code: &[u8]) -> Result<Self, WasmError> {
25        use sp_maybe_compressed_blob::CODE_BLOB_BOMB_LIMIT;
26        let wasm_code = sp_maybe_compressed_blob::decompress(wasm_code, CODE_BLOB_BOMB_LIMIT)
27            .map_err(|e| WasmError::Other(format!("Decompression error: {:?}", e)))?;
28        Self::new(&wasm_code)
29    }
30
31    /// Create `RuntimeBlob` from the given WASM or PolkaVM program blob.
32    ///
33    /// Returns `Err` if the blob cannot be deserialized.
34    ///
35    /// Will only accept a PolkaVM program if the `SUBSTRATE_ENABLE_POLKAVM` environment
36    /// variable is set to `1`.
37    pub fn new(raw_blob: &[u8]) -> Result<Self, WasmError> {
38        if raw_blob.starts_with(b"PVM\0") {
39            if crate::is_polkavm_enabled() {
40                return Ok(Self(BlobKind::PolkaVM(
41                    polkavm::ProgramBlob::parse(raw_blob)?.into_owned(),
42                )));
43            } else {
44                return Err(WasmError::Other("expected a WASM runtime blob, found a PolkaVM runtime blob; set the 'SUBSTRATE_ENABLE_POLKAVM' environment variable to enable the experimental PolkaVM-based executor".to_string()));
45            }
46        }
47
48        let raw_module: Module = deserialize_buffer(raw_blob)
49            .map_err(|e| WasmError::Other(format!("cannot deserialize module: {:?}", e)))?;
50        Ok(Self(BlobKind::WebAssembly(raw_module)))
51    }
52
53    /// Run a pass that instrument this module so as to introduce a deterministic stack height
54    /// limit.
55    ///
56    /// It will introduce a global mutable counter. The instrumentation will increase the counter
57    /// according to the "cost" of the callee. If the cost exceeds the `stack_depth_limit` constant,
58    /// the instrumentation will trap. The counter will be decreased as soon as the the callee
59    /// returns.
60    ///
61    /// The stack cost of a function is computed based on how much locals there are and the maximum
62    /// depth of the wasm operand stack.
63    ///
64    /// Only valid for WASM programs; will return an error if the blob is a PolkaVM program.
65    pub fn inject_stack_depth_metering(self, stack_depth_limit: u32) -> Result<Self, WasmError> {
66        let injected_module =
67            wasm_instrument::inject_stack_limiter(self.into_webassembly_blob()?, stack_depth_limit)
68                .map_err(|e| {
69                    WasmError::Other(format!("cannot inject the stack limiter: {:?}", e))
70                })?;
71
72        Ok(Self(BlobKind::WebAssembly(injected_module)))
73    }
74
75    /// Converts a WASM memory import into a memory section and exports it.
76    ///
77    /// Does nothing if there's no memory import.
78    ///
79    /// May return an error in case the WASM module is invalid.
80    ///
81    /// Only valid for WASM programs; will return an error if the blob is a PolkaVM program.
82    pub fn convert_memory_import_into_export(&mut self) -> Result<(), WasmError> {
83        let raw_module = self.as_webassembly_blob_mut()?;
84        let import_section = match raw_module.import_section_mut() {
85            Some(import_section) => import_section,
86            None => return Ok(()),
87        };
88
89        let import_entries = import_section.entries_mut();
90        for index in 0..import_entries.len() {
91            let entry = &import_entries[index];
92            let memory_ty = match entry.external() {
93                External::Memory(memory_ty) => *memory_ty,
94                _ => continue,
95            };
96
97            let memory_name = entry.field().to_owned();
98            import_entries.remove(index);
99
100            raw_module
101				.insert_section(Section::Memory(MemorySection::with_entries(vec![memory_ty])))
102				.map_err(|error| {
103					WasmError::Other(format!(
104					"can't convert a memory import into an export: failed to insert a new memory section: {}",
105					error
106				))
107				})?;
108
109            if raw_module.export_section_mut().is_none() {
110                // A module without an export section is somewhat unrealistic, but let's do this
111                // just in case to cover all of our bases.
112                raw_module
113                    .insert_section(Section::Export(Default::default()))
114                    .expect("an export section can be always inserted if it doesn't exist; qed");
115            }
116            raw_module
117				.export_section_mut()
118				.expect("export section already existed or we just added it above, so it always exists; qed")
119				.entries_mut()
120				.push(ExportEntry::new(memory_name, Internal::Memory(0)));
121
122            break;
123        }
124
125        Ok(())
126    }
127
128    /// Modifies the blob's memory section according to the given `heap_alloc_strategy`.
129    ///
130    /// Will return an error in case there is no memory section present,
131    /// or if the memory section is empty.
132    ///
133    /// Only valid for WASM programs; will return an error if the blob is a PolkaVM program.
134    pub fn setup_memory_according_to_heap_alloc_strategy(
135        &mut self,
136        heap_alloc_strategy: HeapAllocStrategy,
137    ) -> Result<(), WasmError> {
138        let raw_module = self.as_webassembly_blob_mut()?;
139        let memory_section = raw_module
140            .memory_section_mut()
141            .ok_or_else(|| WasmError::Other("no memory section found".into()))?;
142
143        if memory_section.entries().is_empty() {
144            return Err(WasmError::Other("memory section is empty".into()));
145        }
146        for memory_ty in memory_section.entries_mut() {
147            let initial = memory_ty.limits().initial();
148            let (min, max) = match heap_alloc_strategy {
149                HeapAllocStrategy::Dynamic { maximum_pages } => {
150                    // Ensure `initial <= maximum_pages`
151                    (
152                        maximum_pages.map(|m| m.min(initial)).unwrap_or(initial),
153                        maximum_pages,
154                    )
155                }
156                HeapAllocStrategy::Static { extra_pages } => {
157                    let pages = initial.saturating_add(extra_pages);
158                    (pages, Some(pages))
159                }
160            };
161            *memory_ty = MemoryType::new(min, max);
162        }
163        Ok(())
164    }
165
166    /// Scans the wasm blob for the first section with the name that matches the given. Returns the
167    /// contents of the custom section if found or `None` otherwise.
168    ///
169    /// Only valid for WASM programs; will return an error if the blob is a PolkaVM program.
170    pub fn custom_section_contents(&self, section_name: &str) -> Option<&[u8]> {
171        self.as_webassembly_blob()
172            .ok()?
173            .custom_sections()
174            .find(|cs| cs.name() == section_name)
175            .map(|cs| cs.payload())
176    }
177
178    /// Consumes this runtime blob and serializes it.
179    pub fn serialize(self) -> Vec<u8> {
180        match self.0 {
181            BlobKind::WebAssembly(raw_module) => {
182                serialize(raw_module).expect("serializing into a vec should succeed; qed")
183            }
184            BlobKind::PolkaVM(ref blob) => blob.as_bytes().to_vec(),
185        }
186    }
187
188    fn as_webassembly_blob(&self) -> Result<&Module, WasmError> {
189        match self.0 {
190            BlobKind::WebAssembly(ref raw_module) => Ok(raw_module),
191            BlobKind::PolkaVM(..) => Err(WasmError::Other(
192                "expected a WebAssembly program; found a PolkaVM program blob".into(),
193            )),
194        }
195    }
196
197    fn as_webassembly_blob_mut(&mut self) -> Result<&mut Module, WasmError> {
198        match self.0 {
199            BlobKind::WebAssembly(ref mut raw_module) => Ok(raw_module),
200            BlobKind::PolkaVM(..) => Err(WasmError::Other(
201                "expected a WebAssembly program; found a PolkaVM program blob".into(),
202            )),
203        }
204    }
205
206    fn into_webassembly_blob(self) -> Result<Module, WasmError> {
207        match self.0 {
208            BlobKind::WebAssembly(raw_module) => Ok(raw_module),
209            BlobKind::PolkaVM(..) => Err(WasmError::Other(
210                "expected a WebAssembly program; found a PolkaVM program blob".into(),
211            )),
212        }
213    }
214
215    /// Gets a reference to the inner PolkaVM program blob, if this is a PolkaVM program.
216    pub fn as_polkavm_blob(&self) -> Option<&polkavm::ProgramBlob<'_>> {
217        match self.0 {
218            BlobKind::WebAssembly(..) => None,
219            BlobKind::PolkaVM(ref blob) => Some(blob),
220        }
221    }
222}