lib/machine/src/checkpoint/roots/test.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const admission = @import("../../admission/root.zig");
2 const checkpoint = @import("../root.zig");
3 const core = @import("machine_instance_core");
4 const fixture = @import("../../instance/fixture/root.zig").fixture;
5 const instance = @import("../../instance/root.zig");
6 const os = @import("os");
7 const profile = @import("../../profile/root.zig");
8 const std = @import("std");
9
10 const roots = checkpoint.roots;
11 const k0_elf = @embedFile("machine-k0-elf");
12 const k0_manifest = @embedFile("machine-k0-manifest");
13
14 const Entry = struct {
15 kind: roots.ObjectKind,
16 digest: os.abi.Digest,
17 offset: u32,
18 length: u32,
19 marked: bool,
20 };
21
22 const Hidden = struct {
23 kind: roots.ObjectKind,
24 digest: os.abi.Digest,
25 };
26
27 const CrashCut = enum(u8) {
28 after_begin,
29 after_manifest_put,
30 after_staged_read,
31 before_commit,
32 after_commit,
33 };
34
35 const TestStore = struct {
36 entries: []Entry,
37 bytes: []u8,
38 lookup: []u32,
39 entry_limit: u32,
40 byte_limit: u64,
41 committed_entries: u32 = 0,
42 committed_bytes: u64 = 0,
43 staged_entries: u32 = 0,
44 staged_bytes: u64 = 0,
45 root_count: u8 = 0,
46 root_storage: [roots.chain_limit + 4]roots.ManifestRoot = undefined,
47 root_seed_masks: [roots.chain_limit + 4]u8 = @splat(0),
48 blocks: [roots.chain_limit + 4]os.abi.BlockRoot = undefined,
49 block_marked: [roots.chain_limit + 4]bool = @splat(false),
50 block_count: u8 = 0,
51 block_verifications: u32 = 0,
52 put_calls: u32 = 0,
53 retain_calls: u32 = 0,
54 collection_commits: u16 = 0,
55 active: bool = false,
56 collection_active: bool = false,
57 collection_snapshot: roots.maintenance.CollectionSnapshot = undefined,
58 block_available: bool = true,
59 forge_page: bool = false,
60 forged: bool = false,
61 hidden: ?Hidden = null,
62 crash_cut: ?CrashCut = null,
63
64 fn init(entries: []Entry, bytes: []u8, lookup: []u32) TestStore {
65 std.debug.assert(lookup.len != 0);
66 std.debug.assert((lookup.len & (lookup.len - 1)) == 0);
67 std.debug.assert(entries.len <= lookup.len / 2);
68 @memset(lookup, 0);
69 return .{
70 .entries = entries,
71 .bytes = bytes,
72 .lookup = lookup,
73 .entry_limit = @intCast(entries.len),
74 .byte_limit = bytes.len,
75 };
76 }
77
78 fn reset(self: *TestStore) void {
79 self.committed_entries = 0;
80 self.committed_bytes = 0;
81 self.staged_entries = 0;
82 self.staged_bytes = 0;
83 self.root_count = 0;
84 self.block_count = 0;
85 self.block_verifications = 0;
86 self.put_calls = 0;
87 self.retain_calls = 0;
88 self.collection_commits = 0;
89 self.active = false;
90 self.collection_active = false;
91 self.block_available = true;
92 self.forge_page = false;
93 self.forged = false;
94 self.hidden = null;
95 self.crash_cut = null;
96 self.entry_limit = @intCast(self.entries.len);
97 self.byte_limit = self.bytes.len;
98 @memset(self.lookup, 0);
99 @memset(&self.root_seed_masks, 0);
100 @memset(&self.block_marked, false);
101 }
102
103 fn restart(self: *TestStore) TestStore {
104 const committed_entries = self.committed_entries;
105 const committed_bytes = self.committed_bytes;
106 const root_count = self.root_count;
107 const block_count = self.block_count;
108 var roots_copy: [roots.chain_limit + 4]roots.ManifestRoot = undefined;
109 var seeds_copy: [roots.chain_limit + 4]u8 = @splat(0);
110 var blocks_copy: [roots.chain_limit + 4]os.abi.BlockRoot = undefined;
111 @memcpy(roots_copy[0..root_count], self.root_storage[0..root_count]);
112 @memcpy(seeds_copy[0..root_count], self.root_seed_masks[0..root_count]);
113 @memcpy(blocks_copy[0..block_count], self.blocks[0..block_count]);
114 var result = TestStore.init(self.entries, self.bytes, self.lookup);
115 result.committed_entries = committed_entries;
116 result.committed_bytes = committed_bytes;
117 result.staged_entries = committed_entries;
118 result.staged_bytes = committed_bytes;
119 result.root_count = root_count;
120 result.block_count = block_count;
121 @memcpy(result.root_storage[0..root_count], roots_copy[0..root_count]);
122 @memcpy(result.root_seed_masks[0..root_count], seeds_copy[0..root_count]);
123 @memcpy(result.blocks[0..block_count], blocks_copy[0..block_count]);
124 for (result.entries[0..committed_entries]) |*entry| entry.marked = false;
125 result.rebuildLookup();
126 return result;
127 }
128
129 fn storage(self: *TestStore) roots.Storage {
130 return .{ .context = self, .vtable = &vtable };
131 }
132
133 const vtable: roots.Storage.VTable = .{
134 .begin = begin,
135 .put = put,
136 .read_staged = readStaged,
137 .commit = commit,
138 .abort = abort,
139 .read = read,
140 .verify_block = verifyBlock,
141 .begin_collection = beginCollection,
142 .read_seed = readSeed,
143 .retain_object = retainObject,
144 .retain_block = retainBlock,
145 .commit_collection = commitCollection,
146 .abort_collection = abortCollection,
147 };
148
149 fn begin(context: *anyopaque, capacity: roots.Capacity) roots.StorageError!void {
150 const self = from(context);
151 if (self.active or self.collection_active) {
152 return error.RootTransactionInUse;
153 }
154 if (capacity.objects > self.entry_limit or capacity.bytes > self.byte_limit) {
155 return error.RootCapacityExceeded;
156 }
157 self.staged_entries = self.committed_entries;
158 self.staged_bytes = self.committed_bytes;
159 self.active = true;
160 if (self.takeCrash(.after_begin)) return error.RootWriteFailed;
161 }
162
163 fn put(
164 context: *anyopaque,
165 kind: roots.ObjectKind,
166 digest: os.abi.Digest,
167 content: []const u8,
168 ) roots.StorageError!void {
169 const self = from(context);
170 if (!self.active) return error.RootWriteFailed;
171 self.put_calls += 1;
172 if (self.find(kind, digest, self.staged_entries)) |entry| {
173 if (!std.mem.eql(u8, self.entryBytes(entry), content)) {
174 return error.RootCollision;
175 }
176 return;
177 }
178 const next_bytes = std.math.add(u64, self.staged_bytes, content.len) catch
179 return error.RootCapacityExceeded;
180 if (self.staged_entries >= self.entry_limit or next_bytes > self.byte_limit) {
181 return error.RootCapacityExceeded;
182 }
183 const offset: usize = @intCast(self.staged_bytes);
184 @memcpy(self.bytes[offset..][0..content.len], content);
185 const entry_index = self.staged_entries;
186 self.entries[entry_index] = .{
187 .kind = kind,
188 .digest = digest,
189 .offset = @intCast(offset),
190 .length = @intCast(content.len),
191 .marked = false,
192 };
193 try self.indexEntry(entry_index);
194 self.staged_entries += 1;
195 self.staged_bytes = next_bytes;
196 if (self.forge_page and !self.forged and kind == .page) {
197 self.bytes[offset] ^= 1;
198 self.forged = true;
199 }
200 if (kind == .manifest and self.takeCrash(.after_manifest_put)) {
201 return error.RootWriteFailed;
202 }
203 }
204
205 fn readStaged(
206 context: *anyopaque,
207 kind: roots.ObjectKind,
208 digest: os.abi.Digest,
209 output: []u8,
210 ) roots.StorageError!void {
211 const self = from(context);
212 if (!self.active) return error.RootReadFailed;
213 try self.copy(kind, digest, self.staged_entries, output);
214 if (kind == .manifest and self.takeCrash(.after_staged_read)) {
215 return error.RootReadFailed;
216 }
217 }
218
219 fn commit(
220 context: *anyopaque,
221 root: roots.ManifestRoot,
222 ) roots.StorageError!void {
223 const self = from(context);
224 if (self.takeCrash(.before_commit)) return error.RootWriteFailed;
225 if (!self.active or self.find(.manifest, root.digest, self.staged_entries) == null) {
226 return error.RootWriteFailed;
227 }
228 if (!self.hasRoot(root)) {
229 if (self.root_count == self.root_storage.len) {
230 return error.RootCapacityExceeded;
231 }
232 self.root_storage[self.root_count] = root;
233 self.root_seed_masks[self.root_count] = 0;
234 self.root_count += 1;
235 }
236 self.committed_entries = self.staged_entries;
237 self.committed_bytes = self.staged_bytes;
238 self.active = false;
239 if (self.takeCrash(.after_commit)) return error.RootWriteFailed;
240 }
241
242 fn abort(context: *anyopaque) void {
243 const self = from(context);
244 if (!self.active) return;
245 self.staged_entries = self.committed_entries;
246 self.staged_bytes = self.committed_bytes;
247 self.active = false;
248 self.rebuildLookup();
249 }
250
251 fn read(
252 context: *anyopaque,
253 kind: roots.ObjectKind,
254 digest: os.abi.Digest,
255 output: []u8,
256 ) roots.StorageError!void {
257 const self = from(context);
258 return self.copy(kind, digest, self.committed_entries, output);
259 }
260
261 fn verifyBlock(
262 context: *anyopaque,
263 block: os.abi.BlockRoot,
264 ) roots.StorageError!void {
265 const self = from(context);
266 self.block_verifications += 1;
267 if (!self.block_available) return error.RootReadFailed;
268 if (block.generation == 0) return error.RootReadFailed;
269 os.abi.wire.validateDigest(block.digest) catch return error.RootReadFailed;
270 if (self.blockIndex(block) == null) {
271 if (self.collection_active) return error.RootMissing;
272 if (self.block_count == self.blocks.len) {
273 return error.RootCapacityExceeded;
274 }
275 self.blocks[self.block_count] = block;
276 self.block_marked[self.block_count] = false;
277 self.block_count += 1;
278 }
279 }
280
281 fn beginCollection(
282 context: *anyopaque,
283 capacity: roots.maintenance.CollectionCapacity,
284 ) roots.StorageError!roots.maintenance.CollectionSnapshot {
285 const self = from(context);
286 if (self.active or self.collection_active) {
287 return error.RootTransactionInUse;
288 }
289 const snapshot = self.collectionSnapshot();
290 if (!snapshotFits(capacity.limits, snapshot)) {
291 return error.RootCollectionCapacityExceeded;
292 }
293 self.clearCollectionMarks();
294 self.collection_snapshot = snapshot;
295 self.collection_active = true;
296 return snapshot;
297 }
298
299 fn readSeed(
300 context: *anyopaque,
301 class: roots.maintenance.ReachabilityClass,
302 expected_index: u16,
303 ) roots.StorageError!roots.ManifestRoot {
304 const self = from(context);
305 if (!self.collection_active) return error.RootCollectionFailed;
306 var index: u16 = 0;
307 for (self.root_storage[0..self.root_count], 0..) |root, root_index| {
308 if (self.root_seed_masks[root_index] & classMask(class) == 0) continue;
309 if (index == expected_index) return root;
310 index += 1;
311 }
312 return error.RootSeedMissing;
313 }
314
315 fn retainObject(
316 context: *anyopaque,
317 kind: roots.ObjectKind,
318 digest: os.abi.Digest,
319 ) roots.StorageError!roots.maintenance.Retention {
320 const self = from(context);
321 if (!self.collection_active) return error.RootCollectionFailed;
322 if (self.hidden) |hidden| {
323 if (kind == hidden.kind and std.mem.eql(u8, &digest, &hidden.digest)) {
324 return error.RootMissing;
325 }
326 }
327 const index = self.findIndex(kind, digest, self.committed_entries) orelse
328 return error.RootMissing;
329 self.retain_calls += 1;
330 if (self.entries[index].marked) return .existing;
331 self.entries[index].marked = true;
332 return .fresh;
333 }
334
335 fn retainBlock(
336 context: *anyopaque,
337 block: os.abi.BlockRoot,
338 ) roots.StorageError!roots.maintenance.Retention {
339 const self = from(context);
340 if (!self.collection_active) return error.RootCollectionFailed;
341 const index = self.blockIndex(block) orelse return error.RootMissing;
342 self.retain_calls += 1;
343 if (self.block_marked[index]) return .existing;
344 self.block_marked[index] = true;
345 return .fresh;
346 }
347
348 fn commitCollection(
349 context: *anyopaque,
350 ) roots.StorageError!roots.maintenance.CollectionReport {
351 const self = from(context);
352 if (!self.collection_active) return error.RootCollectionFailed;
353 const snapshot = self.collection_snapshot;
354 const retained_objects = self.compactObjects();
355 const retained_roots = self.compactRoots();
356 const retained_blocks = self.compactBlocks();
357 self.collection_active = false;
358 self.collection_commits += 1;
359 return .{
360 .retained_roots = retained_roots,
361 .collected_roots = snapshot.roots - retained_roots,
362 .retained_objects = retained_objects,
363 .collected_objects = snapshot.objects - retained_objects,
364 .retained_blocks = retained_blocks,
365 .collected_blocks = snapshot.blocks - retained_blocks,
366 };
367 }
368
369 fn abortCollection(context: *anyopaque) void {
370 const self = from(context);
371 if (!self.collection_active) return;
372 self.clearCollectionMarks();
373 self.collection_active = false;
374 }
375
376 fn seedRoot(
377 self: *TestStore,
378 class: roots.maintenance.ReachabilityClass,
379 root: roots.ManifestRoot,
380 ) roots.StorageError!void {
381 if (self.active or self.collection_active) {
382 return error.RootTransactionInUse;
383 }
384 const index = self.rootIndex(root) orelse return error.RootSeedMissing;
385 self.root_seed_masks[index] |= classMask(class);
386 }
387
388 fn collectionSnapshot(
389 self: *const TestStore,
390 ) roots.maintenance.CollectionSnapshot {
391 return .{
392 .seeds = self.seedCounts(),
393 .roots = self.root_count,
394 .objects = self.committed_entries,
395 .blocks = self.block_count,
396 };
397 }
398
399 fn seedCounts(self: *const TestStore) roots.maintenance.SeedCounts {
400 var counts: roots.maintenance.SeedCounts = .{
401 .chic_worlds = 0,
402 .live_staged_instances = 0,
403 .retained_receipts = 0,
404 .concurrent_readers = 0,
405 };
406 for (self.root_seed_masks[0..self.root_count]) |mask| {
407 if (mask & classMask(.chic_world) != 0) counts.chic_worlds += 1;
408 if (mask & classMask(.live_staged_instance) != 0) {
409 counts.live_staged_instances += 1;
410 }
411 if (mask & classMask(.retained_receipt) != 0) {
412 counts.retained_receipts += 1;
413 }
414 if (mask & classMask(.concurrent_reader) != 0) {
415 counts.concurrent_readers += 1;
416 }
417 }
418 return counts;
419 }
420
421 fn compactObjects(self: *TestStore) u32 {
422 var retained: u32 = 0;
423 var retained_bytes: u64 = 0;
424 for (self.entries[0..self.committed_entries]) |entry| {
425 if (!entry.marked) continue;
426 const length: usize = entry.length;
427 const source: usize = entry.offset;
428 const destination: usize = @intCast(retained_bytes);
429 if (source != destination) {
430 std.mem.copyForwards(
431 u8,
432 self.bytes[destination..][0..length],
433 self.bytes[source..][0..length],
434 );
435 }
436 self.entries[retained] = .{
437 .kind = entry.kind,
438 .digest = entry.digest,
439 .offset = @intCast(destination),
440 .length = entry.length,
441 .marked = false,
442 };
443 retained += 1;
444 retained_bytes += entry.length;
445 }
446 self.committed_entries = retained;
447 self.committed_bytes = retained_bytes;
448 self.staged_entries = retained;
449 self.staged_bytes = retained_bytes;
450 self.rebuildLookup();
451 return retained;
452 }
453
454 fn compactRoots(self: *TestStore) u16 {
455 const original = self.root_count;
456 var retained: u8 = 0;
457 for (self.root_storage[0..original], 0..) |root, index| {
458 if (self.find(.manifest, root.digest, self.committed_entries) == null) {
459 continue;
460 }
461 self.root_storage[retained] = root;
462 self.root_seed_masks[retained] = self.root_seed_masks[index];
463 retained += 1;
464 }
465 @memset(self.root_seed_masks[retained..original], 0);
466 self.root_count = retained;
467 return retained;
468 }
469
470 fn compactBlocks(self: *TestStore) u16 {
471 const original = self.block_count;
472 var retained: u8 = 0;
473 for (self.blocks[0..original], 0..) |block, index| {
474 if (!self.block_marked[index]) continue;
475 self.blocks[retained] = block;
476 self.block_marked[retained] = false;
477 retained += 1;
478 }
479 @memset(self.block_marked[retained..original], false);
480 self.block_count = retained;
481 return retained;
482 }
483
484 fn clearCollectionMarks(self: *TestStore) void {
485 for (self.entries[0..self.committed_entries]) |*entry| {
486 entry.marked = false;
487 }
488 @memset(self.block_marked[0..self.block_count], false);
489 }
490
491 fn blockIndex(
492 self: *const TestStore,
493 block: os.abi.BlockRoot,
494 ) ?u8 {
495 for (self.blocks[0..self.block_count], 0..) |existing, index| {
496 if (std.meta.eql(existing, block)) return @intCast(index);
497 }
498 return null;
499 }
500
501 fn rootIndex(
502 self: *const TestStore,
503 root: roots.ManifestRoot,
504 ) ?u8 {
505 for (self.root_storage[0..self.root_count], 0..) |existing, index| {
506 if (std.meta.eql(existing, root)) return @intCast(index);
507 }
508 return null;
509 }
510
511 fn takeCrash(self: *TestStore, cut: CrashCut) bool {
512 if (self.crash_cut != cut) return false;
513 self.crash_cut = null;
514 return true;
515 }
516
517 fn snapshotFits(
518 limits: roots.maintenance.CollectionLimits,
519 snapshot: roots.maintenance.CollectionSnapshot,
520 ) bool {
521 inline for (.{
522 roots.maintenance.ReachabilityClass.chic_world,
523 roots.maintenance.ReachabilityClass.live_staged_instance,
524 roots.maintenance.ReachabilityClass.retained_receipt,
525 roots.maintenance.ReachabilityClass.concurrent_reader,
526 }) |class| {
527 if (snapshot.seeds.count(class) > limits.seeds.count(class)) {
528 return false;
529 }
530 }
531 return snapshot.roots <= limits.roots and
532 snapshot.objects <= limits.objects and
533 snapshot.blocks <= limits.blocks;
534 }
535
536 fn classMask(class: roots.maintenance.ReachabilityClass) u8 {
537 const shift: u3 = @intCast(@backingInt(class));
538 return @as(u8, 1) << shift;
539 }
540
541 fn copy(
542 self: *TestStore,
543 kind: roots.ObjectKind,
544 digest: os.abi.Digest,
545 count: u32,
546 output: []u8,
547 ) roots.StorageError!void {
548 if (self.hidden) |hidden| {
549 if (kind == hidden.kind and std.mem.eql(u8, &digest, &hidden.digest)) {
550 return error.RootMissing;
551 }
552 }
553 const entry = self.find(kind, digest, count) orelse
554 return error.RootMissing;
555 const content = self.entryBytes(entry);
556 if (content.len != output.len) return error.RootReadFailed;
557 @memcpy(output, content);
558 }
559
560 fn find(
561 self: *const TestStore,
562 kind: roots.ObjectKind,
563 digest: os.abi.Digest,
564 count: u32,
565 ) ?Entry {
566 const index = self.findIndex(kind, digest, count) orelse return null;
567 return self.entries[index];
568 }
569
570 fn findIndex(
571 self: *const TestStore,
572 kind: roots.ObjectKind,
573 digest: os.abi.Digest,
574 count: u32,
575 ) ?u32 {
576 var slot = self.lookupSlot(kind, digest);
577 for (0..self.lookup.len) |_| {
578 const encoded_index = self.lookup[slot];
579 if (encoded_index == 0) return null;
580 const entry_index = encoded_index - 1;
581 if (entry_index < count) {
582 const entry = self.entries[entry_index];
583 if (entry.kind == kind and
584 std.mem.eql(u8, &entry.digest, &digest)) return entry_index;
585 }
586 slot = (slot + 1) & (self.lookup.len - 1);
587 }
588 return null;
589 }
590
591 fn indexEntry(self: *TestStore, entry_index: u32) roots.StorageError!void {
592 const entry = self.entries[entry_index];
593 var slot = self.lookupSlot(entry.kind, entry.digest);
594 for (0..self.lookup.len) |_| {
595 if (self.lookup[slot] == 0) {
596 self.lookup[slot] = entry_index + 1;
597 return;
598 }
599 slot = (slot + 1) & (self.lookup.len - 1);
600 }
601 return error.RootCapacityExceeded;
602 }
603
604 fn rebuildLookup(self: *TestStore) void {
605 @memset(self.lookup, 0);
606 for (0..self.committed_entries) |entry_index| {
607 self.indexEntry(@intCast(entry_index)) catch unreachable;
608 }
609 }
610
611 fn lookupSlot(
612 self: *const TestStore,
613 kind: roots.ObjectKind,
614 digest: os.abi.Digest,
615 ) usize {
616 var hash: u64 = 14_695_981_039_346_656_037;
617 hash = (hash ^ @backingInt(kind)) *% 1_099_511_628_211;
618 for (digest) |byte| {
619 hash = (hash ^ byte) *% 1_099_511_628_211;
620 }
621 return @intCast(hash & @as(u64, @intCast(self.lookup.len - 1)));
622 }
623
624 fn entryBytes(self: *const TestStore, entry: Entry) []const u8 {
625 const offset: usize = entry.offset;
626 return self.bytes[offset..][0..entry.length];
627 }
628
629 fn hasRoot(self: *const TestStore, root: roots.ManifestRoot) bool {
630 for (self.root_storage[0..self.root_count]) |existing| {
631 if (std.meta.eql(existing, root)) return true;
632 }
633 return false;
634 }
635
636 fn firstEntry(self: *const TestStore, kind: roots.ObjectKind) Entry {
637 for (self.entries[0..self.committed_entries]) |entry| {
638 if (entry.kind == kind) return entry;
639 }
640 unreachable;
641 }
642
643 fn lastEntrySince(
644 self: *const TestStore,
645 kind: roots.ObjectKind,
646 first: u32,
647 ) Entry {
648 var index = self.committed_entries;
649 while (index > first) {
650 index -= 1;
651 const entry = self.entries[index];
652 if (entry.kind == kind) return entry;
653 }
654 unreachable;
655 }
656
657 fn from(context: *anyopaque) *TestStore {
658 return @ptrCast(@alignCast(context));
659 }
660 };
661
662 const delta_extra_objects: usize = 256;
663 const delta_extra_bytes: usize = 1024 * 1024;
664 var entry_storage: [roots.publication_capacity.objects + delta_extra_objects]Entry =
665 undefined;
666 var object_bytes: [roots.publication_capacity.bytes + delta_extra_bytes]u8 =
667 undefined;
668 var lookup_storage: [131_072]u32 = undefined;
669 var published_ram: [checkpoint.ram_bytes]u8 align(checkpoint.ram_alignment) = undefined;
670 var reopened_ram: [checkpoint.ram_bytes]u8 align(checkpoint.ram_alignment) = undefined;
671 const delta_page_limit: usize = 64;
672 const DeltaPageStorage = [delta_page_limit * checkpoint.hot.page_bytes]u8;
673 var delta_indices: [delta_page_limit]u16 = undefined;
674 var delta_pages: DeltaPageStorage align(checkpoint.hot.page_bytes) = undefined;
675 const branch_page_limit: usize = 128;
676 const BranchPageStorage = [branch_page_limit * roots.page_bytes]u8;
677 var first_branch_indices: [branch_page_limit]u16 = undefined;
678 var first_branch_pages: BranchPageStorage align(roots.page_bytes) = undefined;
679 var first_branch_authenticated: [roots.branch.authentication_word_count]u64 = undefined;
680 var first_branch_digests: [roots.page_count]os.abi.Digest = undefined;
681 var second_branch_indices: [branch_page_limit]u16 = undefined;
682 var second_branch_pages: BranchPageStorage align(roots.page_bytes) = undefined;
683 var second_branch_authenticated: [roots.branch.authentication_word_count]u64 = undefined;
684 var second_branch_digests: [roots.page_count]os.abi.Digest = undefined;
685
686 const DeltaWitness = struct {
687 parent: roots.Binding,
688 first_delta: roots.Binding,
689 full: roots.Binding,
690 root: roots.ManifestRoot,
691 manifest: roots.Manifest,
692 missing: Entry,
693 };
694
695 test "immutable roots deduplicate, isolate pages, and reject unsafe publication" {
696 var checkpoint_storage = checkpoint.Storage.init();
697 const captured = try capture(&checkpoint_storage, &published_ram);
698 var store = TestStore.init(&entry_storage, &object_bytes, &lookup_storage);
699 const injected = store.storage();
700
701 const first = try expectOneDeduplicatedRoot(&store, injected, &captured);
702 try expectReopenedRootOwnsBytes(&store, injected, first);
703 try expectEveryReopenDigestVerified(&store, injected, first.root);
704 try expectForgedDigestRejected(&store, &captured);
705 try expectCapacityRejectedBeforeWrites(&store, &captured);
706 }
707
708 test "dirty delta roots match full capture and reopen after restart" {
709 var store = TestStore.init(&entry_storage, &object_bytes, &lookup_storage);
710 const witness = try publishDeltaChain(&store);
711 @memset(&published_ram, 0xa1);
712 @memset(&reopened_ram, 0xa2);
713 @memset(&delta_indices, 0);
714 @memset(&delta_pages, 0xa3);
715
716 var restarted = store.restart();
717 const reopened = try roots.reopen(restarted.storage(), witness.root);
718 try std.testing.expectEqualDeep(witness.manifest, reopened);
719 try std.testing.expectEqual(@as(u32, 2), restarted.block_verifications);
720
721 restarted.hidden = .{
722 .kind = witness.missing.kind,
723 .digest = witness.missing.digest,
724 };
725 try std.testing.expectError(
726 error.RootMissing,
727 roots.reopen(restarted.storage(), witness.root),
728 );
729 }
730
731 test "compacted checkpoint chain preserves bytes and machine digest" {
732 var store = TestStore.init(&entry_storage, &object_bytes, &lookup_storage);
733 const witness = try publishDeltaChain(&store);
734 const compacted = try roots.maintenance.compact(
735 store.storage(),
736 witness.root,
737 );
738 const old = try roots.materialize(
739 store.storage(),
740 witness.root,
741 &published_ram,
742 );
743 const new = try roots.materialize(
744 store.storage(),
745 compacted.root,
746 &reopened_ram,
747 );
748 try std.testing.expect(old.parent != null);
749 try std.testing.expect(new.parent == null);
750 try std.testing.expect(!std.meta.eql(witness.root, compacted.root));
751 try std.testing.expectEqualDeep(old.machine, new.machine);
752 try std.testing.expectEqualDeep(old.memory, new.memory);
753 try std.testing.expectEqualDeep(old.pages, new.pages);
754 try std.testing.expectEqualSlices(u8, &published_ram, &reopened_ram);
755 }
756
757 test "collection retains every root class and removes an orphan" {
758 var store = TestStore.init(&entry_storage, &object_bytes, &lookup_storage);
759 const witness = try publishDeltaChain(&store);
760 const compacted = try roots.maintenance.compact(
761 store.storage(),
762 witness.root,
763 );
764 var reader = try restoreFirstBranch(&store, witness.first_delta.root, 0);
765 try store.seedRoot(.chic_world, compacted.root);
766 try store.seedRoot(.live_staged_instance, witness.full.root);
767 try store.seedRoot(.retained_receipt, witness.parent.root);
768 try store.seedRoot(.concurrent_reader, reader.root);
769 const capacity = try exactCollectionCapacity(&store);
770 const report = try roots.maintenance.collect(store.storage(), capacity);
771 try std.testing.expect(capacity.metadata_bytes >= capacity.limits.objects);
772 try std.testing.expect(@as(u64, store.retain_calls) <= capacity.work);
773 try std.testing.expect(report.collected_roots > 0);
774 try std.testing.expect(report.collected_objects > 0);
775 _ = try roots.reopen(store.storage(), compacted.root);
776 _ = try roots.reopen(store.storage(), witness.full.root);
777 _ = try roots.reopen(store.storage(), witness.parent.root);
778 var byte: [1]u8 = undefined;
779 try reader.read(0, &byte);
780 try std.testing.expectError(
781 error.RootMissing,
782 roots.reopen(store.storage(), witness.root),
783 );
784 }
785
786 test "collection capacity refuses before classification" {
787 var store = TestStore.init(&entry_storage, &object_bytes, &lookup_storage);
788 const witness = try publishDeltaChain(&store);
789 try store.seedRoot(.chic_world, witness.root);
790 const before = store.collectionSnapshot();
791 var limits = collectionLimits(before);
792 limits.objects -= 1;
793 const capacity = try roots.maintenance.CollectionCapacity.derive(limits);
794 try std.testing.expectError(
795 error.RootCollectionCapacityExceeded,
796 roots.maintenance.collect(store.storage(), capacity),
797 );
798 try std.testing.expectEqual(@as(u32, 0), store.retain_calls);
799 try std.testing.expectEqual(@as(u16, 0), store.collection_commits);
800 try std.testing.expectEqualDeep(before, store.collectionSnapshot());
801 _ = try roots.reopen(store.storage(), witness.root);
802 }
803
804 test "compaction crash cuts expose only complete roots" {
805 var store = TestStore.init(&entry_storage, &object_bytes, &lookup_storage);
806 const witness = try publishDeltaChain(&store);
807 const entries = store.committed_entries;
808 const bytes = store.committed_bytes;
809 const root_count = store.root_count;
810 const cuts = [_]CrashCut{
811 .after_begin,
812 .after_manifest_put,
813 .after_staged_read,
814 .before_commit,
815 .after_commit,
816 };
817 for (cuts) |cut| {
818 restoreCrashBaseline(&store, entries, bytes, root_count);
819 store.crash_cut = cut;
820 try expectCompactionCrash(&store, witness.root, cut);
821 var restarted = store.restart();
822 try expectCompletePublishedRoots(&restarted);
823 _ = try roots.reopen(restarted.storage(), witness.root);
824 if (cut == .after_commit) {
825 try std.testing.expectEqual(root_count + 1, restarted.root_count);
826 const compacted = restarted.root_storage[root_count];
827 const value = try roots.reopen(restarted.storage(), compacted);
828 try std.testing.expect(value.parent == null);
829 try std.testing.expectEqualDeep(witness.manifest.machine, value.machine);
830 } else {
831 try std.testing.expectEqual(root_count, restarted.root_count);
832 }
833 }
834 }
835
836 test "shared root branch untouched residency is metadata only" {
837 var checkpoint_storage = checkpoint.Storage.init();
838 const captured = try capture(&checkpoint_storage, &published_ram);
839 var store = TestStore.init(&entry_storage, &object_bytes, &lookup_storage);
840 const binding = try roots.bind(store.storage(), &captured);
841 const committed_bytes = store.committed_bytes;
842 const put_calls = store.put_calls;
843 const branch = try restoreFirstBranch(&store, binding.root, 0);
844 try expectUntouchedBranchResidentBound(&branch);
845 try std.testing.expectEqual(committed_bytes, store.committed_bytes);
846 try std.testing.expectEqual(put_calls, store.put_calls);
847 }
848
849 test "shared root branches authenticate lazily and isolate private writes" {
850 var store = TestStore.init(&entry_storage, &object_bytes, &lookup_storage);
851 const witness = try publishDeltaChain(&store);
852 var first = try restoreFirstBranch(&store, witness.root, branch_page_limit);
853 var second = try restoreSecondBranch(&store, witness.root, branch_page_limit);
854 try expectUntouchedBranchResidentBound(&first);
855
856 const address = checkpoint.ram_bytes - roots.page_bytes;
857 var original: [1]u8 = undefined;
858 try first.read(address, &original);
859 try second.read(address, &original);
860 const committed_bytes = store.committed_bytes;
861 const put_calls = store.put_calls;
862 try first.write(address, &.{0xa1});
863 try expectBranchByte(&first, address, 0xa1);
864 try expectBranchByte(&second, address, original[0]);
865 try second.write(address, &.{0xb2});
866 try expectBranchByte(&first, address, 0xa1);
867 try expectBranchByte(&second, address, 0xb2);
868 try std.testing.expectEqual(@as(u16, 1), first.privatePageCount());
869 try std.testing.expectEqual(@as(u16, 1), second.privatePageCount());
870 try std.testing.expectEqual(committed_bytes, store.committed_bytes);
871 try std.testing.expectEqual(put_calls, store.put_calls);
872 }
873
874 test "shared root branch rejects sabotage at first page touch" {
875 var checkpoint_storage = checkpoint.Storage.init();
876 const captured = try capture(&checkpoint_storage, &published_ram);
877 var store = TestStore.init(&entry_storage, &object_bytes, &lookup_storage);
878 const binding = try roots.bind(store.storage(), &captured);
879 var branch = try restoreFirstBranch(&store, binding.root, 1);
880 const page = store.firstEntry(.page);
881 store.bytes[page.offset] ^= 1;
882 var byte: [1]u8 = undefined;
883 try std.testing.expectError(error.RootObjectCorrupt, branch.read(0, &byte));
884 store.bytes[page.offset] ^= 1;
885 try branch.read(0, &byte);
886 try std.testing.expectEqual(published_ram[0], byte[0]);
887 }
888
889 test "shared root branch pool exhaustion refuses before writes" {
890 var checkpoint_storage = checkpoint.Storage.init();
891 const captured = try capture(&checkpoint_storage, &published_ram);
892 var store = TestStore.init(&entry_storage, &object_bytes, &lookup_storage);
893 const binding = try roots.bind(store.storage(), &captured);
894 var branch = try restoreFirstBranch(&store, binding.root, 1);
895 const address = roots.page_bytes - 1;
896 var before: [2]u8 = undefined;
897 try branch.read(address, &before);
898 try std.testing.expectError(
899 error.BranchPageCapacityExceeded,
900 branch.write(address, &.{ 0x91, 0x92 }),
901 );
902 try std.testing.expectEqual(@as(u16, 0), branch.privatePageCount());
903 var after: [2]u8 = undefined;
904 try branch.read(address, &after);
905 try std.testing.expectEqualSlices(u8, &before, &after);
906 }
907
908 test "instances restore shared roots and keep branch writes private" {
909 var store = TestStore.init(&entry_storage, &object_bytes, &lookup_storage);
910 const witness = try publishDeltaChain(&store);
911 const execution = try os.boot.kernel.manifest.parse(k0_manifest);
912 const selected = input(execution);
913 const restore_input: instance.SharedRestoreInput = .{
914 .expected_root = witness.root,
915 .profile = selected.profile,
916 .execution_manifest = k0_manifest,
917 .fence = selected.fence,
918 };
919 var first_owner = instance.Storage.init();
920 var second_owner = instance.Storage.init();
921 var first = try expectSharedInstance(
922 &first_owner,
923 store.storage(),
924 try firstBranchStorage(branch_page_limit),
925 restore_input,
926 );
927 defer first.deinit();
928 var second = try expectSharedInstance(
929 &second_owner,
930 store.storage(),
931 try secondBranchStorage(branch_page_limit),
932 restore_input,
933 );
934 defer second.deinit();
935
936 try std.testing.expectEqual(@as(usize, 0), first.ram.len);
937 try std.testing.expectEqual(
938 @as(u16, @intCast(core.layout.reactivation_page_count)),
939 try first.privatePageCount(),
940 );
941 try std.testing.expect(
942 try first.residentMemoryBytes() < checkpoint.ram_bytes / 16,
943 );
944 const ready = try first.run();
945 try std.testing.expectEqual(
946 os.abi.channel.DoorbellCode.ready,
947 ready.doorbell.code,
948 );
949 var activation: instance.EventBatch = undefined;
950 try first.takeEvents(&activation);
951 const address = checkpoint.ram_bytes - roots.page_bytes;
952 var original: [1]u8 = undefined;
953 try first.readMemory(address, &original);
954 const committed_bytes = store.committed_bytes;
955 const put_calls = store.put_calls;
956 try first.writeMemory(address, &.{0xa1});
957 try expectInstanceByte(&first, address, 0xa1);
958 try expectInstanceByte(&second, address, original[0]);
959 try second.writeMemory(address, &.{0xb2});
960 try expectInstanceByte(&first, address, 0xa1);
961 try expectInstanceByte(&second, address, 0xb2);
962 try std.testing.expectEqual(committed_bytes, store.committed_bytes);
963 try std.testing.expectEqual(put_calls, store.put_calls);
964
965 first.deinit();
966 @memset(&first_branch_pages, 0xa5);
967 var exhausted_owner = instance.Storage.init();
968 const exhausted = instance.Instance.restoreShared(
969 &exhausted_owner,
970 store.storage(),
971 try firstBranchStorage(core.layout.reactivation_page_count - 1),
972 restore_input,
973 );
974 switch (exhausted) {
975 .rejected => |failure| try std.testing.expectEqual(
976 error.MemoryCapacityExceeded,
977 failure,
978 ),
979 .ready, .unavailable => return error.ExpectedCapacityRefusal,
980 }
981 try std.testing.expect(std.mem.allEqual(
982 u8,
983 first_branch_pages[0 .. (core.layout.reactivation_page_count - 1) * roots.page_bytes],
984 0xa5,
985 ));
986 }
987
988 test "immutable root publication capacity rejects max plus one" {
989 var page_limits = roots.publication_limits;
990 page_limits.pages += 1;
991 try std.testing.expectError(
992 error.PageCapacityExceeded,
993 roots.Capacity.derive(page_limits),
994 );
995 var node_limits = roots.publication_limits;
996 node_limits.nodes += 1;
997 try std.testing.expectError(
998 error.NodeCapacityExceeded,
999 roots.Capacity.derive(node_limits),
1000 );
1001 var manifest_limits = roots.publication_limits;
1002 manifest_limits.manifests += 1;
1003 try std.testing.expectError(
1004 error.ManifestCapacityExceeded,
1005 roots.Capacity.derive(manifest_limits),
1006 );
1007 }
1008
1009 fn exactCollectionCapacity(
1010 store: *const TestStore,
1011 ) !roots.maintenance.CollectionCapacity {
1012 return roots.maintenance.CollectionCapacity.derive(
1013 collectionLimits(store.collectionSnapshot()),
1014 );
1015 }
1016
1017 fn collectionLimits(
1018 snapshot: roots.maintenance.CollectionSnapshot,
1019 ) roots.maintenance.CollectionLimits {
1020 return .{
1021 .seeds = snapshot.seeds,
1022 .roots = snapshot.roots,
1023 .objects = snapshot.objects,
1024 .blocks = snapshot.blocks,
1025 };
1026 }
1027
1028 fn restoreCrashBaseline(
1029 store: *TestStore,
1030 entries: u32,
1031 bytes: u64,
1032 root_count: u8,
1033 ) void {
1034 store.committed_entries = entries;
1035 store.committed_bytes = bytes;
1036 store.staged_entries = entries;
1037 store.staged_bytes = bytes;
1038 store.root_count = root_count;
1039 store.active = false;
1040 store.collection_active = false;
1041 store.crash_cut = null;
1042 store.rebuildLookup();
1043 }
1044
1045 fn expectCompactionCrash(
1046 store: *TestStore,
1047 root: roots.ManifestRoot,
1048 cut: CrashCut,
1049 ) !void {
1050 switch (cut) {
1051 .after_staged_read => try std.testing.expectError(
1052 error.RootReadFailed,
1053 roots.maintenance.compact(store.storage(), root),
1054 ),
1055 else => try std.testing.expectError(
1056 error.RootWriteFailed,
1057 roots.maintenance.compact(store.storage(), root),
1058 ),
1059 }
1060 }
1061
1062 fn expectCompletePublishedRoots(store: *TestStore) !void {
1063 for (store.root_storage[0..store.root_count]) |root| {
1064 _ = try roots.reopen(store.storage(), root);
1065 }
1066 }
1067
1068 fn publishDeltaChain(store: *TestStore) !DeltaWitness {
1069 const execution = try os.boot.kernel.manifest.parse(k0_manifest);
1070 const selected = input(execution);
1071 var live_storage = instance.Storage.init();
1072 var machine = switch (instance.Instance.init(
1073 &live_storage,
1074 &fixture.ram,
1075 selected,
1076 )) {
1077 .ready => |value| value,
1078 .unavailable => return error.UnexpectedBackendUnavailable,
1079 .rejected => |failure| return failure,
1080 };
1081 defer machine.deinit();
1082 try driveToReceipt(&machine, selected.fence, 0);
1083
1084 var parent_storage = checkpoint.Storage.init();
1085 const parent = try machine.captureCheckpoint(&parent_storage, &published_ram);
1086 const parent_binding = try roots.bind(store.storage(), &parent);
1087 dirtyLastPages(&machine, 2, 0x41);
1088 const hot_storage = try checkpoint.hot.Storage.init(
1089 &delta_indices,
1090 &delta_pages,
1091 );
1092 const first_snapshot = try machine.captureHot(&parent, hot_storage);
1093 try std.testing.expectEqual(@as(u16, 2), first_snapshot.dirtyPageCount());
1094 try std.testing.expectEqualDeep(
1095 parent_binding.manifest.block,
1096 first_snapshot.material.receipt.block_root,
1097 );
1098 try expectDeltaCapacityRejected(store, parent_binding.root, &first_snapshot);
1099 const first_put = store.put_calls;
1100 const first_block_verifications = store.block_verifications;
1101 const first_delta = try roots.bindDelta(
1102 store.storage(),
1103 parent_binding.root,
1104 &first_snapshot,
1105 );
1106 try std.testing.expectEqual(@as(u32, 19), store.put_calls - first_put);
1107 try std.testing.expectEqual(
1108 first_block_verifications,
1109 store.block_verifications,
1110 );
1111
1112 var full_storage = checkpoint.Storage.init();
1113 const full = try machine.captureCheckpoint(&full_storage, &reopened_ram);
1114 const full_binding = try roots.bind(store.storage(), &full);
1115 try expectSameMachine(first_delta, full_binding);
1116 return publishNextDelta(
1117 store,
1118 &machine,
1119 parent_binding,
1120 first_delta,
1121 full_binding,
1122 &full,
1123 hot_storage,
1124 selected.fence,
1125 );
1126 }
1127
1128 fn restoreFirstBranch(
1129 store: *TestStore,
1130 root: roots.ManifestRoot,
1131 pages: usize,
1132 ) !roots.branch.Branch {
1133 return roots.branch.restore(
1134 store.storage(),
1135 root,
1136 try firstBranchStorage(pages),
1137 );
1138 }
1139
1140 fn firstBranchStorage(pages: usize) !roots.branch.Storage {
1141 return roots.branch.Storage.init(
1142 first_branch_indices[0..pages],
1143 first_branch_pages[0 .. pages * roots.page_bytes],
1144 &first_branch_authenticated,
1145 &first_branch_digests,
1146 );
1147 }
1148
1149 fn restoreSecondBranch(
1150 store: *TestStore,
1151 root: roots.ManifestRoot,
1152 pages: usize,
1153 ) !roots.branch.Branch {
1154 return roots.branch.restore(
1155 store.storage(),
1156 root,
1157 try secondBranchStorage(pages),
1158 );
1159 }
1160
1161 fn secondBranchStorage(pages: usize) !roots.branch.Storage {
1162 return roots.branch.Storage.init(
1163 second_branch_indices[0..pages],
1164 second_branch_pages[0 .. pages * roots.page_bytes],
1165 &second_branch_authenticated,
1166 &second_branch_digests,
1167 );
1168 }
1169
1170 fn expectUntouchedBranchResidentBound(branch: *const roots.branch.Branch) !void {
1171 try std.testing.expectEqual(@as(u16, 0), branch.privatePageCount());
1172 try std.testing.expect(branch.residentBytes() < 4 * roots.page_bytes);
1173 try std.testing.expect(branch.residentBytes() * 1_024 < checkpoint.ram_bytes);
1174 }
1175
1176 fn expectBranchByte(
1177 branch: *roots.branch.Branch,
1178 address: usize,
1179 expected: u8,
1180 ) !void {
1181 var actual: [1]u8 = undefined;
1182 try branch.read(address, &actual);
1183 try std.testing.expectEqual(expected, actual[0]);
1184 }
1185
1186 fn expectSharedInstance(
1187 storage: *instance.Storage,
1188 root_storage: roots.Storage,
1189 branch_storage: roots.branch.Storage,
1190 input_value: instance.SharedRestoreInput,
1191 ) !instance.Instance {
1192 return switch (instance.Instance.restoreShared(
1193 storage,
1194 root_storage,
1195 branch_storage,
1196 input_value,
1197 )) {
1198 .ready => |value| value,
1199 .unavailable => error.UnexpectedBackendUnavailable,
1200 .rejected => |failure| failure,
1201 };
1202 }
1203
1204 fn expectInstanceByte(
1205 machine: *const instance.Instance,
1206 address: usize,
1207 expected: u8,
1208 ) !void {
1209 var actual: [1]u8 = undefined;
1210 try machine.readMemory(address, &actual);
1211 try std.testing.expectEqual(expected, actual[0]);
1212 }
1213
1214 fn publishNextDelta(
1215 store: *TestStore,
1216 machine: *instance.Instance,
1217 parent: roots.Binding,
1218 first_delta: roots.Binding,
1219 full_binding: roots.Binding,
1220 full: *const checkpoint.Checkpoint,
1221 hot_storage: checkpoint.hot.Storage,
1222 fence: os.abi.ActivationFence,
1223 ) !DeltaWitness {
1224 var next = fence;
1225 next.generation += 1;
1226 next.token = @splat(0x66);
1227 try machine.reactivate(next);
1228 try driveToReceipt(machine, next, os.k0.request_bytes.len);
1229 dirtyLastPages(machine, 3, 0x52);
1230 const second_snapshot = try machine.captureHot(full, hot_storage);
1231 try std.testing.expect(second_snapshot.dirtyPageCount() > 0);
1232 try std.testing.expect(!std.meta.eql(
1233 first_delta.manifest.block,
1234 second_snapshot.material.receipt.block_root,
1235 ));
1236 const before_entries = store.committed_entries;
1237 const block_verifications = store.block_verifications;
1238 const second_delta = try roots.bindDelta(
1239 store.storage(),
1240 first_delta.root,
1241 &second_snapshot,
1242 );
1243 try std.testing.expectEqual(
1244 block_verifications + 1,
1245 store.block_verifications,
1246 );
1247 return .{
1248 .parent = parent,
1249 .first_delta = first_delta,
1250 .full = full_binding,
1251 .root = second_delta.root,
1252 .manifest = second_delta.manifest,
1253 .missing = store.lastEntrySince(.page, before_entries),
1254 };
1255 }
1256
1257 fn dirtyLastPages(machine: *instance.Instance, count: usize, byte: u8) void {
1258 std.debug.assert(count <= delta_page_limit);
1259 for (0..count) |offset| {
1260 const page_index = checkpoint.page_count - 1 - offset;
1261 machine.ram[page_index * checkpoint.ram_alignment] = byte + @as(
1262 u8,
1263 @intCast(offset),
1264 );
1265 }
1266 }
1267
1268 fn expectDeltaCapacityRejected(
1269 store: *TestStore,
1270 parent: roots.ManifestRoot,
1271 snapshot: *const checkpoint.hot.Snapshot,
1272 ) !void {
1273 const entry_limit = store.entry_limit;
1274 const roots_before = store.root_count;
1275 const puts_before = store.put_calls;
1276 store.entry_limit = 0;
1277 defer store.entry_limit = entry_limit;
1278 try std.testing.expectError(
1279 error.RootCapacityExceeded,
1280 roots.bindDelta(store.storage(), parent, snapshot),
1281 );
1282 try std.testing.expectEqual(roots_before, store.root_count);
1283 try std.testing.expectEqual(puts_before, store.put_calls);
1284 try std.testing.expect(!store.active);
1285 }
1286
1287 fn expectSameMachine(delta: roots.Binding, full: roots.Binding) !void {
1288 try std.testing.expectEqualDeep(delta.manifest.machine, full.manifest.machine);
1289 try std.testing.expectEqualDeep(delta.manifest.memory, full.manifest.memory);
1290 try std.testing.expectEqualDeep(delta.manifest.pages, full.manifest.pages);
1291 try std.testing.expect(delta.manifest.parent != null);
1292 try std.testing.expect(full.manifest.parent == null);
1293 }
1294
1295 fn expectNothingPublished(store: *const TestStore) !void {
1296 try std.testing.expectEqual(@as(u32, 0), store.committed_entries);
1297 try std.testing.expectEqual(@as(u64, 0), store.committed_bytes);
1298 try std.testing.expectEqual(@as(u8, 0), store.root_count);
1299 try std.testing.expect(!store.active);
1300 }
1301
1302 fn expectOneDeduplicatedRoot(
1303 store: *TestStore,
1304 storage: roots.Storage,
1305 captured: *const checkpoint.Checkpoint,
1306 ) !roots.Binding {
1307 const first = try roots.bind(storage, captured);
1308 const object_count = store.committed_entries;
1309 const stored_bytes = store.committed_bytes;
1310 const second = try roots.bind(storage, captured);
1311 try std.testing.expectEqualDeep(first, second);
1312 try std.testing.expectEqual(object_count, store.committed_entries);
1313 try std.testing.expectEqual(stored_bytes, store.committed_bytes);
1314 try std.testing.expectEqual(@as(u8, 1), store.root_count);
1315 return first;
1316 }
1317
1318 fn expectReopenedRootOwnsBytes(
1319 store: *TestStore,
1320 storage: roots.Storage,
1321 first: roots.Binding,
1322 ) !void {
1323 const reopened = try roots.reopen(storage, first.root);
1324 try std.testing.expectEqualDeep(first.manifest, reopened);
1325 const changed_index = published_ram.len - 1;
1326 const original = published_ram[changed_index];
1327 published_ram[changed_index] ^= 1;
1328 const materialized = try roots.materialize(storage, first.root, &reopened_ram);
1329 try std.testing.expectEqualDeep(first.manifest, materialized);
1330 try std.testing.expectEqual(original, reopened_ram[changed_index]);
1331 try std.testing.expect(published_ram[changed_index] != reopened_ram[changed_index]);
1332 published_ram[changed_index] = original;
1333 try std.testing.expectEqual(@as(u32, 4), store.block_verifications);
1334 }
1335
1336 fn expectEveryReopenDigestVerified(
1337 store: *TestStore,
1338 storage: roots.Storage,
1339 root: roots.ManifestRoot,
1340 ) !void {
1341 const page = store.firstEntry(.page);
1342 store.bytes[page.offset] ^= 1;
1343 try std.testing.expectError(error.RootObjectCorrupt, roots.reopen(storage, root));
1344 store.bytes[page.offset] ^= 1;
1345
1346 const node = store.firstEntry(.node);
1347 store.bytes[node.offset] ^= 1;
1348 try std.testing.expectError(error.RootObjectCorrupt, roots.reopen(storage, root));
1349 store.bytes[node.offset] ^= 1;
1350
1351 const manifest = store.firstEntry(.manifest);
1352 store.bytes[manifest.offset] ^= 1;
1353 try std.testing.expectError(error.ManifestRootMismatch, roots.reopen(storage, root));
1354 store.bytes[manifest.offset] ^= 1;
1355
1356 store.block_available = false;
1357 try std.testing.expectError(error.RootReadFailed, roots.reopen(storage, root));
1358 store.block_available = true;
1359 try std.testing.expectEqual(@as(u32, 7), store.block_verifications);
1360 }
1361
1362 fn expectForgedDigestRejected(
1363 store: *TestStore,
1364 captured: *const checkpoint.Checkpoint,
1365 ) !void {
1366 store.reset();
1367 store.forge_page = true;
1368 try std.testing.expectError(
1369 error.RootObjectCorrupt,
1370 roots.bind(store.storage(), captured),
1371 );
1372 try std.testing.expect(store.forged);
1373 try expectNothingPublished(store);
1374 }
1375
1376 fn expectCapacityRejectedBeforeWrites(
1377 store: *TestStore,
1378 captured: *const checkpoint.Checkpoint,
1379 ) !void {
1380 store.reset();
1381 store.entry_limit = roots.publication_capacity.objects - 1;
1382 try std.testing.expectError(
1383 error.RootCapacityExceeded,
1384 roots.bind(store.storage(), captured),
1385 );
1386 try std.testing.expectEqual(@as(u32, 0), store.put_calls);
1387 try expectNothingPublished(store);
1388 }
1389
1390 fn capture(
1391 storage: *checkpoint.Storage,
1392 destination: []align(checkpoint.ram_alignment) u8,
1393 ) !checkpoint.Checkpoint {
1394 const execution = try os.boot.kernel.manifest.parse(k0_manifest);
1395 const selected = input(execution);
1396 var live_storage = instance.Storage.init();
1397 var machine = switch (instance.Instance.init(
1398 &live_storage,
1399 &fixture.ram,
1400 selected,
1401 )) {
1402 .ready => |value| value,
1403 .unavailable => return error.UnexpectedBackendUnavailable,
1404 .rejected => |failure| return failure,
1405 };
1406 defer machine.deinit();
1407 try driveToReceipt(&machine, selected.fence, 0);
1408 return machine.captureCheckpoint(storage, destination);
1409 }
1410
1411 fn driveToReceipt(
1412 machine: *instance.Instance,
1413 fence: os.abi.ActivationFence,
1414 terminal_offset: u64,
1415 ) !void {
1416 const ready = try machine.run();
1417 try std.testing.expectEqual(os.abi.channel.DoorbellCode.ready, ready.doorbell.code);
1418 var events: instance.EventBatch = undefined;
1419 try machine.takeEvents(&events);
1420 const basis = try machine.admissionBasis();
1421 const admitted = try admission.prepare(
1422 basis,
1423 try admission.terminal(
1424 terminal_offset,
1425 os.k0.request_bytes,
1426 ),
1427 );
1428 const delivery = try admission.bindDelivery(admitted, @splat(0x88), fence);
1429 try machine.deliverAdmitted(&delivery);
1430 const quiescent = try machine.run();
1431 try std.testing.expectEqual(
1432 os.abi.channel.DoorbellCode.quiescent,
1433 quiescent.doorbell.code,
1434 );
1435 try machine.takeEvents(&events);
1436 try machine.acknowledge(delivery.receipt);
1437 _ = try machine.quiescenceReceipt();
1438 }
1439
1440 fn input(execution: os.boot.kernel.manifest.View) instance.Input {
1441 return .{
1442 .profile = profile.interpretedContinuationTestV1(),
1443 .elf = k0_elf,
1444 .execution_manifest = k0_manifest,
1445 .expected_execution_fingerprint = execution.header.fingerprint,
1446 .fence = .{
1447 .world = @splat(0x44),
1448 .generation = 1,
1449 .token = @splat(0x55),
1450 },
1451 .initial_time_tick = 7,
1452 .entropy_generation = 1,
1453 .terminal_offset = 0,
1454 .effect_frontier = 0,
1455 .block_root = @splat(0x33),
1456 .source_root = @splat(0x77),
1457 .input_frontier = 0,
1458 .terminal_input_offset = 0,
1459 .outstanding_effect = null,
1460 };
1461 }