referrerpolicy=no-referrer-when-downgrade

sp_virtualization/
lib.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
18//! This crate is intended for use by runtime code (e.g. pallet-contracts) to spawn PolkaVM
19//! instances and execute calls into them. Its purpose is to add one layer of abstraction so that
20//! it works transparently from the actual runtime (via the host functions defined in this crate)
21//! but also from tests (which run natively).
22//!
23//! The crate exposes the runtime-side forwarder API and the [`#[runtime_interface]`] declaration
24//! plus the [`VirtManagerBackend`] trait that backs it. The concrete polkavm-driven backend lives
25//! in the `sc-virtualization` client crate, which registers a `VirtManager` via [`VirtManagerExt`]
26//! before dispatching runtime calls.
27//!
28//! Please keep in mind that the interface is kept simple because it has to match the interface
29//! of the host function so that the abstraction works. It will never expose the whole PolkaVM
30//! interface.
31//!
32//! # ⚠️ Unstable API — Do Not Use in Production ⚠️
33//!
34//! **This crate's API is unstable and subject to breaking changes without notice.**
35//!
36//! The virtualization host functions exposed by this crate have **not been stabilized** and are
37//! **not available on Polkadot** (or any other production relay/parachain) until they are. Using
38//! them in a production runtime **will cause your runtime to break** when the API changes.
39//!
40//! This crate should **only** be used for:
41//! - Local testing and development
42//! - Experimentation on test networks
43//!
44//! **Do not** ship runtimes that depend on this crate to any chain you care about. There is no
45//! stability guarantee and no deprecation period — the interface may change at any time.
46
47#![cfg_attr(substrate_runtime, no_std)]
48
49mod forwarder;
50mod host_functions;
51pub mod tests;
52
53pub use crate::tests::run as run_tests;
54pub use forwarder::{Execution, Instance, Module};
55#[cfg(not(substrate_runtime))]
56pub use host_functions::{ExecBuffer, ExecStatus, VirtManagerBackend, VirtManagerExt};
57
58/// Aggregate of all host functions exposed by this crate.
59///
60/// Plug this into your node's `HostFunctions` tuple alongside the other host-function sets
61/// (e.g. [`sp_io::SubstrateHostFunctions`]). At runtime, register a [`VirtManagerExt`]
62/// wrapping your backend on the externalities before any of these host functions are
63/// invoked — without it, every call panics. All other interaction with the virtualization
64/// machinery should go through [`Module`], [`Instance`], and [`Execution`].
65#[cfg(not(substrate_runtime))]
66#[doc(inline)]
67pub use host_functions::virtualization::HostFunctions;
68
69use num_enum::{IntoPrimitive, TryFromPrimitive};
70
71/// The target we use for all logging.
72pub const LOG_TARGET: &str = "virtualization";
73
74/// Maximum length of a syscall symbol in bytes.
75pub const MAX_SYSCALL_SYMBOL_LEN: usize = 32;
76
77/// Opaque handle to a compiled module.
78#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
79#[repr(transparent)]
80pub struct ModuleId(u32);
81
82/// Opaque handle to a virtualization instance.
83#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
84#[repr(transparent)]
85pub struct InstanceId(u32);
86
87/// The result of running a virtualization instance.
88///
89/// `V` is the idle [`Instance`] type (returned on completion or error),
90/// `I` is the [`Execution`] type (returned when a syscall is encountered).
91#[derive(Debug, PartialEq, Eq)]
92pub enum ExecResult<V, I> {
93	/// Execution finished normally. Returns the idle instance for potential reuse.
94	Finished {
95		/// The idle instance, ready to be prepared for another call.
96		instance: V,
97		/// How much gas is remaining after the execution.
98		gas_left: i64,
99	},
100	/// A syscall was encountered. The caller should handle the syscall and then
101	/// call [`Execution::run`] again to continue execution.
102	Syscall {
103		/// The running execution, with memory access available for syscall handling.
104		execution: I,
105		/// How much gas is remaining at the point of the syscall.
106		gas_left: i64,
107		/// The symbol identifying the syscall.
108		syscall_symbol: SyscallSymbol,
109		/// Register arguments a0-a5.
110		a0: u64,
111		a1: u64,
112		a2: u64,
113		a3: u64,
114		a4: u64,
115		a5: u64,
116	},
117	/// An error occurred during execution. Returns the idle instance.
118	Error {
119		/// The idle instance, returned for potential reuse or cleanup.
120		instance: V,
121		/// The error that occurred.
122		error: ExecError,
123	},
124}
125
126/// A syscall symbol with a maximum length of [`MAX_SYSCALL_SYMBOL_LEN`] bytes.
127#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
128#[repr(C)]
129pub struct SyscallSymbol {
130	bytes: [u8; MAX_SYSCALL_SYMBOL_LEN],
131	len: u64,
132}
133
134impl SyscallSymbol {
135	/// Build a [`SyscallSymbol`] from a byte slice.
136	///
137	/// Returns `None` if `bytes` exceeds [`MAX_SYSCALL_SYMBOL_LEN`].
138	pub fn new(bytes: &[u8]) -> Option<Self> {
139		if bytes.len() > MAX_SYSCALL_SYMBOL_LEN {
140			return None;
141		}
142		let mut buf = [0u8; MAX_SYSCALL_SYMBOL_LEN];
143		buf[..bytes.len()].copy_from_slice(bytes);
144		Some(Self { bytes: buf, len: bytes.len() as u64 })
145	}
146}
147
148impl AsRef<[u8]> for SyscallSymbol {
149	fn as_ref(&self) -> &[u8] {
150		&self.bytes[..self.len as usize]
151	}
152}
153
154/// Status returned by the `compile_*` host functions.
155///
156/// Lets the caller distinguish a cheap cache hit from a fresh compile so it can
157/// charge weight accordingly.
158#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
159#[repr(u8)]
160pub enum CompileStatus {
161	/// Module was already in the per-extension cache; no compilation occurred.
162	Cached = 0,
163	/// Module was freshly compiled (and, for [`Module::from_storage_key`], the
164	/// program bytes were read from storage).
165	Compiled = 1,
166}
167
168/// Errors that can be emitted when compiling a program into a module.
169#[derive(TryFromPrimitive, IntoPrimitive, strum::EnumCount, Debug, PartialEq, Eq)]
170#[repr(i32)]
171pub enum ModuleError {
172	/// The supplied code was invalid.
173	InvalidImage = -1,
174	/// No module with the given identifier has been compiled yet.
175	NotCached = -2,
176	/// No code was found at the supplied storage key.
177	NotFound = -3,
178}
179
180/// Errors that can be emitted when instantiating a new virtualization instance.
181#[derive(TryFromPrimitive, IntoPrimitive, strum::EnumCount, Debug, PartialEq, Eq)]
182#[repr(i32)]
183pub enum InstantiateError {
184	/// The supplied code was invalid.
185	InvalidImage = -1,
186	/// The supplied `module_id` was invalid or the module was not found.
187	InvalidModule = -2,
188}
189
190/// Errors that can be emitted when executing a new virtualization instance.
191#[derive(TryFromPrimitive, IntoPrimitive, strum::EnumCount, Debug, PartialEq, Eq)]
192#[repr(i32)]
193pub enum ExecError {
194	/// The supplied `instance_id` was invalid or the instance was destroyed.
195	InvalidInstance = -1,
196	/// The supplied code was invalid. Most likely caused by invalid entry points.
197	InvalidImage = -2,
198	/// The execution ran out of gas before it could finish.
199	OutOfGas = -3,
200	/// The execution trapped before it could finish.
201	///
202	/// This can be caused by executing an `unimp` instruction.
203	Trap = -4,
204}
205
206/// Errors that can be emitted when accessing a virtualization instance's memory.
207#[derive(TryFromPrimitive, IntoPrimitive, strum::EnumCount, Debug, PartialEq, Eq)]
208#[repr(i32)]
209pub enum MemoryError {
210	/// The supplied `instance_id` was invalid or the instance was destroyed.
211	InvalidInstance = -1,
212	/// The memory region specified is not accessible.
213	OutOfBounds = -2,
214}
215
216/// Errors that can be emitted when destroying a virtualization instance.
217#[derive(TryFromPrimitive, IntoPrimitive, strum::EnumCount, Debug, PartialEq, Eq)]
218#[repr(i32)]
219pub enum DestroyError {
220	/// The supplied `instance_id` was invalid or the instance was destroyed.
221	InvalidInstance = -1,
222}