corevm_host/package.rs
1use crate::{
2 fs, Arg, AudioMode, InputChunk, InputStreams, OutputStream, PageNum, RangeSet, ServiceMessage,
3 VideoMode,
4};
5use alloc::{collections::VecDeque, vec::Vec};
6use bytes::Bytes;
7use codec::{ConstEncodedLen, Decode, Encode, MaxEncodedLen};
8use corevm_types::GuestId;
9use jam_types::{
10 Hash, SegmentBytes, SegmentTreeRoot, ServiceId, SignedGas, VecMap, MEMO_LEN, SEGMENT_LEN,
11};
12
13/// CoreVM-specific [work payload](jam_types::WorkPayload).
14///
15/// This is the input of the work package.
16#[derive(Encode, Decode, Debug, Clone)]
17pub struct CoreVmPayload {
18 /// Inner VM gas.
19 pub gas: SignedGas,
20 /// The prior (known) VM state from which the program run should continue.
21 ///
22 /// Equals [`VmState::initial`] on the first program run.
23 pub vm_state: VmState,
24 /// Execution environment location in the virtual file system.
25 pub exec_ref: fs::BlockRef,
26}
27
28/// CoreVM-specific extrinsics.
29///
30/// This is the input of the work package.
31#[derive(Debug, Default, Clone)]
32pub struct CoreVmExtrinsics {
33 /// Imported memory pages; extrinsic 0.
34 pub imported_memory_pages: Vec<SegmentBytes>,
35 /// Incoming inter-service messages, their sources and their contents; extrinsic 1.
36 pub incoming_service_messages: VecDeque<(ServiceMessage, Bytes)>,
37 /// Input chunks (video/audio/console); extrinsic 2.
38 ///
39 /// In the extrinsic the number of chunks is not stored, only chunks themselves and their
40 /// signatures. Each chunk is signed with [`ExecEnv::input_key`].
41 pub input_chunks: Vec<InputChunk>,
42}
43
44impl CoreVmExtrinsics {
45 /// Max. number of extrinsics a CoreVM-specific work package can have.
46 #[doc(hidden)]
47 pub const MAX_COUNT: usize = 3;
48}
49
50/// CoreVM-specific [work output](jam_types::WorkOutput).
51///
52/// This is the output of the work package.
53#[derive(Encode, Decode, Debug)]
54pub struct CoreVmOutput {
55 /// The specification of the VM's output data.
56 pub vm_output: VmOutput,
57 /// The posterior VM state on which the program run was suspended.
58 ///
59 /// Use this state in [`CoreVmPayload`] to continue the execution in another work package.
60 pub vm_state: VmState,
61 pub old_hash: Hash,
62 pub new_hash: Hash,
63 /// Memory pages that were imported and read from/written to while refining the package.
64 pub touched_imported_pages: VecMap<PageNum, Hash>,
65 /// Memory pages that were read from/written to while refining this work package.
66 ///
67 /// Include deallocated pages indicated by zero hashes.
68 pub updated_pages: VecMap<PageNum, Hash>,
69 /// Execution environment location in the virtual file system.
70 pub exec_ref: fs::BlockRef,
71 /// Inter-service messages that were processed during the program run.
72 pub processed_service_messages: Vec<ServiceMessage>,
73 /// Messages that were sent by this guest to some other guests.
74 pub outgoing_messages: Vec<(GuestId, Vec<u8>)>,
75}
76
77/// The specification of the VM's output data.
78///
79/// Includes the information that is needed to decode work package exports and also the final state
80/// after the VM invocation (remaining gas and the outcome).
81///
82/// Exports have the following structure:
83///
84/// `[ memory pages | output stream 0 | ... | output stream N | null segments ]`
85#[derive(Encode, Decode, MaxEncodedLen, Debug)]
86pub struct VmOutput {
87 /// Remainig inner VM gas.
88 pub remaining_gas: SignedGas,
89 /// The outcome of the inner VM invocation.
90 pub outcome: Outcome,
91 /// The no. of exported memory pages.
92 pub num_memory_pages: u32,
93 /// Per-stream output size.
94 pub stream_len: [u32; OutputStream::COUNT],
95}
96
97impl VmOutput {
98 /// Get the size of the stream `i`.
99 pub fn stream_len(&self, i: OutputStream) -> u32 {
100 self.stream_len[i as usize - 1]
101 }
102
103 /// Total length of all streams.
104 pub fn all_streams_len(&self) -> u32 {
105 self.stream_len.iter().sum()
106 }
107
108 /// Returns `true` if all streams are empty.
109 pub fn is_empty(&self) -> bool {
110 self.stream_len.iter().all(|len| *len == 0)
111 }
112
113 /// Get the number of output segments.
114 pub fn num_output_segments(&self) -> u32 {
115 self.stream_len.iter().sum::<u32>().div_ceil(SEGMENT_LEN as u32)
116 }
117
118 /// Get the start and end offset of the specified stream.
119 ///
120 /// The offsets are measured from the first exported segment.
121 pub fn get_stream_range(&self, stream: OutputStream) -> (u32, u32) {
122 let mut start = 0;
123 let mut end = 0;
124 for i in OutputStream::ALL {
125 let len = self.stream_len[i as usize - 1];
126 if i == stream {
127 end = start + len;
128 break;
129 }
130 start += len;
131 }
132 let offset = self.num_memory_pages * SEGMENT_LEN as u32;
133 (offset + start, offset + end)
134 }
135}
136
137/// The result code returned by CoreVM service.
138///
139/// It is similar to `jam_pvm_common::InvokeOutcome` but does not include host-call
140/// faults because they are automatically handled by CoreVM.
141#[derive(Encode, Decode, MaxEncodedLen, Debug, PartialEq, Eq, Clone, Copy)]
142pub enum Outcome {
143 /// Completed normally.
144 Halt,
145 /// Completed with a panic.
146 Panic,
147 /// Completed with a page fault that couldn't be handled by CoreVM.
148 ///
149 /// Undhandled page fault might occur because _either_ the max. no. of exports is reached _or_
150 /// the corresponding page was not imported by the work package. In the former case the
151 /// program needs to be resumed in the next work package without any further action from the
152 /// builder. In the latter case the page with the specified address has to be imported on the
153 /// next program run to continue.
154 PageFault { page: PageNum, num_pages: u32 },
155 /// Completed by running out of gas.
156 OutOfGas,
157 /// The inner VM has reached the max. no. of exports while appending new data to the output
158 /// stream.
159 OutputLimitReached,
160 /// The program produced one time-slot worth of video frames or audio samples.
161 ///
162 /// If both streams are active, then both streams need to have one time-slot worth of data to
163 /// trigger this outcome.
164 TimeLimitReached,
165 /// The program is waiting for the specified input streams.
166 WaitingForInput(InputStreams),
167 /// The inner VM has reached max. bundle size while reading input stream or host/inter-service
168 /// message or on page fault.
169 InputLimitReached,
170}
171
172/// Inner VM state.
173#[derive(Encode, Decode, Debug, Clone)]
174pub struct VmState {
175 /// Registers.
176 pub regs: [u64; 13],
177 /// Program counter.
178 pub program_counter: u64,
179 /// Current step.
180 pub step: u32,
181 /// Mapped heap memory pages' indices.
182 ///
183 /// These pages were allocated by the guest but not necessarily read from/written to.
184 ///
185 /// Note that it's possible to include only a subset of the pages if you're sure that they will
186 /// not be touched again during the program run. Howeve,r if such a page is touched the engine
187 /// will panic.
188 pub mapped_heap_pages: RangeSet,
189 /// All memory pages that were read from/written to by the guest but haven't been unmapped
190 /// yet.
191 ///
192 /// Note that it's possible to include only a subset of the pages if you're sure that they will
193 /// not be touched again during the program run. However, if such a page is touched the
194 /// engine will silently initialize it from the default: from zeroes for stack/heap and from
195 /// the program's RW data section for RW data. The program might continue without an error
196 /// after that, but the output will most likely be incorrect.
197 pub resident_pages: RangeSet,
198 /// The state of the Linux kernel syscall layer.
199 pub kernel: KernelState,
200 /// The index of the host-call that needs to be restarted on resuming the program execution.
201 #[doc(hidden)]
202 pub restart_host_call: Option<u64>,
203 /// Video output mode.
204 pub video: Option<VideoMode>,
205 /// Audio output mode.
206 pub audio: Option<AudioMode>,
207}
208
209impl VmState {
210 /// Create initial state.
211 pub const fn initial() -> Self {
212 Self {
213 regs: [0; 13],
214 program_counter: 0,
215 mapped_heap_pages: RangeSet::new(),
216 resident_pages: RangeSet::new(),
217 kernel: KernelState { fds: VecMap::new() },
218 restart_host_call: None,
219 video: None,
220 audio: None,
221 step: 0,
222 }
223 }
224}
225
226/// This macro provides `Self::COUNT_WITHOUT_PARAMS` constant that specifies the total number of
227/// variants without parameters.
228macro_rules! enum_with_count {
229 (
230 $(#[$($attr:tt)*])*
231 pub enum $enum:ident {
232 $(
233 $(#[$($variant_attr:tt)*])*
234 $variant:ident$(($($field:ident),+))?,
235 )*
236 }
237 ) => {
238 $(#[$($attr)*])*
239 pub enum $enum {
240 $(
241 $(#[$($variant_attr)*])*
242 $variant$(($($field),+))?,
243 )*
244 }
245
246 impl $enum {
247 /// Total number of all variants without parameters.
248 #[doc(hidden)]
249 pub const COUNT_WITHOUT_PARAMS: usize = {
250 const fn one(_: &'static str) -> usize { 1 }
251 const fn one_if_has_params(_: &'static str, num_params: usize) -> usize {
252 if num_params == 0 { 1 } else { 0 }
253 }
254 0
255 $(
256 + one_if_has_params(
257 stringify!($variant),
258 0 $(+ one(concat!($(stringify!($field),)+)))?
259 )
260 )*
261 };
262 }
263 };
264}
265
266enum_with_count! {
267 /// Storage keys used by CoreVM.
268 #[derive(Encode, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
269 #[non_exhaustive]
270 pub enum StorageKey {
271 /// Inner VM gas.
272 Gas,
273 /// The hash of this CoreVM instance.
274 ///
275 /// Computed with `corevm_engine::compute_state_hash`.
276 StateHash,
277 /// The last known metadata of a memory page with the specified page number.
278 ///
279 /// An instance of [`PageInfo`](crate::PageInfo).
280 PageInfo(PageNum),
281 /// The output data specification of the VM.
282 ///
283 /// An instance of [`VmSpec`](crate::VmSpec).
284 VmSpec,
285 /// The current video output mode.
286 ///
287 /// An instance of [`VideoMode`](crate::VideoMode).
288 VideoMode,
289 /// The current audio output mode.
290 ///
291 /// An instance of [`AudioMode`](crate::AudioMode).
292 AudioMode,
293 /// Execution environment location in the virtual file system.
294 ///
295 /// The environment ([`ExecEnv`]) itself is stored as a file.
296 ExecEnvRef,
297 /// The owner of this CoreVM instance; they can reset the code.
298 Owner,
299 /// The pages that are currently in the storage.
300 ///
301 /// Used internally by `accumulate` to verify `refine` output.
302 #[doc(hidden)]
303 StoredPages,
304 /// Incoming inter-service messages.
305 ///
306 /// An instance of `VecDeque<ServiceMessage>`.
307 IncomingServiceMessages,
308 /// Outgoing inter-service messages.
309 ///
310 /// An instance of [`MessageQueue<OutgoingServiceMessage>`](crate::MessageQueue).
311 OutgoingServiceMessages,
312 /// Outgoing inter-service message with the specified index.
313 OutgoingMessage(u64),
314 /// Incoming inter-service message.
315 IncomingMessage(ServiceMessage),
316 }
317}
318
319/// Memory page metadata.
320#[derive(Encode, Decode, Debug)]
321pub struct PageInfo {
322 /// The hash of the page segment.
323 pub hash: Hash,
324 /// Segment-root of the package exports where the page contents are stored.
325 pub exports_root: SegmentTreeRoot,
326 /// The index of the exported segment where the page contents are stored.
327 pub export_index: u16,
328}
329
330/// Same as [`CoreVmOutput`] but only includes the data necessary to continue program execution.
331#[derive(Encode, Decode, Debug)]
332pub struct VmSpec {
333 /// Segment-root of the package exports where the actual output is stored.
334 pub exports_root: SegmentTreeRoot,
335 /// The specification of the VM's output data.
336 pub output: VmOutput,
337 pub state: VmState,
338}
339
340/// Guest execution environment.
341#[derive(Encode, Decode, Clone, Debug)]
342pub struct ExecEnv {
343 /// Program location in the virtual file system.
344 pub program: fs::BlockRef,
345 /// Root directory location in the virtual file system.
346 pub root_dir: fs::BlockRef,
347 // TODO @ivan add current working directory
348 /// Command-line arguments.
349 pub args: Vec<Arg>,
350 /// Environment variables.
351 pub env: Vec<Arg>,
352 /// Video input mode.
353 pub video_input: Option<VideoMode>,
354 /// Audio input mode.
355 pub audio_input: Option<AudioMode>,
356 /// Ed25519 public key that is used to verify input chunks' signatures.
357 pub input_key: Option<[u8; 32]>,
358}
359
360/// Instruction to send to the CoreVM service.
361///
362/// This should fit into [`Memo`](jam_types::Memo) in the encoded form.
363#[derive(Encode, Decode, Debug)]
364pub enum CoreVmInstruction {
365 /// Reset the VM.
366 Reset {
367 /// Inner VM gas.
368 gas: SignedGas,
369 /// Reference to a file where the environment ([`ExecEnv`]) is stored.
370 exec_ref: fs::BlockRef,
371 },
372 /// Set the owner of this CoreVM instance.
373 SetOwner(ServiceId),
374 /// Push a message from another service to the current service's message queue.
375 PushServiceMessage(ServiceMessage),
376 /// Destroy the VM.
377 ///
378 /// This instruction clears the storage and zombifies the service.
379 Destroy {
380 /// A service that should eject the CoreVM service after it is destroyed.
381 ejector: ServiceId,
382 },
383}
384
385impl MaxEncodedLen for CoreVmInstruction {
386 fn max_encoded_len() -> usize {
387 MEMO_LEN
388 }
389}
390
391/// The state of the Linux kernel syscall layer.
392#[derive(Encode, Decode, Clone, Debug, Default)]
393pub struct KernelState {
394 /// Opened file descriptors.
395 pub fds: VecMap<u32, KernelFd>,
396}
397
398/// In-kernel file descriptor state.
399#[derive(Encode, Decode, MaxEncodedLen, Clone, Debug)]
400pub struct KernelFd {
401 /// File reference in the virtual FS.
402 pub block_ref: fs::BlockRef,
403 /// File read/write offset.
404 pub position: u64,
405}
406
407impl ConstEncodedLen for KernelFd {}