Skip to main content

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 {}