1// This file is part of Substrate.
23// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
56// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
1011// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
1516// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
1819use crate::{error::WasmError, wasm_runtime::HeapAllocStrategy};
20use polkavm::ArcBytes;
21use wasm_instrument::parity_wasm::elements::{
22 deserialize_buffer, serialize, ExportEntry, External, Internal, MemorySection, MemoryType,
23 Module, Section,
24};
2526/// A program blob containing a Substrate runtime.
27#[derive(Clone)]
28pub struct RuntimeBlob(BlobKind);
2930#[derive(Clone)]
31enum BlobKind {
32 WebAssembly(Module),
33 PolkaVM((polkavm::ProgramBlob, ArcBytes)),
34}
3536impl RuntimeBlob {
37/// Create `RuntimeBlob` from the given WASM or PolkaVM compressed program blob.
38 ///
39 /// See [`sp_maybe_compressed_blob`] for details about decompression.
40pub fn uncompress_if_needed(wasm_code: &[u8]) -> Result<Self, WasmError> {
41use sp_maybe_compressed_blob::CODE_BLOB_BOMB_LIMIT;
42let wasm_code = sp_maybe_compressed_blob::decompress(wasm_code, CODE_BLOB_BOMB_LIMIT)
43 .map_err(|e| WasmError::Other(format!("Decompression error: {:?}", e)))?;
44Self::new(&wasm_code)
45 }
4647/// Create `RuntimeBlob` from the given WASM or PolkaVM program blob.
48 ///
49 /// Returns `Err` if the blob cannot be deserialized.
50 ///
51 /// Will only accept a PolkaVM program if the `SUBSTRATE_ENABLE_POLKAVM` environment
52 /// variable is set to `1`.
53pub fn new(raw_blob: &[u8]) -> Result<Self, WasmError> {
54if raw_blob.starts_with(b"PVM\0") {
55if crate::is_polkavm_enabled() {
56let raw = ArcBytes::from(raw_blob);
57let blob = polkavm::ProgramBlob::parse(raw.clone())?;
58return Ok(Self(BlobKind::PolkaVM((blob, raw))));
59 } else {
60return 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()));
61 }
62 }
6364let raw_module: Module = deserialize_buffer(raw_blob)
65 .map_err(|e| WasmError::Other(format!("cannot deserialize module: {:?}", e)))?;
66Ok(Self(BlobKind::WebAssembly(raw_module)))
67 }
6869/// Run a pass that instrument this module so as to introduce a deterministic stack height
70 /// limit.
71 ///
72 /// It will introduce a global mutable counter. The instrumentation will increase the counter
73 /// according to the "cost" of the callee. If the cost exceeds the `stack_depth_limit` constant,
74 /// the instrumentation will trap. The counter will be decreased as soon as the the callee
75 /// returns.
76 ///
77 /// The stack cost of a function is computed based on how much locals there are and the maximum
78 /// depth of the wasm operand stack.
79 ///
80 /// Only valid for WASM programs; will return an error if the blob is a PolkaVM program.
81pub fn inject_stack_depth_metering(self, stack_depth_limit: u32) -> Result<Self, WasmError> {
82let injected_module =
83 wasm_instrument::inject_stack_limiter(self.into_webassembly_blob()?, stack_depth_limit)
84 .map_err(|e| {
85 WasmError::Other(format!("cannot inject the stack limiter: {:?}", e))
86 })?;
8788Ok(Self(BlobKind::WebAssembly(injected_module)))
89 }
9091/// Converts a WASM memory import into a memory section and exports it.
92 ///
93 /// Does nothing if there's no memory import.
94 ///
95 /// May return an error in case the WASM module is invalid.
96 ///
97 /// Only valid for WASM programs; will return an error if the blob is a PolkaVM program.
98pub fn convert_memory_import_into_export(&mut self) -> Result<(), WasmError> {
99let raw_module = self.as_webassembly_blob_mut()?;
100let import_section = match raw_module.import_section_mut() {
101Some(import_section) => import_section,
102None => return Ok(()),
103 };
104105let import_entries = import_section.entries_mut();
106for index in 0..import_entries.len() {
107let entry = &import_entries[index];
108let memory_ty = match entry.external() {
109 External::Memory(memory_ty) => *memory_ty,
110_ => continue,
111 };
112113let memory_name = entry.field().to_owned();
114 import_entries.remove(index);
115116 raw_module
117 .insert_section(Section::Memory(MemorySection::with_entries(vec![memory_ty])))
118 .map_err(|error| {
119 WasmError::Other(format!(
120"can't convert a memory import into an export: failed to insert a new memory section: {}",
121 error
122 ))
123 })?;
124125if raw_module.export_section_mut().is_none() {
126// A module without an export section is somewhat unrealistic, but let's do this
127 // just in case to cover all of our bases.
128raw_module
129 .insert_section(Section::Export(Default::default()))
130 .expect("an export section can be always inserted if it doesn't exist; qed");
131 }
132 raw_module
133 .export_section_mut()
134 .expect("export section already existed or we just added it above, so it always exists; qed")
135 .entries_mut()
136 .push(ExportEntry::new(memory_name, Internal::Memory(0)));
137138break
139}
140141Ok(())
142 }
143144/// Modifies the blob's memory section according to the given `heap_alloc_strategy`.
145 ///
146 /// Will return an error in case there is no memory section present,
147 /// or if the memory section is empty.
148 ///
149 /// Only valid for WASM programs; will return an error if the blob is a PolkaVM program.
150pub fn setup_memory_according_to_heap_alloc_strategy(
151&mut self,
152 heap_alloc_strategy: HeapAllocStrategy,
153 ) -> Result<(), WasmError> {
154let raw_module = self.as_webassembly_blob_mut()?;
155let memory_section = raw_module
156 .memory_section_mut()
157 .ok_or_else(|| WasmError::Other("no memory section found".into()))?;
158159if memory_section.entries().is_empty() {
160return Err(WasmError::Other("memory section is empty".into()))
161 }
162for memory_ty in memory_section.entries_mut() {
163let initial = memory_ty.limits().initial();
164let (min, max) = match heap_alloc_strategy {
165 HeapAllocStrategy::Dynamic { maximum_pages } => {
166// Ensure `initial <= maximum_pages`
167(maximum_pages.map(|m| m.min(initial)).unwrap_or(initial), maximum_pages)
168 },
169 HeapAllocStrategy::Static { extra_pages } => {
170let pages = initial.saturating_add(extra_pages);
171 (pages, Some(pages))
172 },
173 };
174*memory_ty = MemoryType::new(min, max);
175 }
176Ok(())
177 }
178179/// Scans the wasm blob for the first section with the name that matches the given. Returns the
180 /// contents of the custom section if found or `None` otherwise.
181 ///
182 /// Only valid for WASM programs; will return an error if the blob is a PolkaVM program.
183pub fn custom_section_contents(&self, section_name: &str) -> Option<&[u8]> {
184self.as_webassembly_blob()
185 .ok()?
186.custom_sections()
187 .find(|cs| cs.name() == section_name)
188 .map(|cs| cs.payload())
189 }
190191/// Consumes this runtime blob and serializes it.
192pub fn serialize(self) -> Vec<u8> {
193match self.0 {
194 BlobKind::WebAssembly(raw_module) =>
195 serialize(raw_module).expect("serializing into a vec should succeed; qed"),
196 BlobKind::PolkaVM(ref blob) => blob.1.to_vec(),
197 }
198 }
199200fn as_webassembly_blob(&self) -> Result<&Module, WasmError> {
201match self.0 {
202 BlobKind::WebAssembly(ref raw_module) => Ok(raw_module),
203 BlobKind::PolkaVM(..) => Err(WasmError::Other(
204"expected a WebAssembly program; found a PolkaVM program blob".into(),
205 )),
206 }
207 }
208209fn as_webassembly_blob_mut(&mut self) -> Result<&mut Module, WasmError> {
210match self.0 {
211 BlobKind::WebAssembly(ref mut raw_module) => Ok(raw_module),
212 BlobKind::PolkaVM(..) => Err(WasmError::Other(
213"expected a WebAssembly program; found a PolkaVM program blob".into(),
214 )),
215 }
216 }
217218fn into_webassembly_blob(self) -> Result<Module, WasmError> {
219match self.0 {
220 BlobKind::WebAssembly(raw_module) => Ok(raw_module),
221 BlobKind::PolkaVM(..) => Err(WasmError::Other(
222"expected a WebAssembly program; found a PolkaVM program blob".into(),
223 )),
224 }
225 }
226227/// Gets a reference to the inner PolkaVM program blob, if this is a PolkaVM program.
228pub fn as_polkavm_blob(&self) -> Option<&polkavm::ProgramBlob> {
229match self.0 {
230 BlobKind::WebAssembly(..) => None,
231 BlobKind::PolkaVM((ref blob, _)) => Some(blob),
232 }
233 }
234}