referrerpolicy=no-referrer-when-downgrade

sp_virtualization/
host_functions.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18use crate::{
19	CompileStatus, DestroyError, ExecError, InstanceId, InstantiateError, MemoryError, ModuleError,
20	ModuleId, SyscallSymbol,
21};
22use core::mem;
23use num_enum::{IntoPrimitive, TryFromPrimitive};
24use sp_runtime_interface::{
25	pass_by::{
26		ConvertAndReturnAs, PassAs, PassFatPointerAndRead, PassFatPointerAndReadOption,
27		PassFatPointerAndWrite, PassPointerAndWrite,
28	},
29	runtime_interface,
30};
31
32/// Buffer shared between runtime and executor for passing syscall data across the
33/// host function boundary.
34///
35/// The runtime allocates this on its stack and passes it via pointer.
36/// The host fills it in when returning from [`crate::Execution::run`].
37#[derive(Debug, Default)]
38#[repr(C)]
39pub struct ExecBuffer {
40	/// Gas remaining after the execution step.
41	pub gas_left: i64,
42	/// The syscall symbol (only meaningful when the status is [`ExecStatus::Syscall`]).
43	pub syscall_symbol: SyscallSymbol,
44	/// Syscall register arguments a0-a5 (only meaningful for [`ExecStatus::Syscall`]).
45	pub a0: u64,
46	pub a1: u64,
47	pub a2: u64,
48	pub a3: u64,
49	pub a4: u64,
50	pub a5: u64,
51}
52
53impl AsRef<[u8]> for ExecBuffer {
54	fn as_ref(&self) -> &[u8] {
55		// SAFETY: `ExecBuffer` is `#[repr(C)]` with a well-defined layout of primitive fields
56		// and no implicit padding.
57		unsafe {
58			core::slice::from_raw_parts(self as *const Self as *const u8, mem::size_of::<Self>())
59		}
60	}
61}
62
63impl AsMut<[u8]> for ExecBuffer {
64	fn as_mut(&mut self) -> &mut [u8] {
65		// SAFETY: `ExecBuffer` is `#[repr(C)]` with a well-defined layout of primitive fields
66		// and no implicit padding.
67		unsafe {
68			core::slice::from_raw_parts_mut(self as *mut Self as *mut u8, mem::size_of::<Self>())
69		}
70	}
71}
72
73/// Status returned by the `run` host function.
74#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
75#[repr(u32)]
76pub enum ExecStatus {
77	/// Execution finished normally.
78	Finished = 0,
79	/// A syscall was encountered — check the [`ExecBuffer`] for details.
80	Syscall = 1,
81}
82
83/// Implement the i64 wire encoding for error types used in [`RIIntResult`].
84///
85/// Errors carry their wire value directly: each variant is declared with a negative
86/// `#[repr(i32)]` discriminant, so the encoding is a sign-extending cast to `i64` and
87/// the decoding is a `TryFrom<i64>` via `i32`. This relies on [`IntoPrimitive`] (for
88/// `i32::from`) and [`TryFromPrimitive`] (for `Self::try_from(i32)`) being derived on
89/// the type.
90macro_rules! impl_ri_error_encoding {
91	($($t:ty),+ $(,)?) => {$(
92		impl From<$t> for i64 {
93			fn from(error: $t) -> Self {
94				i32::from(error) as i64
95			}
96		}
97
98		impl TryFrom<i64> for $t {
99			type Error = ();
100			fn try_from(value: i64) -> Result<Self, Self::Error> {
101				let v = i32::try_from(value).map_err(|_| ())?;
102				Self::try_from(v).map_err(|_| ())
103			}
104		}
105	)+};
106}
107
108impl_ri_error_encoding!(ModuleError, InstantiateError, ExecError, DestroyError, MemoryError);
109
110impl From<u32> for ModuleId {
111	fn from(id: u32) -> Self {
112		Self(id)
113	}
114}
115
116impl From<ModuleId> for u32 {
117	fn from(id: ModuleId) -> Self {
118		id.0
119	}
120}
121
122impl IntoI64 for ModuleId {
123	const MAX: i64 = u32::MAX as i64;
124}
125
126impl From<ModuleId> for i64 {
127	fn from(id: ModuleId) -> Self {
128		u32::from(id) as i64
129	}
130}
131
132impl TryFrom<i64> for ModuleId {
133	type Error = ();
134	fn try_from(value: i64) -> Result<Self, Self::Error> {
135		u32::try_from(value).map(ModuleId::from).map_err(|_| ())
136	}
137}
138
139/// Result of a successful `compile_*` host function call.
140///
141/// Pairs the produced [`ModuleId`] with a [`CompileStatus`] so the runtime can tell whether
142/// the call hit the in-extension cache (cheap) or required a fresh compile (expensive).
143///
144/// Wire encoding (packed into the `i64` return value):
145/// - low 32 bits hold the `ModuleId`
146/// - bits 32-39 hold the [`CompileStatus`] discriminant (`u8`)
147/// - bits 40-62 are reserved for future packed fields
148/// - the result space stays disjoint from the negative range used by [`ModuleError`].
149pub struct CompiledModule {
150	pub id: ModuleId,
151	pub status: CompileStatus,
152}
153
154impl IntoI64 for CompiledModule {
155	const MAX: i64 = (1i64 << 40) - 1;
156}
157
158impl From<CompiledModule> for i64 {
159	fn from(m: CompiledModule) -> Self {
160		let status = u8::from(m.status) as i64;
161		(status << 32) | (u32::from(m.id) as i64)
162	}
163}
164
165impl TryFrom<i64> for CompiledModule {
166	type Error = ();
167	fn try_from(value: i64) -> Result<Self, Self::Error> {
168		let id = ModuleId::from(value as u32);
169		let status = CompileStatus::try_from((value >> 32) as u8).map_err(|_| ())?;
170		Ok(Self { id, status })
171	}
172}
173
174impl From<u32> for InstanceId {
175	fn from(id: u32) -> Self {
176		Self(id)
177	}
178}
179
180impl From<InstanceId> for u32 {
181	fn from(id: InstanceId) -> Self {
182		id.0
183	}
184}
185
186impl IntoI64 for InstanceId {
187	const MAX: i64 = u32::MAX as i64;
188}
189
190impl From<InstanceId> for i64 {
191	fn from(id: InstanceId) -> Self {
192		u32::from(id) as i64
193	}
194}
195
196impl TryFrom<i64> for InstanceId {
197	type Error = ();
198	fn try_from(value: i64) -> Result<Self, Self::Error> {
199		u32::try_from(value).map(InstanceId::from).map_err(|_| ())
200	}
201}
202
203// The following code is an excerpt from RFC-145 implementation (still to be adopted)
204// ---vvv--- 8< CUT HERE 8< ---vvv---
205
206/// Used to return less-than-64-bit value passed as `i64` through the FFI boundary.
207/// Negative values are used to represent error variants.
208pub enum RIIntResult<R, E> {
209	/// Successful result
210	Ok(R),
211	/// Error result
212	Err(E),
213}
214
215impl<R, E, OR, OE> From<Result<OR, OE>> for RIIntResult<R, E>
216where
217	R: From<OR>,
218	E: From<OE>,
219{
220	fn from(result: Result<OR, OE>) -> Self {
221		match result {
222			Ok(value) => Self::Ok(value.into()),
223			Err(error) => Self::Err(error.into()),
224		}
225	}
226}
227
228impl<R, E, OR, OE> From<RIIntResult<R, E>> for Result<OR, OE>
229where
230	OR: From<R>,
231	OE: From<E>,
232{
233	fn from(result: RIIntResult<R, E>) -> Self {
234		match result {
235			RIIntResult::Ok(value) => Ok(value.into()),
236			RIIntResult::Err(error) => Err(error.into()),
237		}
238	}
239}
240
241trait IntoI64: Into<i64> {
242	const MAX: i64;
243}
244
245impl IntoI64 for u32 {
246	const MAX: i64 = u32::MAX as i64;
247}
248
249impl<R: Into<i64> + IntoI64, E: Into<i64> + strum::EnumCount> From<RIIntResult<R, E>> for i64 {
250	fn from(result: RIIntResult<R, E>) -> Self {
251		match result {
252			RIIntResult::Ok(value) => value.into(),
253			RIIntResult::Err(e) => {
254				let error_code: i64 = e.into();
255				assert!(
256					error_code < 0 && error_code >= -(E::COUNT as i64),
257					"Error variant index out of bounds"
258				);
259				error_code
260			},
261		}
262	}
263}
264
265impl<R: TryFrom<i64> + IntoI64, E: TryFrom<i64> + strum::EnumCount> TryFrom<i64>
266	for RIIntResult<R, E>
267{
268	type Error = ();
269
270	fn try_from(value: i64) -> Result<Self, Self::Error> {
271		if value >= 0 && value <= R::MAX.into() {
272			Ok(RIIntResult::Ok(value.try_into().map_err(|_| ())?))
273		} else if value < 0 && value >= -(E::COUNT as i64) {
274			Ok(RIIntResult::Err(value.try_into().map_err(|_| ())?))
275		} else {
276			Err(())
277		}
278	}
279}
280
281pub struct VoidResult;
282
283impl IntoI64 for VoidResult {
284	const MAX: i64 = 0;
285}
286
287impl From<()> for VoidResult {
288	fn from(_: ()) -> Self {
289		VoidResult
290	}
291}
292
293impl From<VoidResult> for () {
294	fn from(_: VoidResult) -> Self {
295		()
296	}
297}
298
299impl From<VoidResult> for i64 {
300	fn from(_: VoidResult) -> Self {
301		0
302	}
303}
304
305impl TryFrom<i64> for VoidResult {
306	type Error = ();
307
308	fn try_from(value: i64) -> Result<Self, Self::Error> {
309		if value == 0 {
310			Ok(VoidResult)
311		} else {
312			Err(())
313		}
314	}
315}
316
317// ---^^^--- 8< CUT HERE 8< ---^^^---
318
319/// Host functions used to spawn and call into PolkaVM instances.
320///
321/// Use [`crate::Instance`] instead of these raw host functions.
322///
323/// The [`crate::VirtManagerExt`] extension must be registered in the externalities
324/// before any of these host functions can be used.
325///
326/// # ⚠️ Unstable — Do Not Use in Production ⚠️
327///
328/// **This interface is unstable and subject to breaking changes without notice.**
329///
330/// These host functions are **not available on Polkadot** (or any other production
331/// relay/parachain) until the API has been stabilized. If you use them in a production
332/// runtime, your runtime **will break** when the API changes.
333///
334/// Only use for local testing, development, and experimentation on test networks.
335/// There is no stability guarantee and no deprecation period.
336#[runtime_interface]
337pub trait Virtualization {
338	/// Compile the given program bytes into a module.
339	///
340	/// If `identifier` is `Some` and a module is already cached under it, no compilation
341	/// occurs and the returned [`CompiledModule`] carries [`CompileStatus::Cached`].
342	/// Otherwise the bytes are compiled, cached under `identifier` if supplied, and the
343	/// returned [`CompiledModule`] carries [`CompileStatus::Compiled`].
344	///
345	/// The contained `module_id` can be passed to [`instantiate`] to create instances.
346	fn compile_from_bytes(
347		&mut self,
348		program: PassFatPointerAndRead<&[u8]>,
349		identifier: PassFatPointerAndReadOption<&[u8]>,
350	) -> ConvertAndReturnAs<
351		Result<CompiledModule, ModuleError>,
352		RIIntResult<CompiledModule, ModuleError>,
353		i64,
354	> {
355		use sp_externalities::ExternalitiesExt as _;
356		use std::sync::Once;
357		static WARN_ONCE: Once = Once::new();
358		WARN_ONCE.call_once(|| {
359			log::warn!(
360				target: crate::LOG_TARGET,
361				"Virtualization host functions are UNSTABLE and subject to breaking changes. \
362				They are NOT available on Polkadot and using them in production will cause breakage. \
363				Only use for testing and experimentation.",
364			);
365		});
366
367		// Cache lookup first when an identifier is supplied.
368		if let Some(identifier) = identifier {
369			let cache_result = self
370				.extension::<crate::VirtManagerExt>()
371				.expect("VirtManagerExt not registered in externalities")
372				.lookup(identifier);
373			match cache_result {
374				Ok(id) => return Ok(CompiledModule { id, status: CompileStatus::Cached }),
375				Err(ModuleError::NotCached) => {},
376				Err(err) => return Err(err),
377			}
378		}
379
380		let id = self
381			.extension::<crate::VirtManagerExt>()
382			.expect("VirtManagerExt not registered in externalities")
383			.compile_from_bytes(program, identifier)?;
384		Ok(CompiledModule { id, status: CompileStatus::Compiled })
385	}
386
387	/// Look up a previously compiled module by `identifier`.
388	///
389	/// Returns `Ok(module_id)` if a module is cached under `identifier`,
390	/// `Err(ModuleError::NotCached)` otherwise. This is a pure cache lookup — no storage
391	/// access — so the caller pays only the lookup cost.
392	fn lookup(
393		&mut self,
394		identifier: PassFatPointerAndRead<&[u8]>,
395	) -> ConvertAndReturnAs<Result<ModuleId, ModuleError>, RIIntResult<ModuleId, ModuleError>, i64>
396	{
397		use sp_externalities::ExternalitiesExt as _;
398		self.extension::<crate::VirtManagerExt>()
399			.expect("VirtManagerExt not registered in externalities")
400			.lookup(identifier)
401	}
402
403	/// Compile a module whose program bytes live at `storage_key`.
404	///
405	/// Returns a [`CompiledModule`] carrying [`CompileStatus::Cached`] if a module is already
406	/// cached under `storage_key`. On a cache miss, loads the program bytes from storage at
407	/// `storage_key`, compiles them, caches the result under that same key, and returns a
408	/// [`CompiledModule`] carrying [`CompileStatus::Compiled`]. Pass an empty `child_trie`
409	/// to read from the main state trie.
410	fn compile_from_storage_key(
411		&mut self,
412		storage_key: PassFatPointerAndRead<&[u8]>,
413		child_trie: PassFatPointerAndRead<&[u8]>,
414	) -> ConvertAndReturnAs<
415		Result<CompiledModule, ModuleError>,
416		RIIntResult<CompiledModule, ModuleError>,
417		i64,
418	> {
419		use sp_externalities::ExternalitiesExt as _;
420
421		// Try the in-memory cache first.
422		let cache_result = self
423			.extension::<crate::VirtManagerExt>()
424			.expect("VirtManagerExt not registered in externalities")
425			.lookup(storage_key);
426
427		match cache_result {
428			Ok(id) => return Ok(CompiledModule { id, status: CompileStatus::Cached }),
429			Err(ModuleError::NotCached) => {},
430			Err(err) => return Err(err),
431		}
432
433		// Cache miss — load from storage.
434		let code = if child_trie.is_empty() {
435			self.storage(storage_key)
436		} else {
437			let child_info = sp_storage::ChildInfo::new_default(child_trie);
438			self.child_storage(&child_info, storage_key)
439		};
440
441		let code = match code {
442			Some(code) => code,
443			None => return Err(ModuleError::NotFound),
444		};
445
446		// Compile and cache under the storage key so the next lookup hits.
447		let id = self
448			.extension::<crate::VirtManagerExt>()
449			.expect("VirtManagerExt not registered in externalities")
450			.compile_from_bytes(&code, Some(storage_key))?;
451		Ok(CompiledModule { id, status: CompileStatus::Compiled })
452	}
453
454	/// Create a new instance from a compiled module.
455	///
456	/// Returns the `instance_id` which needs to be passed to reference this instance
457	/// when using the other functions of this trait.
458	fn instantiate(
459		&mut self,
460		module_id: PassAs<ModuleId, u32>,
461	) -> ConvertAndReturnAs<
462		Result<InstanceId, InstantiateError>,
463		RIIntResult<InstanceId, InstantiateError>,
464		i64,
465	> {
466		use sp_externalities::ExternalitiesExt as _;
467		self.extension::<crate::VirtManagerExt>()
468			.expect("VirtManagerExt not registered in externalities")
469			.instantiate(module_id)
470	}
471
472	/// Prepare the given instance to run the named exported function.
473	///
474	/// This sets the program counter but does not start execution.
475	/// Call [`run`] afterwards to begin.
476	fn prepare(
477		&mut self,
478		instance_id: PassAs<InstanceId, u32>,
479		function: PassFatPointerAndRead<&[u8]>,
480	) -> ConvertAndReturnAs<Result<(), ExecError>, RIIntResult<VoidResult, ExecError>, i64> {
481		use sp_externalities::ExternalitiesExt as _;
482		self.extension::<crate::VirtManagerExt>()
483			.expect("VirtManagerExt not registered in externalities")
484			.prepare(instance_id, function)
485	}
486
487	/// Set register a0 and run until the next interrupt.
488	///
489	/// Returns `ExecStatus::Finished` or `ExecStatus::Syscall` as `u32`.
490	/// When a syscall occurs, the syscall arguments are written into the
491	/// `exec_buffer` via [`PassPointerAndWrite`].
492	fn run(
493		&mut self,
494		instance_id: PassAs<InstanceId, u32>,
495		gas_left: i64,
496		a0: u64,
497		exec_buffer: PassPointerAndWrite<&mut ExecBuffer, { mem::size_of::<ExecBuffer>() }>,
498	) -> ConvertAndReturnAs<Result<u32, ExecError>, RIIntResult<u32, ExecError>, i64> {
499		use sp_externalities::ExternalitiesExt as _;
500		self.extension::<crate::VirtManagerExt>()
501			.expect("VirtManagerExt not registered in externalities")
502			.run(instance_id, gas_left, a0)
503			.map(|(status, buf)| {
504				*exec_buffer = buf;
505				u32::from(status)
506			})
507	}
508
509	/// Destroy this instance.
510	///
511	/// Any attempt accessing an instance after destruction will yield the `InvalidInstance` error.
512	fn destroy(
513		&mut self,
514		instance_id: PassAs<InstanceId, u32>,
515	) -> ConvertAndReturnAs<Result<(), DestroyError>, RIIntResult<VoidResult, DestroyError>, i64> {
516		use sp_externalities::ExternalitiesExt as _;
517		self.extension::<crate::VirtManagerExt>()
518			.expect("VirtManagerExt not registered in externalities")
519			.destroy(instance_id)
520	}
521
522	/// See [`crate::Execution::read_memory`].
523	fn read_memory(
524		&mut self,
525		instance_id: PassAs<InstanceId, u32>,
526		offset: u32,
527		dest: PassFatPointerAndWrite<&mut [u8]>,
528	) -> ConvertAndReturnAs<Result<(), MemoryError>, RIIntResult<VoidResult, MemoryError>, i64> {
529		use sp_externalities::ExternalitiesExt as _;
530		self.extension::<crate::VirtManagerExt>()
531			.expect("VirtManagerExt not registered in externalities")
532			.read_memory(instance_id, offset, dest)
533	}
534
535	/// See [`crate::Execution::write_memory`].
536	fn write_memory(
537		&mut self,
538		instance_id: PassAs<InstanceId, u32>,
539		offset: u32,
540		src: PassFatPointerAndRead<&[u8]>,
541	) -> ConvertAndReturnAs<Result<(), MemoryError>, RIIntResult<VoidResult, MemoryError>, i64> {
542		use sp_externalities::ExternalitiesExt as _;
543		self.extension::<crate::VirtManagerExt>()
544			.expect("VirtManagerExt not registered in externalities")
545			.write_memory(instance_id, offset, src)
546	}
547}
548
549/// The host-side operations driven by the virtualization host functions.
550///
551/// The concrete implementation lives outside this crate (see `sc-virtualization`) so that
552/// `sp-virtualization` itself does not depend on a specific virtual machine backend.
553#[cfg(not(substrate_runtime))]
554pub trait VirtManagerBackend: Send + 'static {
555	/// Compile `program` into a new module.
556	///
557	/// If `identifier` is `Some`, the compiled module is retained in the per-extension cache
558	/// keyed by that opaque byte slice so a later [`lookup`] with the same bytes can reuse it.
559	///
560	/// [`lookup`]: VirtManagerBackend::lookup
561	fn compile_from_bytes(
562		&mut self,
563		program: &[u8],
564		identifier: Option<&[u8]>,
565	) -> Result<ModuleId, ModuleError>;
566
567	/// Look up a module previously cached under `identifier`.
568	///
569	/// Returns [`ModuleError::NotCached`] if no module is cached under that key. The backend
570	/// never reads storage — `identifier` is an opaque byte slice as far as it is concerned.
571	fn lookup(&mut self, identifier: &[u8]) -> Result<ModuleId, ModuleError>;
572
573	fn instantiate(&mut self, module_id: ModuleId) -> Result<InstanceId, InstantiateError>;
574
575	fn prepare(&mut self, instance_id: InstanceId, function: &[u8]) -> Result<(), ExecError>;
576
577	fn run(
578		&mut self,
579		instance_id: InstanceId,
580		gas_left: i64,
581		a0: u64,
582	) -> Result<(ExecStatus, ExecBuffer), ExecError>;
583
584	fn destroy(&mut self, instance_id: InstanceId) -> Result<(), DestroyError>;
585
586	fn read_memory(
587		&mut self,
588		instance_id: InstanceId,
589		offset: u32,
590		dest: &mut [u8],
591	) -> Result<(), MemoryError>;
592
593	fn write_memory(
594		&mut self,
595		instance_id: InstanceId,
596		offset: u32,
597		src: &[u8],
598	) -> Result<(), MemoryError>;
599}
600
601#[cfg(not(substrate_runtime))]
602sp_externalities::decl_extension! {
603	/// Extension wrapping a [`VirtManagerBackend`] so it can be accessed through
604	/// the externalities by the virtualization host functions.
605	pub struct VirtManagerExt(Box<dyn VirtManagerBackend>);
606}
607
608#[cfg(not(substrate_runtime))]
609impl VirtManagerExt {
610	/// Wrap the given backend so it can be registered as an externalities extension.
611	pub fn new<B: VirtManagerBackend>(backend: B) -> Self {
612		Self(Box::new(backend))
613	}
614}