Skip to main content

jar_kernel/
kernel_assist.rs

1//! σ-aware [`javm::KernelAssist`] implementation.
2//!
3//! After the javm-cap consolidation (Commits 1-3 of the cap-type
4//! plan) the kernel assist holds only the ephemeral per-block kernel
5//! state — gas meters, storage quotas, yield catchers, file_id ↔
6//! cache-reference mapping. Cap content lives in σ's cache and is
7//! looked up through there; this trait surface no longer reaches
8//! into σ.
9//!
10//! `Vm::invoke_cached` resolves DataCap sizes before calling into
11//! this trait, so `host_save` debits the actual logical byte size
12//! while this type remains free of direct cache ownership.
13
14use std::collections::HashMap;
15
16use javm::{KernelAssist, MeterId, QuotaId};
17use javm_cap::{Blake2b256, CapHash, CapHashOrRef, Hash};
18
19/// σ-aware KernelAssist. Owns only ephemeral per-block kernel state.
20pub struct SigmaKernelAssist {
21    /// Per-block ephemeral: gas meters reset at block start.
22    gas_meters: HashMap<MeterId, u64>,
23    /// Per-block ephemeral: storage quotas reset at block start.
24    storage_quotas: HashMap<QuotaId, u64>,
25    /// Per-block ephemeral: yield catcher marker lists.
26    yield_catchers: HashMap<CapHash, Vec<CapHash>>,
27    /// Monotonic nonce for `yield_catcher_new`.
28    next_yc_nonce: u64,
29    /// file_id → cache reference. host_save mints fresh ids, host_open
30    /// reads through this map.
31    files: HashMap<u64, CapHashOrRef>,
32    next_file_id: u64,
33}
34
35impl SigmaKernelAssist {
36    pub fn new() -> Self {
37        Self {
38            gas_meters: HashMap::new(),
39            storage_quotas: HashMap::new(),
40            yield_catchers: HashMap::new(),
41            next_yc_nonce: 0,
42            files: HashMap::new(),
43            next_file_id: 1,
44        }
45    }
46
47    /// Reset per-block ephemeral tables. Called at the start of each
48    /// block apply.
49    pub fn reset_block_state(&mut self) {
50        self.gas_meters.clear();
51        self.storage_quotas.clear();
52        self.yield_catchers.clear();
53    }
54
55    /// Seed the root gas meter (meter_id 0) with the chain's
56    /// block-wide gas budget.
57    pub fn seed_root_gas(&mut self, budget: u64) {
58        self.gas_meters.insert(0, budget);
59    }
60
61    /// Seed the root storage quota (quota_id 0).
62    pub fn seed_root_quota(&mut self, budget: u64) {
63        self.storage_quotas.insert(0, budget);
64    }
65}
66
67impl Default for SigmaKernelAssist {
68    fn default() -> Self {
69        Self::new()
70    }
71}
72
73impl KernelAssist for SigmaKernelAssist {
74    // ---- Gas ----
75    fn gas_meter_get(&self, meter_id: MeterId) -> u64 {
76        self.gas_meters.get(&meter_id).copied().unwrap_or(0)
77    }
78    fn gas_meter_set(&mut self, meter_id: MeterId, value: u64) -> u64 {
79        self.gas_meters.insert(meter_id, value).unwrap_or(0)
80    }
81
82    // ---- Quota ----
83    fn storage_quota_get(&self, quota_id: QuotaId) -> u64 {
84        self.storage_quotas.get(&quota_id).copied().unwrap_or(0)
85    }
86    fn storage_quota_set(&mut self, quota_id: QuotaId, value: u64) -> u64 {
87        self.storage_quotas.insert(quota_id, value).unwrap_or(0)
88    }
89
90    // ---- YieldCatcher ----
91    fn yield_catcher_markers(&self, catcher_hash: CapHash) -> Vec<CapHash> {
92        self.yield_catchers
93            .get(&catcher_hash)
94            .cloned()
95            .unwrap_or_default()
96    }
97    fn yield_catcher_add(&mut self, catcher_hash: CapHash, marker_instance_hash: CapHash) {
98        let entry = self.yield_catchers.entry(catcher_hash).or_default();
99        if !entry.contains(&marker_instance_hash) {
100            entry.push(marker_instance_hash);
101        }
102    }
103    fn yield_catcher_remove(&mut self, catcher_hash: CapHash, marker_instance_hash: CapHash) {
104        if let Some(entry) = self.yield_catchers.get_mut(&catcher_hash) {
105            entry.retain(|m| *m != marker_instance_hash);
106        }
107    }
108    fn yield_catcher_new(&mut self) -> CapHash {
109        let nonce = self.next_yc_nonce;
110        self.next_yc_nonce += 1;
111        let hash = Blake2b256::hash(&nonce.to_le_bytes());
112        self.yield_catchers.insert(hash, Vec::new());
113        hash
114    }
115
116    // ---- File registry ----
117    fn host_open(&mut self, file_id: u64) -> Option<CapHashOrRef> {
118        self.files.get(&file_id).cloned()
119    }
120
121    fn host_save(&mut self, data: CapHashOrRef, quota_id: u64, size: u64) -> Option<u64> {
122        let current = self.storage_quotas.get(&quota_id).copied().unwrap_or(0);
123        if current < size {
124            return None;
125        }
126        self.storage_quotas.insert(quota_id, current - size);
127        let file_id = self.next_file_id;
128        self.next_file_id += 1;
129        self.files.insert(file_id, data);
130        Some(file_id)
131    }
132}
133
134#[cfg(test)]
135mod tests {
136    use super::*;
137
138    #[test]
139    fn host_save_debits_quota_and_allocates_file_id() {
140        let mut ka = SigmaKernelAssist::new();
141        ka.seed_root_quota(1000);
142        let file_id = ka.host_save(CapHashOrRef::Hash([0u8; 32]), 0, 32).unwrap();
143        assert_eq!(file_id, 1); // first allocation
144        assert_eq!(ka.storage_quota_get(0), 968);
145        assert_eq!(ka.host_open(file_id), Some(CapHashOrRef::Hash([0u8; 32])));
146    }
147
148    #[test]
149    fn host_save_exhausted_quota_returns_none() {
150        let mut ka = SigmaKernelAssist::new();
151        // Quota 0 starts empty; any save should fail.
152        assert!(ka.host_save(CapHashOrRef::Hash([0u8; 32]), 0, 1).is_none());
153    }
154
155    #[test]
156    fn reset_block_state_clears_ephemeral_tables() {
157        let mut ka = SigmaKernelAssist::new();
158        ka.seed_root_gas(1000);
159        ka.seed_root_quota(2000);
160        ka.reset_block_state();
161        assert_eq!(ka.gas_meter_get(0), 0);
162        assert_eq!(ka.storage_quota_get(0), 0);
163    }
164
165    #[test]
166    fn yield_catcher_round_trip() {
167        let mut ka = SigmaKernelAssist::new();
168        let yc = ka.yield_catcher_new();
169        let marker = [0xAA; 32];
170        ka.yield_catcher_add(yc, marker);
171        assert_eq!(ka.yield_catcher_markers(yc), vec![marker]);
172        ka.yield_catcher_remove(yc, marker);
173        assert!(ka.yield_catcher_markers(yc).is_empty());
174    }
175}