javm_cap/image.rs
1//! Image: the smallest unit of program specification.
2//!
3//! An `Image` is content-addressed (its `image_id` is the hash of
4//! its serialized content). An Instance's `image_hash` is the
5//! cumulative chain hash tracking the lineage of `set_image` /
6//! `host_derive_spawn` extensions from genesis.
7//!
8//! ```text
9//! genesis (host_derive_spawn from no source):
10//! image_hash = hash(image)
11//!
12//! after set_image(new):
13//! image_hash = hash(prev_chain || hash(new))
14//!
15//! after host_derive_spawn(new, cnode) by a spawner:
16//! spawned.image_hash = hash(spawner.image_hash || hash(new))
17//!
18//! after MGMT_COPY of a Cap::Instance:
19//! copy.image_hash = source.image_hash (preserved)
20//! ```
21//!
22//! This module provides the pure data structures + the chain-hash
23//! computations. Image *content hashing* is done by serializing the
24//! Image canonically and feeding the bytes to `H::hash`; we provide
25//! a simple deterministic encoder here so the v3 implementation
26//! has one canonical form.
27
28use crate::hash::Hash;
29use crate::slot::Key;
30use alloc::collections::BTreeMap;
31use alloc::vec::Vec;
32use ssz_derive::{Decode, Encode};
33
34/// Image: the program spec (code, endpoints, memory layout, slot
35/// declarations, pinned ro caps).
36///
37/// `pinned_slots` and `yield_receiver_slot` reference cnode slots; the
38/// kernel installs declared pinned content into the Instance's cnode
39/// at `set_image` / `host_derive_spawn` time and treats them as
40/// read-only thereafter.
41///
42/// **Validation model.** This is the untrusted SSZ wire form; converting
43/// it to a [`crate::cap::image::ImageCap`] via
44/// [`crate::cap::image::image_cap`] is the "deblob" that validates the
45/// Image's *structure* eagerly (sizes, bounds, slot indices, path depth).
46/// The `code` *bytes* are never screened — instruction *semantics* are
47/// validated lazily, at execution. See [`crate::cap::image::ImageCap`] for
48/// the full structure-eager / semantics-lazy rationale.
49#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, ssz_derive::HashTreeRoot)]
50pub struct Image {
51 /// The (single) code region: raw RV+C+custom-0 bytes. Mapped RO at
52 /// the fixed protocol constant [`crate::layout::CODE_BASE`] (PC =
53 /// `CODE_BASE + byte_offset`) — the load address is *not* chosen by
54 /// the Image, so an untrusted Image cannot place code arbitrarily.
55 /// Code is mapped RO into the guest address space so the guest can
56 /// read its own bytes (AUIPC+load PIC); the JIT executes the native
57 /// translation. Empty for codeless images (kernel placeholders).
58 pub code: CodeRef,
59 /// Endpoints addressable by a [`Key`] selector (the same byte-string key
60 /// type as a cnode slot; in the V1 single-byte ABI the selector is one
61 /// byte). Sparse — only declared endpoints are present, with no fixed
62 /// capacity. An absent key is an undefined endpoint.
63 pub endpoints: BTreeMap<Key, EndpointDef>,
64 /// Memory layout. Each entry maps a `Cap::Data` (resolved through
65 /// the `source` slot path) into the address space at `[start, start
66 /// + size)`. RO vs RW is derived from whether the target slot
67 /// appears in `pinned_slots`. Code is mapped separately at
68 /// [`crate::layout::CODE_BASE`] and is not described here.
69 pub memory_mappings: Vec<MemoryMapping>,
70 /// Pinned read-only caps (Cap::Data or Cap::Image) baked into
71 /// the spec. The kernel rejects mutations to these slots.
72 pub pinned_slots: BTreeMap<Key, PinnedCap>,
73 /// Initial cnode state for non-pinned mutable slots. Only
74 /// honored at standalone (root) Instance bootstrap — a
75 /// parented Instance receives its cnode from the spawner.
76 pub initial_slots: BTreeMap<Key, InitialDataCap>,
77 /// Slot holding `Cap::Instance[YieldReceiver]` — the set of yield_keys this
78 /// Instance catches. The kernel snapshots it at each downward CALL and
79 /// consults the snapshot when routing a yield. None = catches no yields.
80 pub yield_receiver_slot: Option<Key>,
81 /// Cnode slots holding the `Cap::Instance[Gas{meter_key}]` unit handles
82 /// the kernel debits while this Instance runs. Slots are consulted in order:
83 /// empty declared slots are skipped, the first valid non-empty slot is the
84 /// primary meter used in OOG payloads, and later valid slots are fallback
85 /// reserves. Empty list = no Image-declared meter (the frame loans its
86 /// caller's gas scope, or the host budget at root).
87 pub gas_slots: Vec<Key>,
88 /// Cnode slots holding the `Cap::Instance[Quota{quota_key}]` unit handles.
89 /// Same convention as [`Self::gas_slots`].
90 pub quota_slots: Vec<Key>,
91 /// Payload arena: a single byte pool holding the code region and every
92 /// data cap's non-zero pages, packed tightly. [`CodeRef`] indexes the
93 /// contiguous code slice; each [`ArenaPageRef`] indexes a window holding
94 /// one page's non-zero prefix (zero-padded back to `PAGE_SIZE` at decode).
95 /// All-zero pages are **never** stored — they are elided and materialize
96 /// as the canonical zero page at deblob, so blobs carry no `.bss`/
97 /// leading-gap zeros, and trailing zeros within a page are dropped too.
98 /// Identical pages may share one window (dedup); sharing is invisible to
99 /// cap identity. Trailing field so the structural header decodes without
100 /// touching the payload.
101 pub arena: Vec<u8>,
102}
103
104/// Endpoint definition: entry PC + register conventions.
105#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, ssz_derive::HashTreeRoot)]
106pub struct EndpointDef {
107 /// Bytecode address to jump to.
108 pub entry_pc: u64,
109 /// Number of register args supplied by the caller (0..=4
110 /// per spec convention; we store as u8 for flexibility).
111 pub arg_registers: u8,
112 /// Size of the arg cnode the caller may attach.
113 pub arg_cnode_size: u8,
114 /// PVM registers to seed before entering the endpoint. Keyed
115 /// by register index (0..=12). Common usage: φ\[1\] (RISC-V SP)
116 /// ← `stack_top`. The kernel applies these on top of the
117 /// calling-convention defaults (φ\[11\] = endpoint_idx).
118 pub initial_regs: BTreeMap<u8, u64>,
119}
120
121/// One mapped region. The kernel resolves `source` (a cnode slot path
122/// to a `Cap::Data`) at instance start and lays the bytes at `[start,
123/// start + size)` in the address space. RO vs RW is derived from
124/// whether the target slot is in `Image.pinned_slots`.
125#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, ssz_derive::HashTreeRoot)]
126pub struct MemoryMapping {
127 pub start: u64,
128 pub size: u64,
129 /// Cnode path resolving to the `Cap::Data` whose bytes back this
130 /// region.
131 pub source: crate::slot::SlotPath,
132}
133
134/// Pinned slot content. Only content-addressed cap kinds can be
135/// pinned (Data or Image). `Cap::Data` bytes are inlined in the
136/// Image; a future optimisation can add a hash-only variant for
137/// content that lives in σ.data_payloads.
138#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, ssz_derive::HashTreeRoot)]
139pub enum PinnedCap {
140 #[ssz(selector = 0)]
141 /// Pinned `Cap::Data` as a page-granular [`DataDesc`] over the Image
142 /// [`arena`](Image::arena). All-zero pages are elided.
143 Data { desc: DataDesc },
144 #[ssz(selector = 1)]
145 /// Pinned `Cap::Image` by content hash. Cap::Image is itself
146 /// content-addressed; inlining a whole sub-Image makes less
147 /// sense than for Data.
148 Image { content_hash: [u8; 32] },
149}
150
151/// Initial `Cap::Data` content for a non-pinned mutable slot. Used at
152/// standalone (root) Instance bootstrap to seed the cnode. A parented
153/// Instance receives its slots from the spawner and ignores this field.
154///
155/// Now a [`DataDesc`] (page-granular sparse content over the Image arena);
156/// a pure zero region (stack/heap) is `DataDesc { size, pages: [] }`.
157pub type InitialDataCap = DataDesc;
158
159/// A slice of the Image [`arena`](Image::arena) holding the contiguous
160/// code region: `arena[arena_off .. arena_off + len]` are the raw
161/// RV+C+custom-0 bytes, mapped RO at [`crate::layout::CODE_BASE`]. `len`
162/// is the *exact* (non-page-rounded) code length — the recompiler and
163/// `alloc_page_aligned_code` iterate exactly `len` bytes — while the arena
164/// window itself is page-rounded. `CodeRef::default()` (`{0, 0}`) is a
165/// codeless image.
166#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Encode, Decode, ssz_derive::HashTreeRoot)]
167pub struct CodeRef {
168 pub arena_off: u32,
169 pub len: u32,
170}
171
172/// One non-zero page of a [`DataDesc`]: logical page `page_index` is backed
173/// by the arena window `arena[arena_off .. arena_off + len]`, zero-padded to
174/// `PAGE_SIZE` when materialized. `len` (`1..=PAGE_SIZE`) stores only the
175/// page's meaningful prefix — trailing zeros *within* the page are dropped,
176/// so a sub-page-dense region costs `len` bytes, not a full page. Windows are
177/// packed tightly (no `arena_off` alignment). Pages not named by any
178/// `ArenaPageRef` are the canonical zero page (`PageSlot::Empty`).
179///
180/// Named distinctly from the cap-layer `PageRef = Arc<PageBytes>`: this is
181/// a wire descriptor (offsets), not a refcounted runtime page.
182#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode, ssz_derive::HashTreeRoot)]
183pub struct ArenaPageRef {
184 pub page_index: u32,
185 pub arena_off: u32,
186 pub len: u32,
187}
188
189/// Page-granular sparse content of a data cap. `size` is the full logical
190/// extent in bytes (a `PAGE_SIZE` multiple); `pages` names only the
191/// non-zero pages, sorted by `page_index` (strictly ascending, unique), each
192/// backed by an arena window of its non-zero prefix (zero-padded back to
193/// `PAGE_SIZE` at decode) — see [`ArenaPageRef`].
194///
195/// Decodes (via [`DataDesc::to_data_cap`]) to a `DataCap` whose identity is
196/// over logical `{size, page_index -> content}` only — independent of arena
197/// layout, page ordering, or page sharing. So eliding zero pages and
198/// deduplicating identical pages never change a cap hash.
199#[derive(Debug, Clone, Default, PartialEq, Eq, Encode, Decode, ssz_derive::HashTreeRoot)]
200pub struct DataDesc {
201 pub size: u64,
202 pub pages: Vec<ArenaPageRef>,
203}
204
205impl Image {
206 /// Empty Image: no code, no endpoints, no mappings, no slots.
207 /// Useful for tests and as a starting point.
208 pub fn empty() -> Self {
209 Self {
210 code: CodeRef::default(),
211 endpoints: BTreeMap::new(),
212 memory_mappings: Vec::new(),
213 pinned_slots: BTreeMap::new(),
214 initial_slots: BTreeMap::new(),
215 yield_receiver_slot: None,
216 gas_slots: Vec::new(),
217 quota_slots: Vec::new(),
218 arena: Vec::new(),
219 }
220 }
221
222 /// An Image carrying only `code` (no slots/mappings/endpoints): the
223 /// arena holds the page-rounded code at offset 0. Convenience for the
224 /// test helpers that previously did `let mut img = Image::empty();
225 /// img.code = bytes;` — set the other structural fields after.
226 pub fn with_code(code: Vec<u8>) -> Self {
227 ImageBuilder::new().code(code).build()
228 }
229
230 /// The raw code bytes: the `arena` window `[code.arena_off,
231 /// code.arena_off + code.len)`. Empty slice for a codeless image (or
232 /// an out-of-range `CodeRef`, which the deblob rejects separately).
233 pub fn code_bytes(&self) -> &[u8] {
234 let off = self.code.arena_off as usize;
235 let len = self.code.len as usize;
236 match off.checked_add(len) {
237 Some(end) => self.arena.get(off..end).unwrap_or(&[]),
238 None => &[],
239 }
240 }
241
242 /// The Instance data extent in bytes: `mem_top − DATA_BASE`, page-rounded
243 /// (the size of the RW memory `DataCap`). Code is RO direct-mapped at
244 /// `CODE_BASE`, so it contributes nothing here.
245 pub fn mem_extent(&self) -> u64 {
246 let mut mem_top: u32 = 0;
247 for mapping in &self.memory_mappings {
248 let end = (mapping.start + mapping.size) as u32;
249 if end > mem_top {
250 mem_top = end;
251 }
252 }
253 (mem_top as u64)
254 .saturating_sub(crate::layout::DATA_BASE as u64)
255 .next_multiple_of(crate::cap::data::PAGE_SIZE as u64)
256 }
257
258 /// Build the Instance's memory backing [`crate::DataCap`]: every mapping's source
259 /// content (pinned **and** initial) folded at the mapping's offset above
260 /// `DATA_BASE`. This is the same byte layout the legacy `data_overlays`
261 /// produced, collapsed into one dense `DataCap`.
262 ///
263 /// Pinned content is included here (not kept separate) so the cache-free
264 /// `nub-arch-local` engine can seed memory without resolving caps; both
265 /// engines still mark the pinned VAs read-only at seed time, and the
266 /// recompiler maps them as `PinnedCapRo` directly from these slabs, so the
267 /// pinned-RO gas tier is preserved.
268 ///
269 /// Single source of truth for Instance memory layout: both engines seed
270 /// from this backing, so they materialize byte-identical memory.
271 ///
272 /// **Precondition:** the Image must be deblob-validated
273 /// ([`crate::cap::image::image_cap`], which runs [`DataDesc::validate`] on
274 /// every slot) before this is called — it slices `self.arena` by each
275 /// page-ref's `arena_off`, so an out-of-range ref on an *unvalidated*
276 /// Image would panic. Producers ([`ImageBuilder`]) always emit in-bounds
277 /// page-refs, and the deblob rejects malformed ones loudly.
278 pub fn instance_mem_backing(&self) -> crate::cap::data::DataCap {
279 use crate::cap::data::{DataCap, PAGE_SIZE};
280 let size = self.mem_extent().max(PAGE_SIZE as u64);
281 let mut backing = DataCap::from_bytes_sized(&[], size);
282 let data_base = crate::layout::DATA_BASE as u64;
283 for mapping in &self.memory_mappings {
284 let Some(target) = mapping.source.target() else {
285 continue;
286 };
287 let desc: &DataDesc =
288 if let Some(PinnedCap::Data { desc }) = self.pinned_slots.get(target) {
289 desc
290 } else if let Some(desc) = self.initial_slots.get(target) {
291 desc
292 } else {
293 continue;
294 };
295 // Fold each named page at its absolute offset; omitted pages
296 // stay `PageSlot::Empty` (zero). `put_page` canonicalizes
297 // all-zero -> Empty, so this is byte-identical to the previous
298 // contiguous `content.chunks(PAGE_SIZE)` fold for equal content.
299 let base_off = mapping.start.saturating_sub(data_base);
300 for pr in &desc.pages {
301 let off = pr.arena_off as usize;
302 let slice = &self.arena[off..off + pr.len as usize];
303 backing.put_page(base_off + pr.page_index as u64 * PAGE_SIZE as u64, slice);
304 }
305 }
306 backing
307 }
308}
309
310impl DataDesc {
311 /// Number of logical pages (`size / PAGE_SIZE`).
312 pub fn page_count(&self) -> u64 {
313 self.size / crate::cap::data::PAGE_SIZE as u64
314 }
315
316 /// Eagerly validate this descriptor against an arena of `arena_len`
317 /// bytes: `size` a `PAGE_SIZE` multiple; every page-ref page-aligned,
318 /// in-bounds, with `page_index < page_count`; pages strictly ascending
319 /// by `page_index` (canonical, no duplicates). Untrusted wire input is
320 /// checked here — a loud `Err`, never a panic — before any arena slice
321 /// is taken in [`to_data_cap`](Self::to_data_cap).
322 pub fn validate(&self, arena_len: usize) -> Result<(), DataDescError> {
323 use crate::cap::data::PAGE_SIZE;
324 if !self.size.is_multiple_of(PAGE_SIZE as u64) {
325 return Err(DataDescError::SizeNotPageMultiple(self.size));
326 }
327 let page_count = self.page_count();
328 let mut prev: Option<u32> = None;
329 for pr in &self.pages {
330 // A named page stores 1..=PAGE_SIZE non-zero-prefix bytes.
331 if pr.len == 0 || pr.len as usize > PAGE_SIZE {
332 return Err(DataDescError::BadLen(pr.len));
333 }
334 let end = (pr.arena_off as usize)
335 .checked_add(pr.len as usize)
336 .ok_or(DataDescError::OutOfRange(pr.arena_off))?;
337 if end > arena_len {
338 return Err(DataDescError::OutOfRange(pr.arena_off));
339 }
340 if pr.page_index as u64 >= page_count {
341 return Err(DataDescError::PageIndexOutOfRange {
342 page_index: pr.page_index,
343 page_count,
344 });
345 }
346 if prev.is_some_and(|p| pr.page_index <= p) {
347 return Err(DataDescError::NotCanonical(pr.page_index));
348 }
349 prev = Some(pr.page_index);
350 }
351 Ok(())
352 }
353
354 /// Materialize the runtime [`DataCap`](crate::cap::data::DataCap): each
355 /// named page is `Loaded` from its `PAGE_SIZE` arena window at its
356 /// absolute `page_index`; omitted pages are the canonical zero page.
357 /// The result is byte-identical (and hash-identical) to
358 /// `DataCap::from_bytes_sized(equivalent_contiguous_content, size)`.
359 ///
360 /// Assumes [`validate`](Self::validate) has passed (the deblob gate); an
361 /// out-of-range `arena_off` panics — the intended loud failure for a
362 /// producer bug.
363 pub fn to_data_cap(&self, arena: &[u8]) -> crate::cap::data::DataCap {
364 crate::cap::data::DataCap::from_sparse_pages(
365 self.size,
366 self.pages.iter().map(|pr| {
367 let off = pr.arena_off as usize;
368 // `len` bytes (the non-zero prefix); `from_content`/`put_page_idx`
369 // zero-pad back to a full `PAGE_SIZE` page.
370 (pr.page_index, &arena[off..off + pr.len as usize])
371 }),
372 )
373 }
374}
375
376/// Structural faults in a [`DataDesc`] relative to the Image arena,
377/// surfaced eagerly at deblob (per the strict-interface rule: fail loud).
378#[derive(Debug, Clone, PartialEq, Eq)]
379pub enum DataDescError {
380 /// `size` is not a `PAGE_SIZE` multiple.
381 SizeNotPageMultiple(u64),
382 /// A page-ref `len` is 0 or exceeds `PAGE_SIZE`.
383 BadLen(u32),
384 /// A page-ref window `[arena_off, arena_off + len)` exceeds the arena.
385 OutOfRange(u32),
386 /// A `page_index` is `>= size / PAGE_SIZE`.
387 PageIndexOutOfRange { page_index: u32, page_count: u64 },
388 /// Pages are not strictly ascending by `page_index` (unsorted or duplicate).
389 NotCanonical(u32),
390}
391
392/// Pack contiguous `content` (zero-filled to `target_size`) into a
393/// [`DataDesc`] over `arena`, reusing `DataCap::from_bytes_sized`'s exact
394/// canonicalization (all-zero pages elided, the `size` formula) so the
395/// descriptor round-trips to a byte-identical `DataCap`. Identical pages
396/// (keyed by content hash) share one `arena_off` via `dedup`.
397fn pack_data(
398 arena: &mut Vec<u8>,
399 dedup: &mut BTreeMap<[u8; 32], (u32, u32)>,
400 content: &[u8],
401 target_size: u64,
402) -> DataDesc {
403 use crate::cap::data::DataCap;
404 use crate::cap::page::PageSlot;
405 let dc = DataCap::from_bytes_sized(content, target_size);
406 let size = dc.content_len();
407 let mut pages = Vec::new();
408 for p in 0..dc.backing.page_count() {
409 if let PageSlot::Loaded(pb) = dc.backing.page(p) {
410 // A Loaded page is non-zero: store only its prefix up to the last
411 // non-zero byte (trailing zeros within the page zero-pad back
412 // identically at decode), packed tightly. Dedup identical pages by
413 // their full-page content hash → shared (arena_off, len).
414 let (arena_off, len) = *dedup.entry(pb.hash).or_insert_with(|| {
415 let len = pb.bytes.iter().rposition(|&b| b != 0).map_or(1, |i| i + 1);
416 let off = arena.len() as u32;
417 arena.extend_from_slice(&pb.bytes[..len]);
418 (off, len as u32)
419 });
420 pages.push(ArenaPageRef {
421 page_index: p as u32,
422 arena_off,
423 len,
424 });
425 }
426 }
427 DataDesc { size, pages }
428}
429
430/// Canonical Image assembler. Callers supply logical content (code +
431/// per-slot contiguous bytes + size, exactly as before the arena
432/// redesign); [`build`](ImageBuilder::build) packs a single page-granular
433/// `arena` deterministically: code laid contiguously at offset 0, then
434/// each data cap (pinned then initial, in `Key` order) page-split with
435/// all-zero pages elided and byte-identical pages deduplicated. The packing
436/// is a pure function of the logical content, so equal logical Images
437/// produce equal arenas and equal `image_content_hash`es regardless of
438/// builder call order.
439#[derive(Default)]
440pub struct ImageBuilder {
441 code: Vec<u8>,
442 endpoints: BTreeMap<Key, EndpointDef>,
443 memory_mappings: Vec<MemoryMapping>,
444 pinned: BTreeMap<Key, PinnedSpec>,
445 initial: BTreeMap<Key, (Vec<u8>, u64)>,
446 yield_receiver_slot: Option<Key>,
447 gas_slots: Vec<Key>,
448 quota_slots: Vec<Key>,
449}
450
451enum PinnedSpec {
452 Data { content: Vec<u8>, size: u64 },
453 Image { content_hash: [u8; 32] },
454}
455
456impl ImageBuilder {
457 pub fn new() -> Self {
458 Self::default()
459 }
460 pub fn code(mut self, code: Vec<u8>) -> Self {
461 self.code = code;
462 self
463 }
464 pub fn endpoint(mut self, key: Key, ep: EndpointDef) -> Self {
465 self.endpoints.insert(key, ep);
466 self
467 }
468 pub fn mapping(mut self, m: MemoryMapping) -> Self {
469 self.memory_mappings.push(m);
470 self
471 }
472 pub fn pinned_data(mut self, key: Key, content: Vec<u8>, size: u64) -> Self {
473 self.pinned.insert(key, PinnedSpec::Data { content, size });
474 self
475 }
476 pub fn pinned_image(mut self, key: Key, content_hash: [u8; 32]) -> Self {
477 self.pinned.insert(key, PinnedSpec::Image { content_hash });
478 self
479 }
480 pub fn initial_data(mut self, key: Key, content: Vec<u8>, size: u64) -> Self {
481 self.initial.insert(key, (content, size));
482 self
483 }
484 pub fn yield_receiver_slot(mut self, slot: Option<Key>) -> Self {
485 self.yield_receiver_slot = slot;
486 self
487 }
488 pub fn gas_slots(mut self, slots: Vec<Key>) -> Self {
489 self.gas_slots = slots;
490 self
491 }
492 pub fn quota_slots(mut self, slots: Vec<Key>) -> Self {
493 self.quota_slots = slots;
494 self
495 }
496
497 pub fn build(self) -> Image {
498 let mut arena: Vec<u8> = Vec::new();
499 let mut dedup: BTreeMap<[u8; 32], (u32, u32)> = BTreeMap::new();
500
501 // Data caps first, in deterministic Key order (pinned, then
502 // initial). Each non-zero page is appended as its non-zero prefix
503 // (trailing-zero-trimmed, packed tightly), with identical pages
504 // deduplicated by content hash.
505 let mut pinned_slots: BTreeMap<Key, PinnedCap> = BTreeMap::new();
506 for (key, spec) in self.pinned {
507 let pc = match spec {
508 PinnedSpec::Data { content, size } => PinnedCap::Data {
509 desc: pack_data(&mut arena, &mut dedup, &content, size),
510 },
511 PinnedSpec::Image { content_hash } => PinnedCap::Image { content_hash },
512 };
513 pinned_slots.insert(key, pc);
514 }
515 let mut initial_slots: BTreeMap<Key, DataDesc> = BTreeMap::new();
516 for (key, (content, size)) in self.initial {
517 initial_slots.insert(key, pack_data(&mut arena, &mut dedup, &content, size));
518 }
519
520 // Code last: contiguous, stored at its EXACT length. The deblob
521 // re-copies code into a fresh aligned slab, so its arena offset
522 // needs no alignment.
523 let code = if self.code.is_empty() {
524 CodeRef::default()
525 } else {
526 let arena_off = arena.len() as u32;
527 let len = self.code.len() as u32;
528 arena.extend_from_slice(&self.code);
529 CodeRef { arena_off, len }
530 };
531
532 Image {
533 code,
534 endpoints: self.endpoints,
535 memory_mappings: self.memory_mappings,
536 pinned_slots,
537 initial_slots,
538 yield_receiver_slot: self.yield_receiver_slot,
539 gas_slots: self.gas_slots,
540 quota_slots: self.quota_slots,
541 arena,
542 }
543 }
544}
545
546/// Content hash of an Image: SSZ `hash_tree_root` (SHA-256 merkleization
547/// of the derived SSZ container). The canonical encoding/merkleization is
548/// defined by `Image`'s `ssz-derive` impl.
549pub fn image_content_hash(image: &Image) -> [u8; 32] {
550 ssz::hash_tree_root(image)
551}
552
553/// Genesis image-hash chain: a freshly-derived Instance (with no
554/// prior chain) has `image_hash = image_content_hash`.
555///
556/// This is the case for the very first Instance the chain spec
557/// produces. Subsequent Instances always derive from some spawner
558/// via `chain_extend`.
559pub fn chain_genesis<H: Hash>(image: &Image) -> H::Out
560where
561 H::Out: From<[u8; 32]>,
562{
563 image_content_hash(image).into()
564}
565
566/// Extend an image-hash chain with a new image:
567/// `result = H(prev_chain || image_content_hash(new_image))`.
568///
569/// Used for both `set_image(new)` on an existing Instance and
570/// `host_derive_spawn(new, cnode)` from a spawner.
571pub fn chain_extend<H: Hash>(prev_chain: &H::Out, new_image: &Image) -> H::Out
572where
573 H::Out: AsRef<[u8]>,
574{
575 let new_image_hash = image_content_hash(new_image);
576 H::hash_pair(prev_chain.as_ref(), &new_image_hash)
577}