1use core::fmt;
18use std::collections::HashMap;
19
20use javm_cap::{Blake2b256, CapHash, CapHashOrRef, Hash};
21
22pub type MeterId = u64;
25
26pub type QuotaId = u64;
28
29pub trait KernelAssist {
41 fn gas_meter_get(&self, meter_id: MeterId) -> u64;
45
46 fn gas_meter_set(&mut self, meter_id: MeterId, value: u64) -> u64;
49
50 fn storage_quota_get(&self, quota_id: QuotaId) -> u64;
53 fn storage_quota_set(&mut self, quota_id: QuotaId, value: u64) -> u64;
54
55 fn yield_catcher_markers(&self, catcher_hash: CapHash) -> Vec<CapHash>;
61
62 fn yield_catcher_add(&mut self, catcher_hash: CapHash, marker_instance_hash: CapHash);
64
65 fn yield_catcher_remove(&mut self, catcher_hash: CapHash, marker_instance_hash: CapHash);
67
68 fn yield_catcher_new(&mut self) -> CapHash;
71
72 fn host_open(&mut self, _file_id: u64) -> Option<CapHashOrRef> {
85 None
86 }
87
88 fn host_save(&mut self, _data: CapHashOrRef, _quota_id: u64, _size: u64) -> Option<u64> {
92 None
93 }
94}
95
96pub struct InProcessKernelAssist {
105 gas_meters: HashMap<MeterId, u64>,
106 storage_quotas: HashMap<QuotaId, u64>,
107 yield_catchers: HashMap<CapHash, Vec<CapHash>>,
108 next_yc_nonce: u64,
112 files: HashMap<u64, CapHashOrRef>,
115 next_file_id: u64,
116}
117
118impl InProcessKernelAssist {
119 pub fn new() -> Self {
120 Self {
121 gas_meters: HashMap::new(),
122 storage_quotas: HashMap::new(),
123 yield_catchers: HashMap::new(),
124 next_yc_nonce: 0,
125 files: HashMap::new(),
126 next_file_id: 1,
127 }
128 }
129
130 pub fn reset_block_state(&mut self) {
134 self.gas_meters.clear();
135 self.storage_quotas.clear();
136 self.yield_catchers.clear();
137 }
141
142 pub fn register_file(&mut self, file_id: u64, data: CapHashOrRef) {
145 self.files.insert(file_id, data);
146 if file_id >= self.next_file_id {
147 self.next_file_id = file_id + 1;
148 }
149 }
150}
151
152impl Default for InProcessKernelAssist {
153 fn default() -> Self {
154 Self::new()
155 }
156}
157
158impl fmt::Debug for InProcessKernelAssist {
159 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
160 f.debug_struct("InProcessKernelAssist")
161 .field("gas_meters", &self.gas_meters.len())
162 .field("storage_quotas", &self.storage_quotas.len())
163 .field("yield_catchers", &self.yield_catchers.len())
164 .field("files", &self.files.len())
165 .finish()
166 }
167}
168
169impl KernelAssist for InProcessKernelAssist {
170 fn gas_meter_get(&self, meter_id: MeterId) -> u64 {
171 self.gas_meters.get(&meter_id).copied().unwrap_or(0)
172 }
173
174 fn gas_meter_set(&mut self, meter_id: MeterId, value: u64) -> u64 {
175 self.gas_meters.insert(meter_id, value).unwrap_or(0)
176 }
177
178 fn storage_quota_get(&self, quota_id: QuotaId) -> u64 {
179 self.storage_quotas.get("a_id).copied().unwrap_or(0)
180 }
181
182 fn storage_quota_set(&mut self, quota_id: QuotaId, value: u64) -> u64 {
183 self.storage_quotas.insert(quota_id, value).unwrap_or(0)
184 }
185
186 fn yield_catcher_markers(&self, catcher_hash: CapHash) -> Vec<CapHash> {
187 self.yield_catchers
188 .get(&catcher_hash)
189 .cloned()
190 .unwrap_or_default()
191 }
192
193 fn yield_catcher_add(&mut self, catcher_hash: CapHash, marker_instance_hash: CapHash) {
194 let entry = self.yield_catchers.entry(catcher_hash).or_default();
195 if !entry.contains(&marker_instance_hash) {
196 entry.push(marker_instance_hash);
197 }
198 }
199
200 fn yield_catcher_remove(&mut self, catcher_hash: CapHash, marker_instance_hash: CapHash) {
201 if let Some(entry) = self.yield_catchers.get_mut(&catcher_hash) {
202 entry.retain(|m| *m != marker_instance_hash);
203 }
204 }
205
206 fn yield_catcher_new(&mut self) -> CapHash {
207 let nonce = self.next_yc_nonce;
208 self.next_yc_nonce += 1;
209 let hash = Blake2b256::hash(&nonce.to_le_bytes());
212 self.yield_catchers.insert(hash, Vec::new());
213 hash
214 }
215
216 fn host_open(&mut self, file_id: u64) -> Option<CapHashOrRef> {
217 self.files.get(&file_id).cloned()
218 }
219
220 fn host_save(&mut self, data: CapHashOrRef, quota_id: u64, size: u64) -> Option<u64> {
221 let q = self.storage_quotas.get("a_id).copied().unwrap_or(0);
222 if q < size {
223 return None;
224 }
225 self.storage_quotas.insert(quota_id, q - size);
226 let id = self.next_file_id;
227 self.next_file_id += 1;
228 self.files.insert(id, data);
229 Some(id)
230 }
231}
232
233#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
245pub enum KernelImage {
246 GasMeter,
247 StorageQuota,
248 YieldCatcher,
249 Gas,
251 Quota,
253 SetGasMeter,
254 SetStorageQuota,
255 MintGas,
256 MintQuota,
257 CreateYieldCatcher,
258 OogMarker,
259 StorageExhaustedMarker,
260 File,
264 HostOpen,
267 HostSave,
269}
270
271const fn const_kernel_image_label(kind: KernelImage) -> &'static [u8] {
276 match kind {
277 KernelImage::GasMeter => b"kernel:gasmeter",
278 KernelImage::StorageQuota => b"kernel:storagequota",
279 KernelImage::YieldCatcher => b"kernel:yieldcatcher",
280 KernelImage::Gas => b"kernel:gas",
281 KernelImage::Quota => b"kernel:quota",
282 KernelImage::SetGasMeter => b"kernel:set_gas_meter",
283 KernelImage::SetStorageQuota => b"kernel:set_storage_quota",
284 KernelImage::MintGas => b"kernel:mint_gas",
285 KernelImage::MintQuota => b"kernel:mint_quota",
286 KernelImage::CreateYieldCatcher => b"kernel:create_yield_catcher",
287 KernelImage::OogMarker => b"kernel:oog_marker",
288 KernelImage::StorageExhaustedMarker => b"kernel:storage_exhausted_marker",
289 KernelImage::File => b"kernel:file",
290 KernelImage::HostOpen => b"kernel:host_open",
291 KernelImage::HostSave => b"kernel:host_save",
292 }
293}
294
295pub fn kernel_image_hash(kind: KernelImage) -> CapHash {
297 Blake2b256::hash(const_kernel_image_label(kind))
298}
299
300pub fn recognize_kernel_image(hash: CapHash) -> Option<KernelImage> {
304 [
307 KernelImage::GasMeter,
308 KernelImage::StorageQuota,
309 KernelImage::YieldCatcher,
310 KernelImage::Gas,
311 KernelImage::Quota,
312 KernelImage::SetGasMeter,
313 KernelImage::SetStorageQuota,
314 KernelImage::MintGas,
315 KernelImage::MintQuota,
316 KernelImage::CreateYieldCatcher,
317 KernelImage::OogMarker,
318 KernelImage::StorageExhaustedMarker,
319 KernelImage::File,
320 KernelImage::HostOpen,
321 KernelImage::HostSave,
322 ]
323 .into_iter()
324 .find(|kind| kernel_image_hash(*kind) == hash)
325}
326
327#[cfg(test)]
328mod tests {
329 use super::*;
330
331 #[test]
332 fn gas_meter_get_missing_returns_zero() {
333 let k = InProcessKernelAssist::new();
334 assert_eq!(k.gas_meter_get(42), 0);
335 }
336
337 #[test]
338 fn gas_meter_set_returns_previous() {
339 let mut k = InProcessKernelAssist::new();
340 assert_eq!(k.gas_meter_set(1, 100), 0);
341 assert_eq!(k.gas_meter_set(1, 200), 100);
342 assert_eq!(k.gas_meter_get(1), 200);
343 }
344
345 #[test]
346 fn storage_quota_round_trip() {
347 let mut k = InProcessKernelAssist::new();
348 assert_eq!(k.storage_quota_set(7, 1024), 0);
349 assert_eq!(k.storage_quota_get(7), 1024);
350 assert_eq!(k.storage_quota_set(7, 2048), 1024);
351 }
352
353 #[test]
354 fn yield_catcher_add_and_read_markers() {
355 let mut k = InProcessKernelAssist::new();
356 let yc = k.yield_catcher_new();
357 let m1 = [1u8; 32];
358 let m2 = [2u8; 32];
359 k.yield_catcher_add(yc, m1);
360 k.yield_catcher_add(yc, m2);
361 assert_eq!(k.yield_catcher_markers(yc), vec![m1, m2]);
362 }
363
364 #[test]
365 fn yield_catcher_add_is_set_semantics() {
366 let mut k = InProcessKernelAssist::new();
367 let yc = k.yield_catcher_new();
368 let m = [9u8; 32];
369 k.yield_catcher_add(yc, m);
370 k.yield_catcher_add(yc, m); assert_eq!(k.yield_catcher_markers(yc).len(), 1);
372 }
373
374 #[test]
375 fn yield_catcher_remove() {
376 let mut k = InProcessKernelAssist::new();
377 let yc = k.yield_catcher_new();
378 let m = [3u8; 32];
379 k.yield_catcher_add(yc, m);
380 k.yield_catcher_remove(yc, m);
381 assert!(k.yield_catcher_markers(yc).is_empty());
382 k.yield_catcher_remove(yc, [9u8; 32]);
384 }
385
386 #[test]
387 fn yield_catcher_new_returns_distinct_hashes() {
388 let mut k = InProcessKernelAssist::new();
389 let a = k.yield_catcher_new();
390 let b = k.yield_catcher_new();
391 assert_ne!(a, b);
392 }
393
394 #[test]
395 fn reset_block_state_clears_meters_and_catchers() {
396 let mut k = InProcessKernelAssist::new();
397 k.gas_meter_set(1, 100);
398 k.storage_quota_set(2, 200);
399 let yc = k.yield_catcher_new();
400 k.yield_catcher_add(yc, [4u8; 32]);
401
402 k.reset_block_state();
403
404 assert_eq!(k.gas_meter_get(1), 0);
405 assert_eq!(k.storage_quota_get(2), 0);
406 assert!(k.yield_catcher_markers(yc).is_empty());
407 }
408
409 #[test]
410 fn kernel_image_hash_is_deterministic() {
411 assert_eq!(
412 kernel_image_hash(KernelImage::GasMeter),
413 kernel_image_hash(KernelImage::GasMeter)
414 );
415 }
416
417 #[test]
418 fn kernel_images_have_distinct_hashes() {
419 let all = [
420 KernelImage::GasMeter,
421 KernelImage::StorageQuota,
422 KernelImage::YieldCatcher,
423 KernelImage::Gas,
424 KernelImage::Quota,
425 KernelImage::SetGasMeter,
426 KernelImage::SetStorageQuota,
427 KernelImage::MintGas,
428 KernelImage::MintQuota,
429 KernelImage::CreateYieldCatcher,
430 KernelImage::OogMarker,
431 KernelImage::StorageExhaustedMarker,
432 KernelImage::File,
433 KernelImage::HostOpen,
434 KernelImage::HostSave,
435 ];
436 for (i, a) in all.iter().enumerate() {
437 for b in &all[i + 1..] {
438 assert_ne!(
439 kernel_image_hash(*a),
440 kernel_image_hash(*b),
441 "{:?} vs {:?}",
442 a,
443 b
444 );
445 }
446 }
447 }
448
449 #[test]
450 fn recognize_known_image() {
451 let h = kernel_image_hash(KernelImage::OogMarker);
452 assert_eq!(recognize_kernel_image(h), Some(KernelImage::OogMarker));
453 }
454
455 #[test]
456 fn recognize_unknown_image_is_none() {
457 let h = Blake2b256::hash(b"user:my-cool-image");
458 assert_eq!(recognize_kernel_image(h), None);
459 }
460}