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}