Skip to main content

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}