tigerbeetle-node 0.11.8 → 0.11.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.client.node.sha256 +1 -1
- package/package.json +4 -3
- package/scripts/build_lib.sh +41 -0
- package/src/node.zig +1 -1
- package/src/tigerbeetle/scripts/validate_docs.sh +7 -1
- package/src/tigerbeetle/src/benchmark.zig +3 -3
- package/src/tigerbeetle/src/config.zig +31 -16
- package/src/tigerbeetle/src/constants.zig +48 -9
- package/src/tigerbeetle/src/ewah.zig +5 -5
- package/src/tigerbeetle/src/ewah_fuzz.zig +1 -1
- package/src/tigerbeetle/src/lsm/binary_search.zig +1 -1
- package/src/tigerbeetle/src/lsm/bloom_filter.zig +1 -1
- package/src/tigerbeetle/src/lsm/compaction.zig +34 -21
- package/src/tigerbeetle/src/lsm/forest_fuzz.zig +84 -104
- package/src/tigerbeetle/src/lsm/grid.zig +19 -13
- package/src/tigerbeetle/src/lsm/manifest_log.zig +8 -10
- package/src/tigerbeetle/src/lsm/manifest_log_fuzz.zig +18 -13
- package/src/tigerbeetle/src/lsm/merge_iterator.zig +1 -1
- package/src/tigerbeetle/src/lsm/segmented_array.zig +17 -17
- package/src/tigerbeetle/src/lsm/segmented_array_fuzz.zig +1 -1
- package/src/tigerbeetle/src/lsm/set_associative_cache.zig +1 -1
- package/src/tigerbeetle/src/lsm/table.zig +8 -20
- package/src/tigerbeetle/src/lsm/table_immutable.zig +1 -1
- package/src/tigerbeetle/src/lsm/table_iterator.zig +3 -3
- package/src/tigerbeetle/src/lsm/table_mutable.zig +14 -2
- package/src/tigerbeetle/src/lsm/test.zig +5 -4
- package/src/tigerbeetle/src/lsm/tree.zig +1 -2
- package/src/tigerbeetle/src/lsm/tree_fuzz.zig +85 -115
- package/src/tigerbeetle/src/message_bus.zig +4 -4
- package/src/tigerbeetle/src/message_pool.zig +7 -10
- package/src/tigerbeetle/src/ring_buffer.zig +22 -12
- package/src/tigerbeetle/src/simulator.zig +366 -239
- package/src/tigerbeetle/src/state_machine/auditor.zig +5 -5
- package/src/tigerbeetle/src/state_machine/workload.zig +3 -3
- package/src/tigerbeetle/src/state_machine.zig +190 -178
- package/src/tigerbeetle/src/{util.zig → stdx.zig} +2 -0
- package/src/tigerbeetle/src/storage.zig +13 -6
- package/src/tigerbeetle/src/{test → testing/cluster}/message_bus.zig +3 -3
- package/src/tigerbeetle/src/{test → testing/cluster}/network.zig +46 -22
- package/src/tigerbeetle/src/testing/cluster/state_checker.zig +169 -0
- package/src/tigerbeetle/src/testing/cluster/storage_checker.zig +202 -0
- package/src/tigerbeetle/src/testing/cluster.zig +443 -0
- package/src/tigerbeetle/src/{test → testing}/fuzz.zig +0 -0
- package/src/tigerbeetle/src/testing/hash_log.zig +66 -0
- package/src/tigerbeetle/src/{test → testing}/id.zig +0 -0
- package/src/tigerbeetle/src/testing/packet_simulator.zig +365 -0
- package/src/tigerbeetle/src/{test → testing}/priority_queue.zig +1 -1
- package/src/tigerbeetle/src/testing/reply_sequence.zig +139 -0
- package/src/tigerbeetle/src/{test → testing}/state_machine.zig +3 -1
- package/src/tigerbeetle/src/testing/storage.zig +757 -0
- package/src/tigerbeetle/src/{test → testing}/table.zig +21 -0
- package/src/tigerbeetle/src/{test → testing}/time.zig +0 -0
- package/src/tigerbeetle/src/tigerbeetle.zig +2 -0
- package/src/tigerbeetle/src/tracer.zig +3 -3
- package/src/tigerbeetle/src/unit_tests.zig +4 -4
- package/src/tigerbeetle/src/vopr.zig +2 -2
- package/src/tigerbeetle/src/vsr/client.zig +5 -2
- package/src/tigerbeetle/src/vsr/clock.zig +93 -53
- package/src/tigerbeetle/src/vsr/journal.zig +109 -98
- package/src/tigerbeetle/src/vsr/journal_format_fuzz.zig +2 -2
- package/src/tigerbeetle/src/vsr/replica.zig +1983 -1430
- package/src/tigerbeetle/src/vsr/replica_format.zig +13 -13
- package/src/tigerbeetle/src/vsr/superblock.zig +240 -142
- package/src/tigerbeetle/src/vsr/superblock_client_table.zig +7 -7
- package/src/tigerbeetle/src/vsr/superblock_free_set.zig +1 -1
- package/src/tigerbeetle/src/vsr/superblock_free_set_fuzz.zig +1 -1
- package/src/tigerbeetle/src/vsr/superblock_fuzz.zig +49 -14
- package/src/tigerbeetle/src/vsr/superblock_manifest.zig +38 -19
- package/src/tigerbeetle/src/vsr/superblock_quorums.zig +48 -48
- package/src/tigerbeetle/src/vsr/superblock_quorums_fuzz.zig +51 -51
- package/src/tigerbeetle/src/vsr.zig +99 -33
- package/src/tigerbeetle/src/demo.zig +0 -132
- package/src/tigerbeetle/src/demo_01_create_accounts.zig +0 -35
- package/src/tigerbeetle/src/demo_02_lookup_accounts.zig +0 -7
- package/src/tigerbeetle/src/demo_03_create_transfers.zig +0 -37
- package/src/tigerbeetle/src/demo_04_create_pending_transfers.zig +0 -61
- package/src/tigerbeetle/src/demo_05_post_pending_transfers.zig +0 -37
- package/src/tigerbeetle/src/demo_06_void_pending_transfers.zig +0 -24
- package/src/tigerbeetle/src/demo_07_lookup_transfers.zig +0 -7
- package/src/tigerbeetle/src/test/cluster.zig +0 -352
- package/src/tigerbeetle/src/test/conductor.zig +0 -366
- package/src/tigerbeetle/src/test/packet_simulator.zig +0 -398
- package/src/tigerbeetle/src/test/state_checker.zig +0 -169
- package/src/tigerbeetle/src/test/storage.zig +0 -864
- package/src/tigerbeetle/src/test/storage_checker.zig +0 -204
|
@@ -3,10 +3,10 @@ const assert = std.debug.assert;
|
|
|
3
3
|
const log = std.log.scoped(.fuzz_vsr_superblock_quorums);
|
|
4
4
|
|
|
5
5
|
const superblock = @import("./superblock.zig");
|
|
6
|
-
const
|
|
6
|
+
const SuperBlockHeader = superblock.SuperBlockHeader;
|
|
7
7
|
const SuperBlockVersion = superblock.SuperBlockVersion;
|
|
8
8
|
|
|
9
|
-
const fuzz = @import("../
|
|
9
|
+
const fuzz = @import("../testing/fuzz.zig");
|
|
10
10
|
const superblock_quorums = @import("superblock_quorums.zig");
|
|
11
11
|
const QuorumsType = superblock_quorums.QuorumsType;
|
|
12
12
|
|
|
@@ -21,7 +21,7 @@ pub fn main() !void {
|
|
|
21
21
|
try fuzz_quorums_working(prng.random());
|
|
22
22
|
|
|
23
23
|
try fuzz_quorum_repairs(prng.random(), .{ .superblock_copies = 4 });
|
|
24
|
-
// TODO: Enable these once
|
|
24
|
+
// TODO: Enable these once SuperBlockHeader is generic over its Constants.
|
|
25
25
|
// try fuzz_quorum_repairs(prng.random(), .{ .superblock_copies = 6 });
|
|
26
26
|
// try fuzz_quorum_repairs(prng.random(), .{ .superblock_copies = 8 });
|
|
27
27
|
}
|
|
@@ -108,18 +108,18 @@ fn test_quorums_working(
|
|
|
108
108
|
const Quorums = QuorumsType(.{ .superblock_copies = 4 });
|
|
109
109
|
const misdirect = random.boolean(); // true:cluster false:replica
|
|
110
110
|
var quorums: Quorums = undefined;
|
|
111
|
-
var
|
|
111
|
+
var headers: [4]SuperBlockHeader = undefined;
|
|
112
112
|
// TODO(Zig): Ideally this would be a [6]?u128 and the access would be
|
|
113
113
|
// "checksums[i] orelse random.int(u128)", but that currently causes the compiler to segfault
|
|
114
114
|
// during code generation.
|
|
115
115
|
var checksums: [6]u128 = undefined;
|
|
116
116
|
for (checksums) |*c| c.* = random.int(u128);
|
|
117
117
|
|
|
118
|
-
// Create
|
|
118
|
+
// Create headers in ascending-sequence order to build the checksum/parent hash chain.
|
|
119
119
|
std.sort.sort(CopyTemplate, copies, {}, CopyTemplate.less_than);
|
|
120
120
|
|
|
121
|
-
for (
|
|
122
|
-
|
|
121
|
+
for (headers) |*header, i| {
|
|
122
|
+
header.* = std.mem.zeroInit(SuperBlockHeader, .{
|
|
123
123
|
.copy = @intCast(u8, i),
|
|
124
124
|
.version = SuperBlockVersion,
|
|
125
125
|
.replica = 1,
|
|
@@ -131,32 +131,32 @@ fn test_quorums_working(
|
|
|
131
131
|
var checksum: ?u128 = null;
|
|
132
132
|
switch (copies[i].variant) {
|
|
133
133
|
.valid => {},
|
|
134
|
-
.valid_high_copy =>
|
|
134
|
+
.valid_high_copy => header.copy = 4,
|
|
135
135
|
.invalid_broken => {
|
|
136
136
|
if (random.boolean() and i > 0) {
|
|
137
|
-
// Error: duplicate
|
|
138
|
-
|
|
137
|
+
// Error: duplicate header (if available).
|
|
138
|
+
header.* = headers[random.uintLessThanBiased(usize, i)];
|
|
139
139
|
checksum = random.int(u128);
|
|
140
140
|
} else {
|
|
141
141
|
// Error: invalid checksum.
|
|
142
142
|
checksum = random.int(u128);
|
|
143
143
|
}
|
|
144
144
|
},
|
|
145
|
-
.invalid_fork =>
|
|
146
|
-
.invalid_parent =>
|
|
145
|
+
.invalid_fork => header.storage_size_max += 1, // Ensure we have a different checksum.
|
|
146
|
+
.invalid_parent => header.parent += 1,
|
|
147
147
|
.invalid_misdirect => {
|
|
148
148
|
if (misdirect) {
|
|
149
|
-
|
|
149
|
+
header.cluster += 1;
|
|
150
150
|
} else {
|
|
151
|
-
|
|
151
|
+
header.replica += 1;
|
|
152
152
|
}
|
|
153
153
|
},
|
|
154
|
-
.invalid_vsr_state =>
|
|
154
|
+
.invalid_vsr_state => header.vsr_state.view += 1,
|
|
155
155
|
}
|
|
156
|
-
|
|
156
|
+
header.checksum = checksum orelse header.calculate_checksum();
|
|
157
157
|
|
|
158
158
|
if (copies[i].variant == .valid or copies[i].variant == .invalid_vsr_state) {
|
|
159
|
-
checksums[
|
|
159
|
+
checksums[header.sequence] = header.checksum;
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
162
|
|
|
@@ -165,7 +165,7 @@ fn test_quorums_working(
|
|
|
165
165
|
} else {
|
|
166
166
|
// Shuffling copies can only change the working quorum when we have a corrupt copy index,
|
|
167
167
|
// because we guess that the true index is the slot.
|
|
168
|
-
random.shuffle(
|
|
168
|
+
random.shuffle(SuperBlockHeader, &headers);
|
|
169
169
|
}
|
|
170
170
|
|
|
171
171
|
const threshold = switch (threshold_count) {
|
|
@@ -175,8 +175,8 @@ fn test_quorums_working(
|
|
|
175
175
|
};
|
|
176
176
|
assert(threshold.count() == threshold_count);
|
|
177
177
|
|
|
178
|
-
if (quorums.working(&
|
|
179
|
-
try std.testing.expectEqual(result, working.
|
|
178
|
+
if (quorums.working(&headers, threshold)) |working| {
|
|
179
|
+
try std.testing.expectEqual(result, working.header.sequence);
|
|
180
180
|
} else |err| {
|
|
181
181
|
try std.testing.expectEqual(result, err);
|
|
182
182
|
}
|
|
@@ -238,7 +238,7 @@ pub const CopyTemplate = struct {
|
|
|
238
238
|
}
|
|
239
239
|
};
|
|
240
240
|
|
|
241
|
-
// Verify that a torn
|
|
241
|
+
// Verify that a torn header write during repair never compromises the existing quorum.
|
|
242
242
|
pub fn fuzz_quorum_repairs(
|
|
243
243
|
random: std.rand.Random,
|
|
244
244
|
comptime options: superblock_quorums.Options,
|
|
@@ -249,66 +249,66 @@ pub fn fuzz_quorum_repairs(
|
|
|
249
249
|
var q1: Quorums = undefined;
|
|
250
250
|
var q2: Quorums = undefined;
|
|
251
251
|
|
|
252
|
-
const
|
|
253
|
-
var
|
|
254
|
-
for (&
|
|
255
|
-
|
|
252
|
+
const headers_valid = blk: {
|
|
253
|
+
var headers: [superblock_copies]SuperBlockHeader = undefined;
|
|
254
|
+
for (&headers) |*header, i| {
|
|
255
|
+
header.* = std.mem.zeroInit(SuperBlockHeader, .{
|
|
256
256
|
.copy = @intCast(u8, i),
|
|
257
257
|
.version = SuperBlockVersion,
|
|
258
258
|
.replica = 1,
|
|
259
259
|
.storage_size_max = superblock.data_file_size_min,
|
|
260
260
|
.sequence = 123,
|
|
261
261
|
});
|
|
262
|
-
|
|
262
|
+
header.set_checksum();
|
|
263
263
|
}
|
|
264
|
-
break :blk
|
|
264
|
+
break :blk headers;
|
|
265
265
|
};
|
|
266
266
|
|
|
267
|
-
const
|
|
268
|
-
var
|
|
269
|
-
|
|
270
|
-
break :blk
|
|
267
|
+
const header_invalid = blk: {
|
|
268
|
+
var header = headers_valid[0];
|
|
269
|
+
header.checksum = 456;
|
|
270
|
+
break :blk header;
|
|
271
271
|
};
|
|
272
272
|
|
|
273
273
|
// Generate a random valid 2/4 quorum.
|
|
274
|
-
// 1 bits indicate valid
|
|
275
|
-
// 0 bits indicate invalid
|
|
274
|
+
// 1 bits indicate valid headers.
|
|
275
|
+
// 0 bits indicate invalid headers.
|
|
276
276
|
var valid = std.bit_set.IntegerBitSet(superblock_copies).initEmpty();
|
|
277
277
|
while (valid.count() < Quorums.Threshold.open.count() or random.boolean()) {
|
|
278
278
|
valid.set(random.uintLessThan(usize, superblock_copies));
|
|
279
279
|
}
|
|
280
280
|
|
|
281
|
-
var
|
|
282
|
-
for (&
|
|
283
|
-
|
|
281
|
+
var working_headers: [superblock_copies]SuperBlockHeader = undefined;
|
|
282
|
+
for (&working_headers) |*header, i| {
|
|
283
|
+
header.* = if (valid.isSet(i)) headers_valid[i] else header_invalid;
|
|
284
284
|
}
|
|
285
|
-
random.shuffle(
|
|
286
|
-
var
|
|
285
|
+
random.shuffle(SuperBlockHeader, &working_headers);
|
|
286
|
+
var repair_headers = working_headers;
|
|
287
287
|
|
|
288
|
-
const working_quorum = q1.working(&
|
|
288
|
+
const working_quorum = q1.working(&working_headers, .open) catch unreachable;
|
|
289
289
|
var quorum_repairs = working_quorum.repairs();
|
|
290
290
|
while (quorum_repairs.next()) |repair_copy| {
|
|
291
291
|
{
|
|
292
|
-
// Simulate a torn
|
|
293
|
-
var
|
|
294
|
-
|
|
295
|
-
const damaged_quorum = q2.working(&
|
|
296
|
-
assert(damaged_quorum.
|
|
292
|
+
// Simulate a torn header write, crash, recover sequence.
|
|
293
|
+
var damaged_headers = repair_headers;
|
|
294
|
+
damaged_headers[repair_copy] = header_invalid;
|
|
295
|
+
const damaged_quorum = q2.working(&damaged_headers, .open) catch unreachable;
|
|
296
|
+
assert(damaged_quorum.header.checksum == working_quorum.header.checksum);
|
|
297
297
|
}
|
|
298
298
|
|
|
299
299
|
// "Finish" the write so that we can test the next repair.
|
|
300
|
-
|
|
300
|
+
repair_headers[repair_copy] = headers_valid[repair_copy];
|
|
301
301
|
|
|
302
|
-
const quorum_repaired = q2.working(&
|
|
303
|
-
assert(quorum_repaired.
|
|
302
|
+
const quorum_repaired = q2.working(&repair_headers, .open) catch unreachable;
|
|
303
|
+
assert(quorum_repaired.header.checksum == working_quorum.header.checksum);
|
|
304
304
|
}
|
|
305
305
|
|
|
306
306
|
// At the end of all repairs, we expect to have every copy of the superblock.
|
|
307
307
|
// They do not need to be in their home slot.
|
|
308
308
|
var copies = Quorums.QuorumCount.initEmpty();
|
|
309
|
-
for (
|
|
310
|
-
assert(
|
|
311
|
-
copies.set(
|
|
309
|
+
for (repair_headers) |repair_header| {
|
|
310
|
+
assert(repair_header.checksum == working_quorum.header.checksum);
|
|
311
|
+
copies.set(repair_header.copy);
|
|
312
312
|
}
|
|
313
|
-
assert(
|
|
313
|
+
assert(repair_headers.len == copies.count());
|
|
314
314
|
}
|
|
@@ -17,6 +17,7 @@ pub const tigerbeetle = @import("tigerbeetle.zig");
|
|
|
17
17
|
pub const time = @import("time.zig");
|
|
18
18
|
pub const tracer = @import("tracer.zig");
|
|
19
19
|
pub const config = @import("config.zig");
|
|
20
|
+
pub const stdx = @import("stdx.zig");
|
|
20
21
|
pub const superblock = @import("vsr/superblock.zig");
|
|
21
22
|
pub const lsm = .{
|
|
22
23
|
.tree = @import("lsm/tree.zig"),
|
|
@@ -25,6 +26,9 @@ pub const lsm = .{
|
|
|
25
26
|
.forest = @import("lsm/forest.zig"),
|
|
26
27
|
.posted_groove = @import("lsm/posted_groove.zig"),
|
|
27
28
|
};
|
|
29
|
+
pub const testing = .{
|
|
30
|
+
.cluster = @import("testing/cluster.zig"),
|
|
31
|
+
};
|
|
28
32
|
|
|
29
33
|
pub const ReplicaType = @import("vsr/replica.zig").ReplicaType;
|
|
30
34
|
pub const format = @import("vsr/replica_format.zig").format;
|
|
@@ -34,7 +38,7 @@ pub const Clock = @import("vsr/clock.zig").Clock;
|
|
|
34
38
|
pub const JournalType = @import("vsr/journal.zig").JournalType;
|
|
35
39
|
pub const SlotRange = @import("vsr/journal.zig").SlotRange;
|
|
36
40
|
pub const SuperBlockType = superblock.SuperBlockType;
|
|
37
|
-
pub const VSRState = superblock.
|
|
41
|
+
pub const VSRState = superblock.SuperBlockHeader.VSRState;
|
|
38
42
|
|
|
39
43
|
/// The version of our Viewstamped Replication protocol in use, including customizations.
|
|
40
44
|
/// For backwards compatibility through breaking changes (e.g. upgrading checksums/ciphers).
|
|
@@ -102,9 +106,6 @@ pub const Command = enum(u8) {
|
|
|
102
106
|
do_view_change,
|
|
103
107
|
start_view,
|
|
104
108
|
|
|
105
|
-
recovery,
|
|
106
|
-
recovery_response,
|
|
107
|
-
|
|
108
109
|
request_start_view,
|
|
109
110
|
request_headers,
|
|
110
111
|
request_prepare,
|
|
@@ -319,8 +320,6 @@ pub const Header = extern struct {
|
|
|
319
320
|
.start_view_change => self.invalid_start_view_change(),
|
|
320
321
|
.do_view_change => self.invalid_do_view_change(),
|
|
321
322
|
.start_view => self.invalid_start_view(),
|
|
322
|
-
.recovery => self.invalid_recovery(),
|
|
323
|
-
.recovery_response => self.invalid_recovery_response(),
|
|
324
323
|
.request_start_view => self.invalid_request_start_view(),
|
|
325
324
|
.request_headers => self.invalid_request_headers(),
|
|
326
325
|
.request_prepare => self.invalid_request_prepare(),
|
|
@@ -523,29 +522,6 @@ pub const Header = extern struct {
|
|
|
523
522
|
return null;
|
|
524
523
|
}
|
|
525
524
|
|
|
526
|
-
fn invalid_recovery(self: *const Header) ?[]const u8 {
|
|
527
|
-
assert(self.command == .recovery);
|
|
528
|
-
if (self.parent != 0) return "parent != 0";
|
|
529
|
-
if (self.client != 0) return "client != 0";
|
|
530
|
-
if (self.request != 0) return "request != 0";
|
|
531
|
-
if (self.view != 0) return "view != 0";
|
|
532
|
-
if (self.op != 0) return "op != 0";
|
|
533
|
-
if (self.commit != 0) return "commit != 0";
|
|
534
|
-
if (self.timestamp != 0) return "timestamp != 0";
|
|
535
|
-
if (self.operation != .reserved) return "operation != .reserved";
|
|
536
|
-
return null;
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
fn invalid_recovery_response(self: *const Header) ?[]const u8 {
|
|
540
|
-
assert(self.command == .recovery_response);
|
|
541
|
-
if (self.parent != 0) return "parent != 0";
|
|
542
|
-
if (self.client != 0) return "client != 0";
|
|
543
|
-
if (self.request != 0) return "request != 0";
|
|
544
|
-
if (self.timestamp != 0) return "timestamp != 0";
|
|
545
|
-
if (self.operation != .reserved) return "operation != .reserved";
|
|
546
|
-
return null;
|
|
547
|
-
}
|
|
548
|
-
|
|
549
525
|
fn invalid_request_start_view(self: *const Header) ?[]const u8 {
|
|
550
526
|
assert(self.command == .request_start_view);
|
|
551
527
|
if (self.parent != 0) return "parent != 0";
|
|
@@ -826,8 +802,6 @@ pub fn exponential_backoff_with_jitter(
|
|
|
826
802
|
}
|
|
827
803
|
|
|
828
804
|
test "exponential_backoff_with_jitter" {
|
|
829
|
-
const testing = std.testing;
|
|
830
|
-
|
|
831
805
|
var prng = std.rand.DefaultPrng.init(0);
|
|
832
806
|
const random = prng.random();
|
|
833
807
|
|
|
@@ -838,8 +812,8 @@ test "exponential_backoff_with_jitter" {
|
|
|
838
812
|
var attempt = max - attempts;
|
|
839
813
|
while (attempt < max) : (attempt += 1) {
|
|
840
814
|
const ebwj = exponential_backoff_with_jitter(random, min, max, attempt);
|
|
841
|
-
try testing.expect(ebwj >= min);
|
|
842
|
-
try testing.expect(ebwj <= max);
|
|
815
|
+
try std.testing.expect(ebwj >= min);
|
|
816
|
+
try std.testing.expect(ebwj <= max);
|
|
843
817
|
}
|
|
844
818
|
}
|
|
845
819
|
|
|
@@ -1024,3 +998,95 @@ pub fn quorums(replica_count: u8) struct {
|
|
|
1024
998
|
.view_change = quorum_view_change,
|
|
1025
999
|
};
|
|
1026
1000
|
}
|
|
1001
|
+
|
|
1002
|
+
/// The SuperBlock's persisted VSR headers.
|
|
1003
|
+
/// One of the following:
|
|
1004
|
+
///
|
|
1005
|
+
/// - SV headers (consecutive chain)
|
|
1006
|
+
/// - DVC headers (disjoint chain)
|
|
1007
|
+
pub const ViewChangeHeaders = struct {
|
|
1008
|
+
/// Headers are ordered from high-to-low op.
|
|
1009
|
+
slice: []const Header,
|
|
1010
|
+
|
|
1011
|
+
pub const BoundedArray = std.BoundedArray(Header, constants.pipeline_prepare_queue_max);
|
|
1012
|
+
|
|
1013
|
+
pub fn init(slice: []const Header) ViewChangeHeaders {
|
|
1014
|
+
ViewChangeHeaders.verify(slice);
|
|
1015
|
+
|
|
1016
|
+
return .{ .slice = slice };
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
pub fn verify(slice: []const Header) void {
|
|
1020
|
+
assert(slice.len > 0);
|
|
1021
|
+
assert(slice.len <= constants.pipeline_prepare_queue_max);
|
|
1022
|
+
|
|
1023
|
+
var child: ?*const Header = null;
|
|
1024
|
+
for (slice) |*header| {
|
|
1025
|
+
assert(header.valid_checksum());
|
|
1026
|
+
assert(header.command == .prepare);
|
|
1027
|
+
|
|
1028
|
+
if (child) |child_header| {
|
|
1029
|
+
assert(header.op < child_header.op);
|
|
1030
|
+
assert(header.view <= child_header.view);
|
|
1031
|
+
assert((header.op + 1 == child_header.op) ==
|
|
1032
|
+
(header.checksum == child_header.parent));
|
|
1033
|
+
assert(header.timestamp < child_header.timestamp);
|
|
1034
|
+
}
|
|
1035
|
+
child = header;
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
const ViewRange = struct {
|
|
1040
|
+
min: u32, // inclusive
|
|
1041
|
+
max: u32, // inclusive
|
|
1042
|
+
|
|
1043
|
+
pub fn contains(range: ViewRange, view: u32) bool {
|
|
1044
|
+
return range.min <= view and view <= range.max;
|
|
1045
|
+
}
|
|
1046
|
+
};
|
|
1047
|
+
|
|
1048
|
+
/// Returns the range of possible views (of prepare, not commit) for a message that is part of
|
|
1049
|
+
/// the same log_view as these headers.
|
|
1050
|
+
///
|
|
1051
|
+
/// - When these are DVC headers for a log_view=V, we must be in view_change status working to
|
|
1052
|
+
/// transition to a view beyond V. So we will never prepare anything else as part of view V.
|
|
1053
|
+
/// - When these are SV headers for a log_view=V, we can continue to add to them (by preparing
|
|
1054
|
+
/// more ops), but those ops will laways be part of the log_view. If they were prepared during
|
|
1055
|
+
/// a view prior to the log_view, they would already be part of the headers.
|
|
1056
|
+
pub fn view_for_op(headers: ViewChangeHeaders, op: u64, log_view: u32) ViewRange {
|
|
1057
|
+
const header_newest = &headers.slice[0];
|
|
1058
|
+
const header_oldest = &headers.slice[headers.slice.len - 1];
|
|
1059
|
+
|
|
1060
|
+
if (op < header_oldest.op) return .{ .min = 0, .max = header_oldest.view };
|
|
1061
|
+
if (op > header_newest.op) return .{ .min = log_view, .max = log_view };
|
|
1062
|
+
|
|
1063
|
+
for (headers.slice) |*header| {
|
|
1064
|
+
if (header.op == op) return .{ .min = header.view, .max = header.view };
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
for (headers.slice[0 .. headers.slice.len - 1]) |*header_next, header_next_index| {
|
|
1068
|
+
const header_prev = headers.slice[header_next_index + 1];
|
|
1069
|
+
if (header_prev.op < op and op < header_next.op) {
|
|
1070
|
+
return .{ .min = header_prev.view, .max = header_next.view };
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
unreachable;
|
|
1074
|
+
}
|
|
1075
|
+
};
|
|
1076
|
+
|
|
1077
|
+
test "ViewChangeHeaders.view_for_op" {
|
|
1078
|
+
var headers_array = [_]Header{
|
|
1079
|
+
std.mem.zeroInit(Header, .{ .op = 9, .view = 10 }),
|
|
1080
|
+
std.mem.zeroInit(Header, .{ .op = 6, .view = 7 }),
|
|
1081
|
+
};
|
|
1082
|
+
|
|
1083
|
+
const headers = ViewChangeHeaders{ .slice = &headers_array };
|
|
1084
|
+
try std.testing.expect(std.meta.eql(headers.view_for_op(11, 12), .{ .min = 12, .max = 12 }));
|
|
1085
|
+
try std.testing.expect(std.meta.eql(headers.view_for_op(10, 12), .{ .min = 12, .max = 12 }));
|
|
1086
|
+
try std.testing.expect(std.meta.eql(headers.view_for_op(9, 12), .{ .min = 10, .max = 10 }));
|
|
1087
|
+
try std.testing.expect(std.meta.eql(headers.view_for_op(8, 12), .{ .min = 7, .max = 10 }));
|
|
1088
|
+
try std.testing.expect(std.meta.eql(headers.view_for_op(7, 12), .{ .min = 7, .max = 10 }));
|
|
1089
|
+
try std.testing.expect(std.meta.eql(headers.view_for_op(6, 12), .{ .min = 7, .max = 7 }));
|
|
1090
|
+
try std.testing.expect(std.meta.eql(headers.view_for_op(5, 12), .{ .min = 0, .max = 7 }));
|
|
1091
|
+
try std.testing.expect(std.meta.eql(headers.view_for_op(0, 12), .{ .min = 0, .max = 7 }));
|
|
1092
|
+
}
|
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
const std = @import("std");
|
|
2
|
-
const assert = std.debug.assert;
|
|
3
|
-
|
|
4
|
-
const constants = @import("constants.zig");
|
|
5
|
-
|
|
6
|
-
const tb = @import("tigerbeetle.zig");
|
|
7
|
-
const Account = tb.Account;
|
|
8
|
-
const Transfer = tb.Transfer;
|
|
9
|
-
|
|
10
|
-
const CreateAccountsResult = tb.CreateAccountsResult;
|
|
11
|
-
const CreateTransfersResult = tb.CreateTransfersResult;
|
|
12
|
-
|
|
13
|
-
const util = @import("util.zig");
|
|
14
|
-
const IO = @import("io.zig").IO;
|
|
15
|
-
const Storage = @import("storage.zig").Storage;
|
|
16
|
-
const MessagePool = @import("message_pool.zig").MessagePool;
|
|
17
|
-
const MessageBus = @import("message_bus.zig").MessageBusClient;
|
|
18
|
-
const StateMachine = @import("state_machine.zig").StateMachineType(Storage, .{
|
|
19
|
-
.message_body_size_max = constants.message_body_size_max,
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
const vsr = @import("vsr.zig");
|
|
23
|
-
const Header = vsr.Header;
|
|
24
|
-
const Client = vsr.Client(StateMachine, MessageBus);
|
|
25
|
-
|
|
26
|
-
pub const log_level: std.log.Level = .alert;
|
|
27
|
-
|
|
28
|
-
pub fn request(
|
|
29
|
-
operation: StateMachine.Operation,
|
|
30
|
-
batch: anytype,
|
|
31
|
-
on_reply: fn (
|
|
32
|
-
user_data: u128,
|
|
33
|
-
operation: StateMachine.Operation,
|
|
34
|
-
results: Client.Error![]const u8,
|
|
35
|
-
) void,
|
|
36
|
-
) !void {
|
|
37
|
-
const allocator = std.heap.page_allocator;
|
|
38
|
-
const client_id = std.crypto.random.int(u128);
|
|
39
|
-
const cluster_id: u32 = 0;
|
|
40
|
-
var addresses = [_]std.net.Address{try std.net.Address.parseIp4("127.0.0.1", constants.port)};
|
|
41
|
-
|
|
42
|
-
var io = try IO.init(32, 0);
|
|
43
|
-
defer io.deinit();
|
|
44
|
-
|
|
45
|
-
var message_pool = try MessagePool.init(allocator, .client);
|
|
46
|
-
defer message_pool.deinit(allocator);
|
|
47
|
-
|
|
48
|
-
var client = try Client.init(
|
|
49
|
-
allocator,
|
|
50
|
-
client_id,
|
|
51
|
-
cluster_id,
|
|
52
|
-
@intCast(u8, addresses.len),
|
|
53
|
-
&message_pool,
|
|
54
|
-
.{
|
|
55
|
-
.configuration = &addresses,
|
|
56
|
-
.io = &io,
|
|
57
|
-
},
|
|
58
|
-
);
|
|
59
|
-
defer client.deinit(allocator);
|
|
60
|
-
|
|
61
|
-
const message = client.get_message();
|
|
62
|
-
defer client.unref(message);
|
|
63
|
-
|
|
64
|
-
const body = std.mem.asBytes(&batch);
|
|
65
|
-
util.copy_disjoint(.inexact, u8, message.buffer[@sizeOf(Header)..], body);
|
|
66
|
-
|
|
67
|
-
client.request(
|
|
68
|
-
0,
|
|
69
|
-
on_reply,
|
|
70
|
-
operation,
|
|
71
|
-
message,
|
|
72
|
-
body.len,
|
|
73
|
-
);
|
|
74
|
-
|
|
75
|
-
while (client.request_queue.count > 0) {
|
|
76
|
-
client.tick();
|
|
77
|
-
try io.run_for_ns(constants.tick_ms * std.time.ns_per_ms);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
pub fn on_create_accounts(
|
|
82
|
-
user_data: u128,
|
|
83
|
-
operation: StateMachine.Operation,
|
|
84
|
-
results: Client.Error![]const u8,
|
|
85
|
-
) void {
|
|
86
|
-
_ = user_data;
|
|
87
|
-
_ = operation;
|
|
88
|
-
|
|
89
|
-
print_results(CreateAccountsResult, results);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
pub fn on_lookup_accounts(
|
|
93
|
-
user_data: u128,
|
|
94
|
-
operation: StateMachine.Operation,
|
|
95
|
-
results: Client.Error![]const u8,
|
|
96
|
-
) void {
|
|
97
|
-
_ = user_data;
|
|
98
|
-
_ = operation;
|
|
99
|
-
|
|
100
|
-
print_results(Account, results);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
pub fn on_lookup_transfers(
|
|
104
|
-
user_data: u128,
|
|
105
|
-
operation: StateMachine.Operation,
|
|
106
|
-
results: Client.Error![]const u8,
|
|
107
|
-
) void {
|
|
108
|
-
_ = user_data;
|
|
109
|
-
_ = operation;
|
|
110
|
-
|
|
111
|
-
print_results(Transfer, results);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
pub fn on_create_transfers(
|
|
115
|
-
user_data: u128,
|
|
116
|
-
operation: StateMachine.Operation,
|
|
117
|
-
results: Client.Error![]const u8,
|
|
118
|
-
) void {
|
|
119
|
-
_ = user_data;
|
|
120
|
-
_ = operation;
|
|
121
|
-
|
|
122
|
-
print_results(CreateTransfersResult, results);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
fn print_results(comptime Results: type, results: Client.Error![]const u8) void {
|
|
126
|
-
const body = results catch unreachable;
|
|
127
|
-
const slice = std.mem.bytesAsSlice(Results, body);
|
|
128
|
-
for (slice) |result| {
|
|
129
|
-
std.debug.print("{}\n", .{result});
|
|
130
|
-
}
|
|
131
|
-
if (slice.len == 0) std.debug.print("OK\n", .{});
|
|
132
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
const tb = @import("tigerbeetle.zig");
|
|
2
|
-
const demo = @import("demo.zig");
|
|
3
|
-
|
|
4
|
-
const Account = tb.Account;
|
|
5
|
-
|
|
6
|
-
pub fn main() !void {
|
|
7
|
-
const accounts = [_]Account{
|
|
8
|
-
Account{
|
|
9
|
-
.id = 1,
|
|
10
|
-
.user_data = 0,
|
|
11
|
-
.reserved = [_]u8{0} ** 48,
|
|
12
|
-
.ledger = 710, // Let's use the ISO-4217 Code Number for ZAR
|
|
13
|
-
.code = 1000, // A chart of accounts code to describe this as a clearing account.
|
|
14
|
-
.flags = .{ .debits_must_not_exceed_credits = true },
|
|
15
|
-
.debits_pending = 0,
|
|
16
|
-
.debits_posted = 0,
|
|
17
|
-
.credits_pending = 0,
|
|
18
|
-
.credits_posted = 0,
|
|
19
|
-
},
|
|
20
|
-
Account{
|
|
21
|
-
.id = 2,
|
|
22
|
-
.user_data = 0,
|
|
23
|
-
.reserved = [_]u8{0} ** 48,
|
|
24
|
-
.ledger = 710, // Let's use the ISO-4217 Code Number for ZAR
|
|
25
|
-
.code = 2000, // A chart of accounts code to describe this as a payable account.
|
|
26
|
-
.flags = .{},
|
|
27
|
-
.debits_pending = 0,
|
|
28
|
-
.debits_posted = 0,
|
|
29
|
-
.credits_pending = 0,
|
|
30
|
-
.credits_posted = 0,
|
|
31
|
-
},
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
try demo.request(.create_accounts, accounts, demo.on_create_accounts);
|
|
35
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
const tb = @import("tigerbeetle.zig");
|
|
2
|
-
const demo = @import("demo.zig");
|
|
3
|
-
|
|
4
|
-
const Transfer = tb.Transfer;
|
|
5
|
-
|
|
6
|
-
pub fn main() !void {
|
|
7
|
-
const transfers = [_]Transfer{
|
|
8
|
-
Transfer{
|
|
9
|
-
.id = 1,
|
|
10
|
-
.debit_account_id = 2,
|
|
11
|
-
.credit_account_id = 1,
|
|
12
|
-
.user_data = 0,
|
|
13
|
-
.reserved = 0,
|
|
14
|
-
.pending_id = 0,
|
|
15
|
-
.timeout = 0,
|
|
16
|
-
.ledger = 710, // Let's use the ISO-4217 Code Number for ZAR
|
|
17
|
-
.code = 1,
|
|
18
|
-
.flags = .{},
|
|
19
|
-
.amount = 10000, // Let's start with some liquidity in account 1.
|
|
20
|
-
},
|
|
21
|
-
Transfer{
|
|
22
|
-
.id = 2,
|
|
23
|
-
.debit_account_id = 1,
|
|
24
|
-
.credit_account_id = 2,
|
|
25
|
-
.user_data = 0,
|
|
26
|
-
.reserved = 0,
|
|
27
|
-
.pending_id = 0,
|
|
28
|
-
.timeout = 0,
|
|
29
|
-
.ledger = 710, // Let's use the ISO-4217 Code Number for ZAR
|
|
30
|
-
.code = 1,
|
|
31
|
-
.flags = .{},
|
|
32
|
-
.amount = 1000,
|
|
33
|
-
},
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
try demo.request(.create_transfers, transfers, demo.on_create_transfers);
|
|
37
|
-
}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
const std = @import("std");
|
|
2
|
-
|
|
3
|
-
const tb = @import("tigerbeetle.zig");
|
|
4
|
-
const demo = @import("demo.zig");
|
|
5
|
-
|
|
6
|
-
const Transfer = tb.Transfer;
|
|
7
|
-
|
|
8
|
-
pub fn main() !void {
|
|
9
|
-
const transfers = [_]Transfer{
|
|
10
|
-
Transfer{
|
|
11
|
-
.id = 1001,
|
|
12
|
-
.debit_account_id = 1,
|
|
13
|
-
.credit_account_id = 2,
|
|
14
|
-
.user_data = 0,
|
|
15
|
-
.reserved = 0,
|
|
16
|
-
.pending_id = 0,
|
|
17
|
-
.timeout = std.time.ns_per_hour,
|
|
18
|
-
.ledger = 710,
|
|
19
|
-
.code = 1,
|
|
20
|
-
.flags = .{
|
|
21
|
-
.pending = true, // Set this transfer to be two-phase.
|
|
22
|
-
},
|
|
23
|
-
.amount = 8000,
|
|
24
|
-
},
|
|
25
|
-
Transfer{
|
|
26
|
-
.id = 1002,
|
|
27
|
-
.debit_account_id = 1,
|
|
28
|
-
.credit_account_id = 2,
|
|
29
|
-
.user_data = 0,
|
|
30
|
-
.reserved = 0,
|
|
31
|
-
.pending_id = 0,
|
|
32
|
-
.timeout = std.time.ns_per_hour,
|
|
33
|
-
.ledger = 710,
|
|
34
|
-
.code = 1,
|
|
35
|
-
.flags = .{
|
|
36
|
-
.pending = true, // Set this transfer to be two-phase.
|
|
37
|
-
.linked = true, // Link this transfer with the next transfer 1003.
|
|
38
|
-
},
|
|
39
|
-
.amount = 500,
|
|
40
|
-
},
|
|
41
|
-
Transfer{
|
|
42
|
-
.id = 1003,
|
|
43
|
-
.debit_account_id = 1,
|
|
44
|
-
.credit_account_id = 2,
|
|
45
|
-
.user_data = 0,
|
|
46
|
-
.reserved = 0,
|
|
47
|
-
.pending_id = 0,
|
|
48
|
-
.timeout = std.time.ns_per_hour,
|
|
49
|
-
.ledger = 710,
|
|
50
|
-
.code = 1,
|
|
51
|
-
.flags = .{
|
|
52
|
-
.pending = true, // Set this transfer to be two-phase.
|
|
53
|
-
// The last transfer in a linked chain has .linked set to false to close the chain.
|
|
54
|
-
// This transfer will succeed or fail together with transfer 1002 above.
|
|
55
|
-
},
|
|
56
|
-
.amount = 500,
|
|
57
|
-
},
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
try demo.request(.create_transfers, transfers, demo.on_create_transfers);
|
|
61
|
-
}
|