lib/machine/src/world/moment.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const fabric = @import("../fabric/root.zig");
  2 const os = @import("os");
  3 const std = @import("std");
  4 const types = @import("types.zig");
  5 
  6 const Sha256 = std.crypto.hash.sha2.Sha256;
  7 const root_domain = "TINYMACHINEWORLDMOMENT1\x00";
  8 
  9 /// The encoding version for moments. A reader checks this version before trusting
 10 /// a stored moment, and rejects a moment written under a version it does not speak.
 11 /// One version exists today: `stable_world_v1`.
 12 pub const Dialect = enum(u16) {
 13     stable_world_v1 = 1,
 14 };
 15 
 16 /// A place in one world's history, fixed by the origin cut it started from together
 17 /// with the ledger root it stands at now. A caller compares moments to know whether
 18 /// two worlds stand at the same place in the same history. Two moments match when
 19 /// both the origin and the position match, and the digest covers the pair, so two
 20 /// worlds that reached the same ledger position from different origins produce different
 21 /// moments.
 22 pub const Moment = struct {
 23     digest: os.abi.Digest,
 24     dialect: Dialect,
 25     origin: types.Root,
 26     fabric: fabric.Root,
 27 };
 28 
 29 pub const Error = error{
 30     ContractMismatch,
 31     FabricFrontierRegression,
 32     InvalidFabricRoot,
 33     InvalidWorldRoot,
 34     MomentMismatch,
 35 };
 36 
 37 /// Produces the moment that belongs to a given origin world root and current ledger
 38 /// root, so a caller records where the world now stands after every ledger change.
 39 /// A contract mismatch between the origin and the current position rejects. Any
 40 /// ledger counter that moved backward relative to the origin rejects: the entry,
 41 /// admission, and fault frontiers are each checked.
 42 pub fn prepare(origin: types.Root, current: fabric.Root) Error!Moment {
 43     try validateMaterial(origin, current);
 44     return rootFor(origin, current);
 45 }
 46 
 47 /// Derives the moment the material should yield and refuses a value that has drifted
 48 /// from it, so a reader who holds a stored moment proves it belongs to this origin
 49 /// and this position. The comparison covers every field, digest included.
 50 pub fn verify(
 51     value: Moment,
 52     origin: types.Root,
 53     current: fabric.Root,
 54 ) Error!void {
 55     const expected = try prepare(origin, current);
 56     if (!std.meta.eql(value, expected)) return error.MomentMismatch;
 57 }
 58 
 59 fn validateMaterial(origin: types.Root, current: fabric.Root) Error!void {
 60     try validateWorldRoot(origin);
 61     try validateFabricRoot(current);
 62     if (!std.meta.eql(origin.machine_contract, current.machine_contract)) {
 63         return error.ContractMismatch;
 64     }
 65     if (current.entry_frontier < origin.fabric.entry_frontier or
 66         current.admission_frontier < origin.fabric.admission_frontier or
 67         current.fault_frontier < origin.fabric.fault_frontier)
 68     {
 69         return error.FabricFrontierRegression;
 70     }
 71 }
 72 
 73 fn validateWorldRoot(value: types.Root) Error!void {
 74     os.abi.wire.validateDigest(value.digest) catch
 75         return error.InvalidWorldRoot;
 76     os.abi.wire.validateDigest(value.machine_contract.digest) catch
 77         return error.InvalidWorldRoot;
 78     if (value.dialect != .connected_world_v1 or
 79         value.node_count == 0 or
 80         value.node_count > types.node_limit or
 81         !std.meta.eql(value.machine_contract, value.fabric.machine_contract))
 82     {
 83         return error.InvalidWorldRoot;
 84     }
 85     validateFabricRoot(value.fabric) catch return error.InvalidWorldRoot;
 86 }
 87 
 88 fn validateFabricRoot(value: fabric.Root) Error!void {
 89     os.abi.wire.validateDigest(value.digest) catch
 90         return error.InvalidFabricRoot;
 91     os.abi.wire.validateDigest(value.machine_contract.digest) catch
 92         return error.InvalidFabricRoot;
 93     if (value.dialect != .ordered_effect_fabric_v3 or
 94         value.entry_frontier < value.admission_frontier or
 95         value.fault_frontier > value.admission_frontier)
 96     {
 97         return error.InvalidFabricRoot;
 98     }
 99 }
100 
101 fn rootFor(origin: types.Root, current: fabric.Root) Moment {
102     var hasher = Sha256.init(.{});
103     hasher.update(root_domain);
104     hashInteger(&hasher, @backingInt(Dialect.stable_world_v1));
105     hashWorldRoot(&hasher, origin);
106     hashFabricRoot(&hasher, current);
107     var digest: os.abi.Digest = undefined;
108     hasher.final(&digest);
109     return .{
110         .digest = digest,
111         .dialect = .stable_world_v1,
112         .origin = origin,
113         .fabric = current,
114     };
115 }
116 
117 fn hashWorldRoot(hasher: *Sha256, value: types.Root) void {
118     hasher.update(&value.digest);
119     hashInteger(hasher, @backingInt(value.dialect));
120     hasher.update(&value.machine_contract.digest);
121     hashFabricRoot(hasher, value.fabric);
122     hashInteger(hasher, value.node_count);
123 }
124 
125 fn hashFabricRoot(hasher: *Sha256, value: fabric.Root) void {
126     hasher.update(&value.digest);
127     hashInteger(hasher, @backingInt(value.dialect));
128     hasher.update(&value.machine_contract.digest);
129     hashInteger(hasher, value.entry_frontier);
130     hashInteger(hasher, value.admission_frontier);
131     hashInteger(hasher, value.fault_frontier);
132 }
133 
134 fn hashInteger(hasher: *Sha256, value: anytype) void {
135     var encoded: [@sizeOf(@TypeOf(value))]u8 = undefined;
136     std.mem.writeInt(@TypeOf(value), &encoded, value, .little);
137     hasher.update(&encoded);
138 }
139 
140 test "moments separate equal fabric positions by exact origin" {
141     const contract = @import("../profile/root.zig").ContractFingerprint{
142         .digest = @splat(0x31),
143     };
144     const initial: fabric.Root = .{
145         .digest = @splat(0x41),
146         .dialect = .ordered_effect_fabric_v3,
147         .machine_contract = contract,
148         .entry_frontier = 4,
149         .admission_frontier = 2,
150         .fault_frontier = 1,
151     };
152     const current: fabric.Root = .{
153         .digest = @splat(0x42),
154         .dialect = .ordered_effect_fabric_v3,
155         .machine_contract = contract,
156         .entry_frontier = 6,
157         .admission_frontier = 3,
158         .fault_frontier = 2,
159     };
160     const first_origin: types.Root = .{
161         .digest = @splat(0x51),
162         .dialect = .connected_world_v1,
163         .machine_contract = contract,
164         .fabric = initial,
165         .node_count = 2,
166     };
167     var second_origin = first_origin;
168     second_origin.digest = @splat(0x52);
169     const first = try prepare(first_origin, current);
170     const second = try prepare(second_origin, current);
171     try std.testing.expect(!std.meta.eql(first, second));
172     try verify(first, first_origin, current);
173     try std.testing.expectError(
174         error.MomentMismatch,
175         verify(first, second_origin, current),
176     );
177 }