Skip to main content

sc_executor_common/runtime_blob/
runtime_blob.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// 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.
10
11// 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.
15
16// 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/>.
18
19use 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};
25
26/// A program blob containing a Substrate runtime.
27#[derive(Clone)]
28pub struct RuntimeBlob(BlobKind);
29
30#[derive(Clone)]
31enum BlobKind {
32	WebAssembly(Module),
33	PolkaVM((polkavm::ProgramBlob, ArcBytes)),
34}
35
36impl RuntimeBlob {
37	/// Create `RuntimeBlob` from the given WASM or PolkaVM compressed program blob.
38	///
39	/// See [`sp_maybe_compressed_blob`] for details about decompression.
40	pub fn uncompress_if_needed(wasm_code: &[u8]) -> Result<Self, WasmError> {
41		use sp_maybe_compressed_blob::CODE_BLOB_BOMB_LIMIT;
42		let wasm_code = sp_maybe_compressed_blob::decompress(wasm_code, CODE_BLOB_BOMB_LIMIT)
43			.map_err(|e| WasmError::Other(format!("Decompression error: {:?}", e)))?;
44		Self::new(&wasm_code)
45	}
46
47	/// 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`.
53	pub fn new(raw_blob: &[u8]) -> Result<Self, WasmError> {
54		if raw_blob.starts_with(b"PVM\0") {
55			if crate::is_polkavm_enabled() {
56				let raw = ArcBytes::from(raw_blob);
57				let blob = polkavm::ProgramBlob::parse(raw.clone())?;
58				return Ok(Self(BlobKind::PolkaVM((blob, raw))));
59			} else {
60				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()));
61			}
62		}
63
64		let raw_module: Module = deserialize_buffer(raw_blob)
65			.map_err(|e| WasmError::Other(format!("cannot deserialize module: {:?}", e)))?;
66		Ok(Self(BlobKind::WebAssembly(raw_module)))
67	}
68
69	/// 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.
81	pub fn inject_stack_depth_metering(self, stack_depth_limit: u32) -> Result<Self, WasmError> {
82		let 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				})?;
87
88		Ok(Self(BlobKind::WebAssembly(injected_module)))
89	}
90
91	/// 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.
98	pub fn convert_memory_import_into_export(&mut self) -> Result<(), WasmError> {
99		let raw_module = self.as_webassembly_blob_mut()?;
100		let import_section = match raw_module.import_section_mut() {
101			Some(import_section) => import_section,
102			None => return Ok(()),
103		};
104
105		let import_entries = import_section.entries_mut();
106		for index in 0..import_entries.len() {
107			let entry = &import_entries[index];
108			let memory_ty = match entry.external() {
109				External::Memory(memory_ty) => *memory_ty,
110				_ => continue,
111			};
112
113			let memory_name = entry.field().to_owned();
114			import_entries.remove(index);
115
116			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				})?;
124
125			if 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.
128				raw_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)));
137
138			break;
139		}
140
141		Ok(())
142	}
143
144	/// Removes the maximum page limit from the memory section.
145	///
146	/// Only valid for WASM programs; will return an error if the blob is a PolkaVM program.
147	pub fn clear_memory_max_limit(&mut self) -> Result<(), WasmError> {
148		let raw_module = self.as_webassembly_blob_mut()?;
149		let memory_section = match raw_module.memory_section_mut() {
150			Some(section) => section,
151			None => return Ok(()),
152		};
153		for memory_ty in memory_section.entries_mut() {
154			*memory_ty = MemoryType::new(memory_ty.limits().initial(), None);
155		}
156		Ok(())
157	}
158
159	/// Modifies the blob's memory section according to the given `heap_alloc_strategy`.
160	///
161	/// Will return an error in case there is no memory section present,
162	/// or if the memory section is empty.
163	///
164	/// Only valid for WASM programs; will return an error if the blob is a PolkaVM program.
165	#[deprecated]
166	pub fn setup_memory_according_to_heap_alloc_strategy(
167		&mut self,
168		heap_alloc_strategy: HeapAllocStrategy,
169	) -> Result<(), WasmError> {
170		let raw_module = self.as_webassembly_blob_mut()?;
171		let memory_section = raw_module
172			.memory_section_mut()
173			.ok_or_else(|| WasmError::Other("no memory section found".into()))?;
174
175		if memory_section.entries().is_empty() {
176			return Err(WasmError::Other("memory section is empty".into()));
177		}
178		for memory_ty in memory_section.entries_mut() {
179			let initial = memory_ty.limits().initial();
180			let (min, max) = match heap_alloc_strategy {
181				HeapAllocStrategy::Dynamic { maximum_pages } => {
182					// Ensure `initial <= maximum_pages`
183					(maximum_pages.map(|m| m.min(initial)).unwrap_or(initial), maximum_pages)
184				},
185				HeapAllocStrategy::Static { extra_pages } => {
186					let pages = initial.saturating_add(extra_pages);
187					(pages, Some(pages))
188				},
189			};
190			*memory_ty = MemoryType::new(min, max);
191		}
192		Ok(())
193	}
194
195	/// Scans the wasm blob for the first section with the name that matches the given. Returns the
196	/// contents of the custom section if found or `None` otherwise.
197	///
198	/// Only valid for WASM programs; will return an error if the blob is a PolkaVM program.
199	pub fn custom_section_contents(&self, section_name: &str) -> Option<&[u8]> {
200		self.as_webassembly_blob()
201			.ok()?
202			.custom_sections()
203			.find(|cs| cs.name() == section_name)
204			.map(|cs| cs.payload())
205	}
206
207	/// Consumes this runtime blob and serializes it.
208	pub fn serialize(self) -> Vec<u8> {
209		match self.0 {
210			BlobKind::WebAssembly(raw_module) => {
211				serialize(raw_module).expect("serializing into a vec should succeed; qed")
212			},
213			BlobKind::PolkaVM(ref blob) => blob.1.to_vec(),
214		}
215	}
216
217	fn as_webassembly_blob(&self) -> Result<&Module, WasmError> {
218		match self.0 {
219			BlobKind::WebAssembly(ref raw_module) => Ok(raw_module),
220			BlobKind::PolkaVM(..) => Err(WasmError::Other(
221				"expected a WebAssembly program; found a PolkaVM program blob".into(),
222			)),
223		}
224	}
225
226	fn as_webassembly_blob_mut(&mut self) -> Result<&mut Module, WasmError> {
227		match self.0 {
228			BlobKind::WebAssembly(ref mut raw_module) => Ok(raw_module),
229			BlobKind::PolkaVM(..) => Err(WasmError::Other(
230				"expected a WebAssembly program; found a PolkaVM program blob".into(),
231			)),
232		}
233	}
234
235	fn into_webassembly_blob(self) -> Result<Module, WasmError> {
236		match self.0 {
237			BlobKind::WebAssembly(raw_module) => Ok(raw_module),
238			BlobKind::PolkaVM(..) => Err(WasmError::Other(
239				"expected a WebAssembly program; found a PolkaVM program blob".into(),
240			)),
241		}
242	}
243
244	/// Gets a reference to the inner PolkaVM program blob, if this is a PolkaVM program.
245	pub fn as_polkavm_blob(&self) -> Option<&polkavm::ProgramBlob> {
246		match self.0 {
247			BlobKind::WebAssembly(..) => None,
248			BlobKind::PolkaVM((ref blob, _)) => Some(blob),
249		}
250	}
251}