Skip to main content

jar_kernel/
apply.rs

1//! Block apply driver.
2//!
3//! For each event in a block:
4//! 1. Publish the event's `payload` as a `Cap::Data` blob in σ's
5//!    cache via `put_cap(&Cap::data_inline(...))`.
6//! 2. CoW-promote the chain's root cnode and rebind slot\[0\] to the
7//!    payload's CapHash; settle to a fresh cnode hash.
8//! 3. Republish the chain InstanceCap with the new root cnode hash;
9//!    the chain instance's hash updates monotonically per event.
10//! 4. Drive `Vm::invoke_cached(&mut σ.caps, chain_instance_hash, ...)`.
11//! 5. Translate `CallResult` → `EventOutcome`. Post-HALT, the call's
12//!    `post_instance_hash` becomes the new `chain_instance_hash`.
13
14use std::sync::Arc;
15
16use javm::{InProcessKernelAssist, Vm};
17use javm_cap::{Cap, CapHash, CapHashOrRef, SlotIdx};
18use javm_exec::ExitReason;
19
20use crate::error::KernelError;
21use crate::state::State;
22
23/// One on-chain event in a block.
24pub struct Event {
25    /// Chain Image endpoint to invoke.
26    pub endpoint_idx: u8,
27    /// Raw payload bytes; delivered as a `Cap::Data` at the chain
28    /// Instance's slot\[0\] before the call.
29    pub payload: Vec<u8>,
30}
31
32/// A block: an ordered sequence of events the chain applies in
33/// turn.
34pub struct Block {
35    pub events: Vec<Event>,
36}
37
38/// Outcome of one event apply.
39#[derive(Debug)]
40pub enum EventOutcome {
41    Halt { return_value: u64, gas_used: u64 },
42    Faulted { reason: String, gas_used: u64 },
43    Paused { gas_used: u64 },
44}
45
46/// Apply one event against the chain Instance. Mutates `state.caps`
47/// and advances `chain_instance_hash` to a fresh value reflecting the
48/// post-call state.
49///
50/// The Vm is borrowed mutably for the duration of this call — the
51/// caller is expected to maintain a long-lived Vm across blocks (the
52/// image cache lives there).
53pub fn apply_event(
54    state: &mut State,
55    vm: &mut Vm<InProcessKernelAssist>,
56    chain_instance_hash: &mut CapHash,
57    event: &Event,
58    gas_budget: u64,
59    _storage_quota: u64,
60) -> Result<EventOutcome, KernelError> {
61    // 1. Publish the event payload as a DataCap.
62    let payload_hash = state.caps.put_cap(&Cap::data_inline(&event.payload))?;
63
64    // 2. Snapshot the chain instance's identifying fields (image hash
65    //    chain, image hash, current cnode hash) plus its memory layout
66    //    (mem_size, rw_overlays). We rebuild a new instance below
67    //    referencing the new cnode hash + the same memory layout.
68    let (image_hash_chain, image_hash, root_cnode_hash, mem_size, overlay_bufs, regs, pc) = {
69        let inst_cap = state
70            .caps
71            .get(CapHashOrRef::Hash(*chain_instance_hash))
72            .ok_or(KernelError::Invariant(
73                "apply_event: chain instance missing in cache",
74            ))?;
75        match &*inst_cap {
76            Cap::Instance(inst) => {
77                let cnode_hash = match &inst.root_cnode {
78                    CapHashOrRef::Hash(h) => *h,
79                    CapHashOrRef::Ref(_) => {
80                        return Err(KernelError::Invariant(
81                            "apply_event: chain instance root_cnode unsettled",
82                        ));
83                    }
84                };
85                let overlays: Vec<(u32, Vec<u8>)> = inst
86                    .rw_overlays
87                    .iter()
88                    .map(|o| (o.start, o.bytes.to_vec()))
89                    .collect();
90                (
91                    inst.image_hash_chain,
92                    inst.image_hash,
93                    cnode_hash,
94                    inst.mem_size,
95                    overlays,
96                    inst.regs,
97                    inst.pc,
98                )
99            }
100            _ => {
101                return Err(KernelError::Invariant(
102                    "apply_event: chain instance hash does not resolve to Cap::Instance",
103                ));
104            }
105        }
106    };
107
108    // CoW-promote the cnode: lazy clone via Arc::clone, then mutate
109    // through Arc::make_mut. The blob entry stays put; the new
110    // instance entry holds an Arc that's cloned-on-mutate.
111    let working_cnode_ref = state
112        .caps
113        .promote_blob_to_instance(&root_cnode_hash)
114        .ok_or(KernelError::Invariant(
115            "apply_event: chain root cnode not in blobs",
116        ))?;
117    let mut cnode_arc =
118        state
119            .caps
120            .get_instance(&working_cnode_ref)
121            .ok_or(KernelError::Invariant(
122                "apply_event: promoted cnode missing in instances tier",
123            ))?;
124    {
125        let cnode_mut = match Arc::make_mut(&mut cnode_arc) {
126            Cap::CNode(cn) => cn,
127            _ => {
128                return Err(KernelError::Invariant(
129                    "apply_event: chain root cnode is not Cap::CNode",
130                ));
131            }
132        };
133        cnode_mut.set(SlotIdx(0), Some(CapHashOrRef::Hash(payload_hash)))?;
134    }
135    state.caps.set_instance(&working_cnode_ref, cnode_arc)?;
136
137    // Settle the cnode (graduates the entry back into blobs at its new
138    // content hash).
139    let new_root_cnode_hash = state.caps.settle(CapHashOrRef::Ref(working_cnode_ref))?;
140
141    // 3. Republish the chain Instance referencing the new cnode and
142    //    the preserved memory layout / regs / PC.
143    let overlay_slices: Vec<(u32, &[u8])> = overlay_bufs
144        .iter()
145        .map(|(s, b)| (*s, b.as_slice()))
146        .collect();
147    let new_chain_instance_hash = state.caps.put_cap(&Cap::instance_with_overlays(
148        image_hash_chain,
149        image_hash,
150        new_root_cnode_hash,
151        &overlay_slices,
152        mem_size,
153        regs,
154        pc,
155        0,
156    ))?;
157
158    // 4. Drive the Vm.
159    let result = vm.invoke_cached(
160        &mut state.caps,
161        new_chain_instance_hash,
162        event.endpoint_idx,
163        [0u64; 4],
164        gas_budget,
165    )?;
166
167    // 5. Translate to EventOutcome and update chain_instance_hash.
168    Ok(match result {
169        javm::CallResult::Halt {
170            return_value,
171            post_instance_hash,
172            gas_used,
173            ..
174        } => {
175            *chain_instance_hash = post_instance_hash;
176            EventOutcome::Halt {
177                return_value,
178                gas_used,
179            }
180        }
181        javm::CallResult::Faulted {
182            reason, gas_used, ..
183        } => {
184            // Faulted events leave the chain instance at the pre-call
185            // hash (we still record the slot[0] payload bump, so the
186            // instance hash post-fault is the one we just published).
187            *chain_instance_hash = new_chain_instance_hash;
188            EventOutcome::Faulted {
189                reason: format_fault(reason),
190                gas_used,
191            }
192        }
193        javm::CallResult::Paused { gas_used, .. } => {
194            *chain_instance_hash = new_chain_instance_hash;
195            EventOutcome::Paused { gas_used }
196        }
197    })
198}
199
200fn format_fault(reason: ExitReason) -> String {
201    match reason {
202        ExitReason::Trap => "trap".into(),
203        ExitReason::Panic => "panic".into(),
204        ExitReason::OutOfGas => "out-of-gas".into(),
205        ExitReason::PageFault(addr) => format!("page-fault@{addr:#x}"),
206        ExitReason::HostCall(idx) => format!("host-call:{idx}"),
207        ExitReason::Ecall => "ecall".into(),
208        ExitReason::Halt => "halt".into(),
209    }
210}
211
212/// Apply a whole block.
213pub fn apply_block(
214    state: &mut State,
215    vm: &mut Vm<InProcessKernelAssist>,
216    chain_instance_hash: &mut CapHash,
217    block: &Block,
218    gas_per_event: u64,
219    quota_per_event: u64,
220) -> Result<Vec<EventOutcome>, KernelError> {
221    let mut outcomes = Vec::with_capacity(block.events.len());
222    for event in &block.events {
223        outcomes.push(apply_event(
224            state,
225            vm,
226            chain_instance_hash,
227            event,
228            gas_per_event,
229            quota_per_event,
230        )?);
231    }
232    Ok(outcomes)
233}