nub_kernel/lib.rs
1//! Nub: the JAR v3 microkernel.
2//!
3//! This crate defines the [`Arch`] substrate trait and the generic
4//! [`Kernel`] over it. The kernel is a *general VM* that invokes JAVM
5//! programs (see `~/docs/minimum-v3`). It is **not** block-apply
6//! specific — block-apply is layered on top in a separate crate.
7//!
8//! ## Layering
9//!
10//! ```text
11//! callers (chain runtime / tests / RPC)
12//! │
13//! jar-apply (block-apply, gas, quota — separate crate, later)
14//! │
15//! javm (JAVM entrypoint: Javm personality, typed Cap
16//! │ surface, guest blob + Hyperlight singleton)
17//! │
18//! nub (generic Nub<P: Personality> handle over backends)
19//! │
20//! ┌────────────┼────────────────┐
21//! │ │
22//! nub-arch-local nub-arch-x86
23//! (in-process, (generic bare-metal guest-kernel lib;
24//! std) javm-guest-x86 supplies the personality
25//! │ + binaries, no_std + no_main)
26//! │ │
27//! └────────────┬────────────────┘
28//! │
29//! nub-kernel ← this crate
30//! (Arch trait, Kernel<A: Arch>, types)
31//! ```
32//!
33//! ## State
34//!
35//! The kernel "owns the state": the invoking `Cap::Instance` and
36//! everything reachable from it. Concretely the [`Arch`] impl holds
37//! the storage (in-process structures for `nub-arch-local`,
38//! guest-resident structures for `nub-arch-x86`); the
39//! [`Kernel`] is a thin generic wrapper that delegates to the Arch.
40//!
41//! ## `no_std`
42//!
43//! This crate is `no_std` by default with an optional `std` feature
44//! (currently enabled by default for ergonomics on host targets). The
45//! Hyperlight Arch impl will pull the no_std build path; in-process
46//! consumers use the std build.
47
48#![cfg_attr(not(feature = "std"), no_std)]
49
50/// 32-byte content hash of a published state object. The personality
51/// defines the hash function (JAVM: SSZ `hash_tree_root`); nub treats
52/// the value as an opaque content-addressed key.
53pub type ObjHash = [u8; 32];
54
55/// Legacy alias for [`ObjHash`] from when the only personality was the
56/// JAVM capability system. Prefer [`ObjHash`] in new code.
57pub type CapHash = ObjHash;
58
59/// Opaque, 32-byte handle to an Instance held by an `Arch`.
60///
61/// The Arch chooses how to interpret it. For the skeleton both
62/// backends use the cap content hash directly, so [`InstanceRef`] is
63/// effectively a [`CapHash`] wrapper. If a backend later wants a
64/// guest-internal handle (e.g. an index into a guest-side table for
65/// cheap lookup), the natural follow-up is to make this an associated
66/// type on [`Arch`].
67#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
68pub struct InstanceRef(pub [u8; 32]);
69
70impl InstanceRef {
71 pub const fn from_hash(hash: CapHash) -> Self {
72 Self(hash)
73 }
74
75 pub const fn as_bytes(&self) -> &[u8; 32] {
76 &self.0
77 }
78}
79
80/// Per-invocation knobs. Empty for the skeleton; fields will land as
81/// the kernel grows (gas budget overrides, quota budget, tracing,
82/// reentrancy depth limits, …).
83#[derive(Copy, Clone, Default, Debug)]
84pub struct InvokeOptions {}
85
86/// Result of a successful invocation. Extensible — fields land as
87/// needed (gas remaining, post-invocation cap hash, host-call trace,
88/// …). For the skeleton we expose only the JAVM HALT return value
89/// and gas used.
90#[derive(Copy, Clone, Debug)]
91pub struct InvokeOutcome {
92 pub return_value: u64,
93 pub gas_used: u64,
94}
95
96/// Low-level CPU/MMU substrate trait. An `Arch` impl runs in the same
97/// address space as the [`Kernel`] that calls it — it owns the
98/// kernel's state and provides the primitives (page mapping, ring
99/// transitions, exception handling, …) needed to execute JAVM
100/// programs. The skeleton trait only exposes [`invoke`](Arch::invoke)
101/// and [`state_root`](Arch::state_root); the substrate-specific
102/// primitives that the kernel will eventually drive (map_pages,
103/// install_handler, …) are intentionally not part of the public
104/// surface yet — they're encapsulated inside [`Arch::invoke`] for
105/// now.
106pub trait Arch {
107 type Error;
108
109 /// Invoke `endpoint` on the `Cap::Instance` identified by
110 /// `target`, passing `args` (opaque caller-defined bytes). The
111 /// Arch impl is responsible for executing the underlying JAVM
112 /// program to termination (HALT / yield / fault / gas-exhausted)
113 /// and reporting the outcome.
114 fn invoke(
115 &mut self,
116 target: InstanceRef,
117 endpoint: u16,
118 args: &[u8],
119 opts: InvokeOptions,
120 ) -> Result<InvokeOutcome, Self::Error>;
121
122 /// Content-addressed root of the Arch's current state — the hash
123 /// of the invoking `Cap::Instance` after the most recent
124 /// invocation (or genesis if none).
125 fn state_root(&self) -> CapHash;
126}
127
128/// The kernel: a thin wrapper over an [`Arch`] impl that owns the
129/// state. `nub` is the microkernel that this represents; callers use
130/// it via the uniform `Nub` handle in the `nub` crate, which selects
131/// the backend (local interpreter vs hyperlight RPC) at construction
132/// time.
133pub struct Kernel<A: Arch> {
134 arch: A,
135}
136
137impl<A: Arch> Kernel<A> {
138 pub const fn new(arch: A) -> Self {
139 Self { arch }
140 }
141
142 pub fn invoke(
143 &mut self,
144 target: InstanceRef,
145 endpoint: u16,
146 args: &[u8],
147 opts: InvokeOptions,
148 ) -> Result<InvokeOutcome, A::Error> {
149 self.arch.invoke(target, endpoint, args, opts)
150 }
151
152 pub fn state_root(&self) -> CapHash {
153 self.arch.state_root()
154 }
155
156 pub fn arch(&self) -> &A {
157 &self.arch
158 }
159}