lib/machine/src/world/owner.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const canon = @import("canon.zig");
2 const checkpoint = @import("../checkpoint/root.zig");
3 const fabric = @import("../fabric/root.zig");
4 const instance = @import("../instance/root.zig");
5 const os = @import("os");
6 const profile = @import("../profile/root.zig");
7 const std = @import("std");
8 const types = @import("types.zig");
9
10 const SealError = error{
11 MachineBoundaryUnavailable,
12 MachineSourceMismatch,
13 NodeCountMismatch,
14 NodeMismatch,
15 ProfileMismatch,
16 SemanticBoundaryMismatch,
17 };
18
19 pub const Error = canon.Error ||
20 checkpoint.Error ||
21 fabric.Error ||
22 instance.QuiescenceReceiptError ||
23 profile.Error ||
24 SealError;
25
26 /// Proves that the caller's checkpoints sit at the semantic boundaries the ledger
27 /// currently holds, then joins them into a single cut so a caller can store the
28 /// boundary now and restore it later. A live binding has to carry the semantic digest
29 /// that its instance's quiescence receipt carries. A retained binding has to name
30 /// a node that the ledger already marks unavailable. The ledger's machine contract
31 /// has to appear in every binding. The binding count must equal the ledger's node
32 /// count, and each binding must name the ledger's node at the same index. Each binding's
33 /// profile fingerprint must equal the one recorded in its checkpoint root.
34 pub fn seal(
35 fabric_owner: *const fabric.Fabric,
36 bindings: []const types.Binding,
37 ) Error!types.Cut {
38 const fabric_cut = try fabric_owner.cut();
39 if (bindings.len != fabric_cut.node_count) {
40 return error.NodeCountMismatch;
41 }
42 var nodes: [types.node_limit]types.Node = @splat(canon.emptyNode());
43 for (bindings, 0..) |binding, index| {
44 const boundary = fabric_cut.nodes[index];
45 if (!std.meta.eql(binding.node, boundary.id)) {
46 return error.NodeMismatch;
47 }
48 if (boundary.machine.kind != .semantic) {
49 return error.MachineBoundaryUnavailable;
50 }
51 const live_semantic: ?os.abi.Digest = switch (binding.source) {
52 .live => |machine| live: {
53 if (!boundary.available) return error.MachineSourceMismatch;
54 const receipt = try machine.quiescenceReceipt();
55 const semantic = try instance.projectSemanticReceipt(receipt);
56 break :live try instance.semanticReceiptDigest(semantic);
57 },
58 .retained => retained: {
59 if (boundary.available) return error.MachineSourceMismatch;
60 break :retained null;
61 },
62 };
63 try profile.validate(binding.execution_profile);
64 const contract = try profile.contractFingerprint(binding.execution_profile);
65 if (!std.meta.eql(contract, fabric_cut.root.machine_contract)) {
66 return error.ContractMismatch;
67 }
68 const identity = try binding.checkpoint.identity();
69 const profile_fingerprint = try profile.profileFingerprint(
70 binding.execution_profile,
71 );
72 if (!std.meta.eql(profile_fingerprint, identity.root.profile)) {
73 return error.ProfileMismatch;
74 }
75 if (!std.mem.eql(
76 u8,
77 &identity.semantic,
78 &boundary.machine.digest,
79 )) {
80 return error.SemanticBoundaryMismatch;
81 }
82 if (live_semantic) |semantic| {
83 if (!std.mem.eql(u8, &semantic, &identity.semantic)) {
84 return error.SemanticBoundaryMismatch;
85 }
86 }
87 nodes[index] = .{ .id = binding.node, .machine = identity.root };
88 }
89 return canon.prepare(
90 fabric_cut.root,
91 nodes[0..fabric_cut.node_count],
92 );
93 }
94
95 /// Hashes a cut's material again and refuses the cut when the result differs from
96 /// the stored root. A node slot holding no node carries the empty padding value.
97 /// The cut's own contract must equal the contract its ledger root carries.
98 pub const verify = canon.verify;