Skip to main content

jar_kernel/
kernel.rs

1//! Top-level `Kernel` API.
2//!
3//! Wraps σ, the chain instance hash, and a long-lived Vm into a
4//! single handle. Consumers (tests, the simple-chain end-to-end demo)
5//! call `Kernel::from_genesis` + `Kernel::apply` and observe state
6//! via `Kernel::state` / `Kernel::state_root`.
7
8use javm::{InProcessKernelAssist, Vm};
9use javm_cap::CapHash;
10use javm_cap::image::Image;
11
12use crate::apply::{Block, EventOutcome, apply_block};
13use crate::error::KernelError;
14use crate::genesis::{Genesis, genesis};
15use crate::state::{State, state_root};
16
17/// A v3 chain instance: σ + the chain instance hash + a long-lived
18/// Vm (so the image cache survives across events).
19pub struct Kernel {
20    state: State,
21    chain_instance_hash: CapHash,
22    vm: Vm<InProcessKernelAssist>,
23}
24
25impl Kernel {
26    /// Bootstrap from a chain Image.
27    pub fn from_genesis(chain_image: Image) -> Self {
28        let Genesis {
29            state,
30            chain_instance_hash,
31            chain_image_hash: _,
32            root_cnode_hash: _,
33        } = genesis(chain_image).expect("genesis must succeed for a valid chain image");
34
35        Self {
36            state,
37            chain_instance_hash,
38            vm: Vm::new(InProcessKernelAssist::new()),
39        }
40    }
41
42    /// Apply a block. Returns per-event outcomes; the post-block
43    /// state-root is available via [`Kernel::state_root`].
44    pub fn apply(
45        &mut self,
46        block: &Block,
47        gas_per_event: u64,
48        quota_per_event: u64,
49    ) -> Result<Vec<EventOutcome>, KernelError> {
50        apply_block(
51            &mut self.state,
52            &mut self.vm,
53            &mut self.chain_instance_hash,
54            block,
55            gas_per_event,
56            quota_per_event,
57        )
58    }
59
60    /// Read-only σ.
61    pub fn state(&self) -> &State {
62        &self.state
63    }
64
65    /// Current state-root.
66    pub fn state_root(&self) -> CapHash {
67        state_root(&self.state)
68    }
69
70    /// Current chain instance hash (advances monotonically as events
71    /// land).
72    pub fn chain_instance_hash(&self) -> CapHash {
73        self.chain_instance_hash
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80    use crate::abi;
81    use crate::apply::{Block, Event, EventOutcome};
82    use javm_cap::image::Image;
83    use std::collections::BTreeMap;
84
85    fn minimal_chain_image() -> Image {
86        // Program: load_imm_64 φ[7] = 42; ecalli 0 (HALT).
87        let mut endpoints = BTreeMap::new();
88        endpoints.insert(
89            0,
90            javm_cap::image::EndpointDef {
91                entry_pc: 0,
92                arg_registers: 0,
93                arg_cnode_size: 0,
94                initial_regs: BTreeMap::new(),
95            },
96        );
97        // Instruction starts at bytes 0 (load_imm_64) and 10 (ecalli).
98        // Packed bitmask (LSB-first): byte 0 = bit 0 set = 0x01;
99        // byte 1 = bit 2 set = 0x04.
100        Image {
101            code: vec![20u8, 7, 42, 0, 0, 0, 0, 0, 0, 0, 10, 0],
102            packed_bitmask: vec![0x01, 0x04],
103            jump_table: Vec::new(),
104            endpoints,
105            memory_mappings: Vec::new(),
106            gas_slots: vec![abi::BARE_GAS_SLOT],
107            quota_slots: vec![abi::BARE_QUOTA_SLOT],
108            pinned_slots: BTreeMap::new(),
109            initial_slots: BTreeMap::new(),
110            yield_marker_slot: Some(abi::BARE_YIELD_CATCHER_SLOT),
111        }
112    }
113
114    #[test]
115    fn kernel_from_genesis_yields_deterministic_state_root() {
116        let k1 = Kernel::from_genesis(minimal_chain_image());
117        let k2 = Kernel::from_genesis(minimal_chain_image());
118        assert_eq!(k1.state_root(), k2.state_root());
119    }
120
121    #[test]
122    fn kernel_apply_advances_state_root_via_payload_publish() {
123        // The minimal_chain_image program halts with 42 (or traps,
124        // depending on bytecode validity). Regardless of the exit
125        // status, the event payload gets published as a DataCap in σ
126        // before the call — that publish alone changes state_root.
127        let mut kernel = Kernel::from_genesis(minimal_chain_image());
128        let root_0 = kernel.state_root();
129
130        let block = Block {
131            events: vec![Event {
132                endpoint_idx: 0,
133                payload: b"hello".to_vec(),
134            }],
135        };
136        let outcomes = kernel.apply(&block, 10_000, 10_000).unwrap();
137        let root_1 = kernel.state_root();
138
139        assert_ne!(root_0, root_1);
140        assert_eq!(outcomes.len(), 1);
141    }
142
143    #[test]
144    fn kernel_apply_replay_is_deterministic() {
145        // Same chain image, same block → same post-apply root.
146        let mut k1 = Kernel::from_genesis(minimal_chain_image());
147        let mut k2 = Kernel::from_genesis(minimal_chain_image());
148        let block = || Block {
149            events: vec![Event {
150                endpoint_idx: 0,
151                payload: b"replay-determinism".to_vec(),
152            }],
153        };
154        let _ = k1.apply(&block(), 10_000, 10_000).unwrap();
155        let _ = k2.apply(&block(), 10_000, 10_000).unwrap();
156        assert_eq!(k1.state_root(), k2.state_root());
157    }
158
159    // Touch the EventOutcome enum so the import isn't unused if the
160    // tests above don't pattern-match it.
161    #[allow(dead_code)]
162    fn _enum_touch(o: EventOutcome) -> EventOutcome {
163        o
164    }
165}