Skip to main content

jar_kernel/
genesis.rs

1//! Chain genesis: σ initialization + kernel-cap injection.
2//!
3//! `genesis` takes a chain `Image` and publishes into σ's cache:
4//! - The chain Image (as a `Cap::Image` blob).
5//! - The kernel-issued unit caps at well-known slots
6//!   (Gas{0}, Quota{0}, YieldCatcher, factories, host entries).
7//! - The chain's root cnode binding those caps + the image's pinned /
8//!   initial slot data caps.
9//! - The chain `Cap::Instance` referencing the chain image and root
10//!   cnode.
11//!
12//! Per the v3 spec's "kernel-issued caps", each unit cap encodes its
13//! identity (meter id, quota id) in `regs[0]` — InstanceCap no longer
14//! has a free-standing `content_hash` field, so identity rides on
15//! observable state. Kernel caps are immutable by convention
16//! (userspace never invokes them via PVM), so `regs[0]` is stable.
17
18use javm::{KernelImage, kernel_image_hash};
19use javm_cap::abi as cap_abi;
20use javm_cap::image::Image;
21use javm_cap::{CNodeCap, CacheDirectory, Cap, CapHash, CapHashOrRef, NUM_REGS, SlotIdx};
22
23use crate::abi;
24use crate::error::KernelError;
25use crate::state::State;
26
27/// Output of `genesis`: the initial σ together with hashes identifying
28/// the chain Image, root cnode, and chain Instance inside the cache.
29pub struct Genesis {
30    pub state: State,
31    pub chain_instance_hash: CapHash,
32    pub chain_image_hash: CapHash,
33    pub root_cnode_hash: CapHash,
34}
35
36/// Mint and publish a kernel-issued unit cap into the cache. The
37/// kernel-image label drives `image_hash_chain`; `id` encodes the
38/// runtime identity (meter id, quota id, etc.) into `regs[0]`. Returns
39/// the published cap's hash. The placeholder image blob is shared
40/// across all kernel unit caps and resolved at publish time.
41fn publish_kernel_unit_cap(
42    cache: &mut CacheDirectory,
43    image: KernelImage,
44    placeholder_image_hash: CapHash,
45    empty_cnode_hash: CapHash,
46    id: u64,
47) -> Result<CapHash, KernelError> {
48    let mut regs = [0u64; NUM_REGS];
49    regs[0] = id;
50    let cap = Cap::instance_with_overlays(
51        kernel_image_hash(image),
52        placeholder_image_hash,
53        empty_cnode_hash,
54        &[],
55        0,
56        regs,
57        0,
58        0,
59    );
60    Ok(cache.put_cap(&cap)?)
61}
62
63/// Stateless variant: no runtime identity (`regs[0] = 0`). Used for
64/// factory and host-entry kernel caps where every chain Instance sees
65/// the same well-known cap.
66fn publish_kernel_stateless_cap(
67    cache: &mut CacheDirectory,
68    image: KernelImage,
69    placeholder_image_hash: CapHash,
70    empty_cnode_hash: CapHash,
71) -> Result<CapHash, KernelError> {
72    publish_kernel_unit_cap(cache, image, placeholder_image_hash, empty_cnode_hash, 0)
73}
74
75/// Construct chain genesis from a chain Image.
76///
77/// Publishes the chain image, the kernel-issued unit caps, the root
78/// cnode (256 slots, populated with kernel caps at `abi::BARE_*` slots
79/// plus pinned/initial slot data caps from the image), and the chain
80/// Instance into σ. Returns hashes for downstream callers.
81pub fn genesis(chain_image: Image) -> Result<Genesis, KernelError> {
82    let mut state = State::new();
83
84    // 1. Build Cap::Data for each pinned/initial slot in the chain
85    //    image; remember each slot's content hash so the Image can
86    //    reference them, and so the root cnode can bind to them later.
87    use javm_cap::image::PinnedCap;
88    let mut chain_pinned_hashes: Vec<(SlotIdx, CapHash)> = Vec::new();
89    let mut chain_initial_hashes: Vec<(SlotIdx, CapHash)> = Vec::new();
90    for (slot, pinned) in &chain_image.pinned_slots {
91        let h = match pinned {
92            PinnedCap::Data { content, size } => state
93                .caps
94                .put_cap(&Cap::data_inline_with_size(content, *size))?,
95            PinnedCap::Image { content_hash } => *content_hash,
96        };
97        chain_pinned_hashes.push((*slot, h));
98    }
99    for (slot, init) in &chain_image.initial_slots {
100        let h = state
101            .caps
102            .put_cap(&Cap::data_inline_with_size(&init.content, init.size))?;
103        chain_initial_hashes.push((*slot, h));
104    }
105
106    // 2. Publish the chain Image referencing the slot data by hash.
107    let chain_image_hash = state.caps.put_cap(&Cap::image_with_slots(
108        &chain_image,
109        &chain_pinned_hashes,
110        &chain_initial_hashes,
111    )?)?;
112
113    // 3. A shared placeholder Image cap referenced by all kernel-issued
114    //    Instance caps. Using a tiny but well-formed Image (1 byte of
115    //    code, single instruction-start bit set) sidesteps the image-cap
116    //    validation while keeping kernel caps content-hashable in a
117    //    stable way. The same hash is reused for every kernel unit.
118    let placeholder_image_hash = state.caps.put_cap(&Cap::image_with_slots(
119        &placeholder_kernel_image(),
120        &[],
121        &[],
122    )?)?;
123
124    // 4. A shared empty cnode for kernel-issued Instance caps. They
125    //    never invoke any of their own slots; the empty cnode keeps
126    //    them well-formed.
127    let empty_cnode_hash = state.caps.put_cap(&Cap::empty_cnode(0)?)?;
128
129    // 4. Publish each kernel-issued unit cap.
130    let gas_hash = publish_kernel_unit_cap(
131        &mut state.caps,
132        KernelImage::Gas,
133        placeholder_image_hash,
134        empty_cnode_hash,
135        0,
136    )?;
137    let quota_hash = publish_kernel_unit_cap(
138        &mut state.caps,
139        KernelImage::Quota,
140        placeholder_image_hash,
141        empty_cnode_hash,
142        0,
143    )?;
144    let yield_catcher_hash = publish_kernel_stateless_cap(
145        &mut state.caps,
146        KernelImage::YieldCatcher,
147        placeholder_image_hash,
148        empty_cnode_hash,
149    )?;
150    let set_gas_meter_hash = publish_kernel_stateless_cap(
151        &mut state.caps,
152        KernelImage::SetGasMeter,
153        placeholder_image_hash,
154        empty_cnode_hash,
155    )?;
156    let set_storage_quota_hash = publish_kernel_stateless_cap(
157        &mut state.caps,
158        KernelImage::SetStorageQuota,
159        placeholder_image_hash,
160        empty_cnode_hash,
161    )?;
162    let mint_gas_hash = publish_kernel_stateless_cap(
163        &mut state.caps,
164        KernelImage::MintGas,
165        placeholder_image_hash,
166        empty_cnode_hash,
167    )?;
168    let mint_quota_hash = publish_kernel_stateless_cap(
169        &mut state.caps,
170        KernelImage::MintQuota,
171        placeholder_image_hash,
172        empty_cnode_hash,
173    )?;
174    let create_yc_hash = publish_kernel_stateless_cap(
175        &mut state.caps,
176        KernelImage::CreateYieldCatcher,
177        placeholder_image_hash,
178        empty_cnode_hash,
179    )?;
180    let host_open_hash = publish_kernel_stateless_cap(
181        &mut state.caps,
182        KernelImage::HostOpen,
183        placeholder_image_hash,
184        empty_cnode_hash,
185    )?;
186    let host_save_hash = publish_kernel_stateless_cap(
187        &mut state.caps,
188        KernelImage::HostSave,
189        placeholder_image_hash,
190        empty_cnode_hash,
191    )?;
192
193    // 5. Build the chain's root cnode entries. Kernel caps go at the
194    //    well-known abi::BARE_* slots; pinned/initial slot data caps
195    //    are republished alongside (they were also republished by
196    //    chain image step above, but the cnode references them by hash
197    //    so we just locate the hashes).
198    let mut entries: Vec<(SlotIdx, CapHashOrRef)> = vec![
199        (abi::BARE_GAS_SLOT, CapHashOrRef::Hash(gas_hash)),
200        (abi::BARE_QUOTA_SLOT, CapHashOrRef::Hash(quota_hash)),
201        (
202            abi::BARE_YIELD_CATCHER_SLOT,
203            CapHashOrRef::Hash(yield_catcher_hash),
204        ),
205        (
206            abi::BARE_SET_GAS_METER_SLOT,
207            CapHashOrRef::Hash(set_gas_meter_hash),
208        ),
209        (
210            abi::BARE_SET_STORAGE_QUOTA_SLOT,
211            CapHashOrRef::Hash(set_storage_quota_hash),
212        ),
213        (abi::BARE_MINT_GAS_SLOT, CapHashOrRef::Hash(mint_gas_hash)),
214        (
215            abi::BARE_MINT_QUOTA_SLOT,
216            CapHashOrRef::Hash(mint_quota_hash),
217        ),
218        (
219            abi::BARE_CREATE_YIELD_CATCHER_SLOT,
220            CapHashOrRef::Hash(create_yc_hash),
221        ),
222        (abi::BARE_HOST_OPEN_SLOT, CapHashOrRef::Hash(host_open_hash)),
223        (abi::BARE_HOST_SAVE_SLOT, CapHashOrRef::Hash(host_save_hash)),
224    ];
225
226    // Pinned + initial slots: the hashes were already recorded above
227    // (step 1) when we built the Cap::Data blobs. Reuse them directly.
228    for (slot, h) in &chain_pinned_hashes {
229        entries.push((*slot, CapHashOrRef::Hash(*h)));
230    }
231    for (slot, h) in &chain_initial_hashes {
232        entries.push((*slot, CapHashOrRef::Hash(*h)));
233    }
234
235    // 6. Publish the root cnode (256 slots, size_log = 8).
236    let root_cnode_hash = {
237        let mut cnode = CNodeCap::new(8).map_err(KernelError::from)?;
238        for (slot, target) in &entries {
239            cnode
240                .set(*slot, Some(target.clone()))
241                .map_err(KernelError::from)?;
242        }
243        state.caps.put_cap(&Cap::CNode(cnode))?
244    };
245
246    // 7. Compute the chain Instance's memory layout from the image's
247    //    memory mappings. Mirrors the recomp path's build_overlays:
248    //    mem_size = max(start + size); rw_overlays come from the
249    //    image's pinned/initial slot contents for each mapping.
250    let (mem_size, overlays) = build_overlays(&chain_image);
251    let overlay_slices: Vec<(u32, &[u8])> = overlays
252        .iter()
253        .map(|(start, bytes)| (*start, bytes.as_slice()))
254        .collect();
255
256    // 8. Publish the chain Instance. `image_hash_chain` mirrors the
257    //    image's content hash directly at genesis (no prior chain).
258    //    `regs` start at zeros (chain doesn't have a unit-id; events
259    //    drive it via cnode slot[0]).
260    let chain_instance_hash = state.caps.put_cap(&Cap::instance_with_overlays(
261        chain_image_hash,
262        chain_image_hash,
263        root_cnode_hash,
264        &overlay_slices,
265        mem_size,
266        [0u64; NUM_REGS],
267        0,
268        0,
269    ))?;
270
271    let _ = cap_abi::BARE_GAS_SLOT; // keep the abi re-export pinned
272
273    Ok(Genesis {
274        state,
275        chain_instance_hash,
276        chain_image_hash,
277        root_cnode_hash,
278    })
279}
280
281/// Build memory overlays from the chain image's memory_mappings.
282/// Mirrors `javm-guest-tests::conformance::build_overlays`: for each
283/// mapping, look up the pinned or initial slot content at the
284/// mapping's target slot; record an overlay tuple if content is
285/// non-empty.
286///
287/// Returns `(mem_size, overlays)` where `mem_size = max(start+size)`
288/// over all mappings.
289pub(crate) fn build_overlays(image: &Image) -> (u32, Vec<(u32, Vec<u8>)>) {
290    use javm_cap::image::PinnedCap;
291    let mut mem_size: u32 = 0;
292    let mut overlays: Vec<(u32, Vec<u8>)> = Vec::new();
293
294    for mapping in &image.memory_mappings {
295        let end = (mapping.start + mapping.size) as u32;
296        if end > mem_size {
297            mem_size = end;
298        }
299
300        let target = mapping.source.target();
301        if let Some(PinnedCap::Data { content, .. }) = image.pinned_slots.get(&target) {
302            if !content.is_empty() {
303                overlays.push((mapping.start as u32, content.clone()));
304            }
305        } else if let Some(init) = image.initial_slots.get(&target)
306            && !init.content.is_empty()
307        {
308            overlays.push((mapping.start as u32, init.content.clone()));
309        }
310    }
311
312    (mem_size, overlays)
313}
314
315/// A minimal well-formed Image used as a placeholder for kernel-
316/// issued unit caps. The image is never actually invoked — kernel
317/// caps short-circuit at the host-call layer.
318fn placeholder_kernel_image() -> Image {
319    use std::collections::BTreeMap;
320    Image {
321        code: vec![0u8], // single TRAP byte
322        packed_bitmask: vec![0x01],
323        jump_table: Vec::new(),
324        endpoints: BTreeMap::new(),
325        memory_mappings: Vec::new(),
326        gas_slots: Vec::new(),
327        quota_slots: Vec::new(),
328        pinned_slots: BTreeMap::new(),
329        initial_slots: BTreeMap::new(),
330        yield_marker_slot: None,
331    }
332}
333
334#[cfg(test)]
335mod tests {
336    use super::*;
337    use javm_cap::Cap;
338
339    fn empty_chain_image() -> Image {
340        use std::collections::BTreeMap;
341        Image {
342            code: vec![10u8, 0],
343            // 2 bytes of code → 2 instruction-start bits → 0b0000_0011.
344            packed_bitmask: vec![0x03],
345            jump_table: Vec::new(),
346            endpoints: BTreeMap::new(),
347            memory_mappings: Vec::new(),
348            gas_slots: vec![abi::BARE_GAS_SLOT],
349            quota_slots: vec![abi::BARE_QUOTA_SLOT],
350            pinned_slots: BTreeMap::new(),
351            initial_slots: BTreeMap::new(),
352            yield_marker_slot: Some(abi::BARE_YIELD_CATCHER_SLOT),
353        }
354    }
355
356    #[test]
357    fn genesis_publishes_chain_instance() {
358        let g = genesis(empty_chain_image()).expect("genesis");
359        // Chain instance exists in cache.
360        let inst = g
361            .state
362            .caps
363            .get(CapHashOrRef::Hash(g.chain_instance_hash))
364            .expect("chain instance present");
365        assert!(matches!(&*inst, Cap::Instance(_)));
366    }
367
368    #[test]
369    fn genesis_populates_root_cnode_with_kernel_caps() {
370        let g = genesis(empty_chain_image()).expect("genesis");
371        let cn_arc = g
372            .state
373            .caps
374            .get(CapHashOrRef::Hash(g.root_cnode_hash))
375            .expect("root cnode present");
376        let cn = match &*cn_arc {
377            Cap::CNode(cn) => cn.clone(),
378            _ => panic!("root cnode is not Cap::CNode"),
379        };
380        assert!(cn.get(abi::BARE_GAS_SLOT).is_some());
381        assert!(cn.get(abi::BARE_QUOTA_SLOT).is_some());
382        assert!(cn.get(abi::BARE_YIELD_CATCHER_SLOT).is_some());
383        assert!(cn.get(abi::BARE_HOST_OPEN_SLOT).is_some());
384        assert!(cn.get(abi::BARE_HOST_SAVE_SLOT).is_some());
385    }
386
387    #[test]
388    fn genesis_is_deterministic() {
389        let g1 = genesis(empty_chain_image()).expect("g1");
390        let g2 = genesis(empty_chain_image()).expect("g2");
391        assert_eq!(g1.chain_instance_hash, g2.chain_instance_hash);
392        assert_eq!(g1.chain_image_hash, g2.chain_image_hash);
393        assert_eq!(g1.root_cnode_hash, g2.root_cnode_hash);
394    }
395}