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 }