lib/machine/src/checkpoint/roots/owner.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const canon = @import("../canon/root.zig");
2 const hot = @import("../hot/root.zig");
3 const manifest = @import("manifest.zig");
4 const owner = @import("../owner/root.zig");
5 const std = @import("std");
6 const tree = @import("tree.zig");
7 const types = @import("types.zig");
8
9 /// Writes a verified durable checkpoint into the store as a full page tree
10 /// under a manifest that has no parent. The provider reserves
11 /// `publication_capacity`, takes a copy of each object, verifies the settled
12 /// block closure, and commits in one step. A page root whose memory digest
13 /// differs from the checkpoint's returns `PageRootMismatch`. Any failure aborts
14 /// the publication transaction. The caller puts a complete capture into the
15 /// store to get a name for it.
16 pub fn bind(
17 storage: types.Storage,
18 checkpoint: *const owner.Checkpoint,
19 ) types.Error!types.Binding {
20 try storage.begin(types.publication_capacity);
21 var transaction_open = true;
22 defer if (transaction_open) storage.abort();
23
24 const contents = try owner.inspect(checkpoint);
25 const pages = try tree.stage(storage, checkpoint.ram);
26 const memory: owner.MemoryDigest = .{ .digest = canon.memory(pages.digest) };
27 if (!std.meta.eql(memory, contents.memory)) {
28 return error.PageRootMismatch;
29 }
30 const value: types.Manifest = .{
31 .machine = contents.root,
32 .memory = memory,
33 .pages = pages,
34 .block = contents.material.receipt.block_root,
35 .material = contents.material,
36 .parent = null,
37 .dirty_pages = @intCast(types.page_count),
38 };
39 try storage.verifyBlock(value.block);
40 const root = try manifest.stage(storage, value);
41 try storage.commit(root);
42 transaction_open = false;
43 return .{ .root = root, .manifest = value };
44 }
45
46 /// Stores a hot snapshot as a manifest whose parent is `parent_root`. The call
47 /// validates each material link along the parent manifest chain and verifies
48 /// the parent checkpoint and the child identity. A chain already 64 entries
49 /// deep returns `DeltaChainCapacityExceeded`. Each changed page becomes a new
50 /// page object, and the tree path above it is staged with it. A block closure
51 /// differing from the parent's is verified. Any failure after the transaction
52 /// opens aborts it.
53 pub fn bindDelta(
54 storage: types.Storage,
55 parent_root: types.ManifestRoot,
56 snapshot: *const hot.Snapshot,
57 ) types.Error!types.Binding {
58 const parent = try inspectParentChain(storage, parent_root);
59 _ = try admitNextDepth(parent.depth);
60 const parent_contents = try owner.inspect(snapshot.parent);
61 try validateSnapshotParent(parent.manifest, parent_contents);
62 const snapshot_identity = try snapshot.identity();
63 const capacity = try tree.deltaCapacity(snapshot.indices);
64 try storage.begin(capacity);
65 var transaction_open = true;
66 defer if (transaction_open) storage.abort();
67
68 const pages = try tree.stageDelta(
69 storage,
70 parent.manifest.pages,
71 snapshot.indices,
72 snapshot.pages,
73 );
74 const memory: owner.MemoryDigest = .{ .digest = canon.memory(pages.digest) };
75 if (!std.meta.eql(memory, snapshot.memory)) return error.PageRootMismatch;
76 const identity = try owner.identify(snapshot.material, memory);
77 if (!std.meta.eql(identity, snapshot_identity)) {
78 return error.DeltaParentMismatch;
79 }
80 const block = snapshot.material.receipt.block_root;
81 if (!std.meta.eql(block, parent.manifest.block)) {
82 try storage.verifyBlock(block);
83 }
84 const value: types.Manifest = .{
85 .machine = identity.root,
86 .memory = memory,
87 .pages = pages,
88 .block = block,
89 .material = snapshot.material,
90 .parent = parent_root,
91 .dirty_pages = snapshot.dirtyPageCount(),
92 };
93 const root = try manifest.stage(storage, value);
94 try storage.commit(root);
95 transaction_open = false;
96 return .{ .root = root, .manifest = value };
97 }
98
99 /// Opens a manifest chain again and authenticates it together with every
100 /// page-tree object beneath it and every block closure it names. The call walks
101 /// the parentless tree in full, then authenticates the changed paths between
102 /// each manifest and its child, which together covers every object the newest
103 /// root reaches. The call then returns the newest manifest.
104 pub fn reopen(
105 storage: types.Storage,
106 expected: types.ManifestRoot,
107 ) types.Error!types.Manifest {
108 return open(storage, expected, null);
109 }
110
111 /// Opens the chain again, fills an aligned destination the caller owns with the
112 /// newest normalized memory, and checks that image against its memory digest. A
113 /// destination whose memory digest differs from the manifest's returns
114 /// `PageRootMismatch`.
115 pub fn materialize(
116 storage: types.Storage,
117 expected: types.ManifestRoot,
118 destination: []align(types.page_bytes) u8,
119 ) types.Error!types.Manifest {
120 return open(storage, expected, destination);
121 }
122
123 /// Authenticates a manifest chain and the delta links along it, leaving the
124 /// pages for later. The call checks the encoding of the parentless page root
125 /// and skips the full tree walk, so a page object is authenticated by the
126 /// branch that first reads it. The caller opens a stored capture for a branch,
127 /// paying for pages only as they are touched.
128 pub fn openShared(
129 storage: types.Storage,
130 expected: types.ManifestRoot,
131 ) types.Error!types.Manifest {
132 var chain: [types.chain_limit]types.Manifest = undefined;
133 const count = try readChain(storage, expected, &chain);
134 var index = count - 1;
135 var parent = chain[index];
136 try storage.verifyBlock(parent.block);
137 try tree.validateRoot(parent.pages);
138 while (index > 0) {
139 index -= 1;
140 const child = chain[index];
141 try owner.validateParent(child.material, parent.material);
142 try tree.verifyDelta(
143 storage,
144 parent.pages,
145 child.pages,
146 child.dirty_pages,
147 );
148 if (!std.meta.eql(child.block, parent.block)) {
149 try storage.verifyBlock(child.block);
150 }
151 parent = child;
152 }
153 return chain[0];
154 }
155
156 fn open(
157 storage: types.Storage,
158 expected: types.ManifestRoot,
159 destination: ?[]align(types.page_bytes) u8,
160 ) types.Error!types.Manifest {
161 var chain: [types.chain_limit]types.Manifest = undefined;
162 const count = try readChain(storage, expected, &chain);
163 var index = count - 1;
164 var parent = chain[index];
165 try storage.verifyBlock(parent.block);
166 try tree.verify(storage, parent.pages);
167 while (index > 0) {
168 index -= 1;
169 const child = chain[index];
170 try owner.validateParent(child.material, parent.material);
171 try tree.verifyDelta(
172 storage,
173 parent.pages,
174 child.pages,
175 child.dirty_pages,
176 );
177 if (!std.meta.eql(child.block, parent.block)) {
178 try storage.verifyBlock(child.block);
179 }
180 parent = child;
181 }
182 const value = chain[0];
183 if (destination) |output| {
184 try tree.materialize(storage, value.pages, output);
185 const actual = try owner.validatedMemoryDigest(
186 output,
187 value.material.immutable_image,
188 );
189 if (!std.meta.eql(actual, value.memory)) return error.PageRootMismatch;
190 }
191 return value;
192 }
193
194 const ParentInspection = struct {
195 manifest: types.Manifest,
196 depth: usize,
197 };
198
199 fn inspectParentChain(
200 storage: types.Storage,
201 expected: types.ManifestRoot,
202 ) types.Error!ParentInspection {
203 var current = expected;
204 var depth: usize = 0;
205 var immediate: types.Manifest = undefined;
206 var previous: ?types.Manifest = null;
207 while (depth < types.chain_limit) : (depth += 1) {
208 const value = try manifest.read(storage, current);
209 if (depth == 0) immediate = value;
210 if (previous) |child| {
211 try owner.validateParent(child.material, value.material);
212 }
213 previous = value;
214 if (value.parent) |parent| {
215 current = parent;
216 } else {
217 return .{ .manifest = immediate, .depth = depth + 1 };
218 }
219 }
220 return error.DeltaChainCapacityExceeded;
221 }
222
223 fn readChain(
224 storage: types.Storage,
225 expected: types.ManifestRoot,
226 output: *[types.chain_limit]types.Manifest,
227 ) types.Error!usize {
228 var current = expected;
229 var count: usize = 0;
230 while (count < output.len) {
231 const value = try manifest.read(storage, current);
232 output[count] = value;
233 count += 1;
234 if (value.parent) |parent| {
235 current = parent;
236 } else {
237 return count;
238 }
239 }
240 return error.DeltaChainCapacityExceeded;
241 }
242
243 fn validateSnapshotParent(
244 manifest_value: types.Manifest,
245 contents: owner.Contents,
246 ) types.Error!void {
247 if (!std.meta.eql(manifest_value.machine, contents.root) or
248 !std.meta.eql(manifest_value.memory, contents.memory) or
249 !std.meta.eql(manifest_value.material, contents.material) or
250 !std.meta.eql(manifest_value.block, contents.material.receipt.block_root))
251 {
252 return error.DeltaParentMismatch;
253 }
254 }
255
256 fn admitNextDepth(parent_depth: usize) types.Error!usize {
257 if (parent_depth >= types.chain_limit) {
258 return error.DeltaChainCapacityExceeded;
259 }
260 return parent_depth + 1;
261 }
262
263 test "delta chain capacity rejects max plus one" {
264 try std.testing.expectEqual(
265 types.chain_limit,
266 try admitNextDepth(types.chain_limit - 1),
267 );
268 try std.testing.expectError(
269 error.DeltaChainCapacityExceeded,
270 admitNextDepth(types.chain_limit),
271 );
272 }