tigerbeetle-node 0.9.0 → 0.11.0

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.
Files changed (112) hide show
  1. package/README.md +305 -103
  2. package/dist/index.d.ts +70 -67
  3. package/dist/index.js +70 -67
  4. package/dist/index.js.map +1 -1
  5. package/package.json +6 -6
  6. package/scripts/download_node_headers.sh +14 -7
  7. package/src/index.ts +11 -10
  8. package/src/node.zig +22 -20
  9. package/src/tigerbeetle/scripts/benchmark.bat +4 -3
  10. package/src/tigerbeetle/scripts/benchmark.sh +25 -10
  11. package/src/tigerbeetle/scripts/confirm_image.sh +44 -0
  12. package/src/tigerbeetle/scripts/fuzz_loop.sh +15 -0
  13. package/src/tigerbeetle/scripts/fuzz_unique_errors.sh +7 -0
  14. package/src/tigerbeetle/scripts/install.sh +20 -4
  15. package/src/tigerbeetle/scripts/install_zig.bat +5 -1
  16. package/src/tigerbeetle/scripts/install_zig.sh +32 -26
  17. package/src/tigerbeetle/scripts/pre-commit.sh +9 -0
  18. package/src/tigerbeetle/scripts/shellcheck.sh +5 -0
  19. package/src/tigerbeetle/scripts/tests_on_alpine.sh +10 -0
  20. package/src/tigerbeetle/scripts/tests_on_ubuntu.sh +14 -0
  21. package/src/tigerbeetle/scripts/upgrade_ubuntu_kernel.sh +12 -3
  22. package/src/tigerbeetle/src/benchmark.zig +19 -9
  23. package/src/tigerbeetle/src/benchmark_array_search.zig +317 -0
  24. package/src/tigerbeetle/src/benchmarks/perf.zig +299 -0
  25. package/src/tigerbeetle/src/c/tb_client/context.zig +103 -0
  26. package/src/tigerbeetle/src/c/tb_client/packet.zig +80 -0
  27. package/src/tigerbeetle/src/c/tb_client/signal.zig +288 -0
  28. package/src/tigerbeetle/src/c/tb_client/thread.zig +328 -0
  29. package/src/tigerbeetle/src/c/tb_client.h +221 -0
  30. package/src/tigerbeetle/src/c/tb_client.zig +104 -0
  31. package/src/tigerbeetle/src/c/test.zig +1 -0
  32. package/src/tigerbeetle/src/cli.zig +143 -84
  33. package/src/tigerbeetle/src/config.zig +161 -20
  34. package/src/tigerbeetle/src/demo.zig +14 -8
  35. package/src/tigerbeetle/src/demo_05_post_pending_transfers.zig +2 -2
  36. package/src/tigerbeetle/src/ewah.zig +318 -0
  37. package/src/tigerbeetle/src/ewah_benchmark.zig +121 -0
  38. package/src/tigerbeetle/src/eytzinger_benchmark.zig +317 -0
  39. package/src/tigerbeetle/src/fifo.zig +17 -1
  40. package/src/tigerbeetle/src/io/darwin.zig +12 -10
  41. package/src/tigerbeetle/src/io/linux.zig +25 -9
  42. package/src/tigerbeetle/src/io/windows.zig +13 -9
  43. package/src/tigerbeetle/src/iops.zig +101 -0
  44. package/src/tigerbeetle/src/lsm/README.md +214 -0
  45. package/src/tigerbeetle/src/lsm/binary_search.zig +341 -0
  46. package/src/tigerbeetle/src/lsm/bloom_filter.zig +125 -0
  47. package/src/tigerbeetle/src/lsm/compaction.zig +557 -0
  48. package/src/tigerbeetle/src/lsm/composite_key.zig +77 -0
  49. package/src/tigerbeetle/src/lsm/direction.zig +11 -0
  50. package/src/tigerbeetle/src/lsm/eytzinger.zig +587 -0
  51. package/src/tigerbeetle/src/lsm/forest.zig +204 -0
  52. package/src/tigerbeetle/src/lsm/forest_fuzz.zig +412 -0
  53. package/src/tigerbeetle/src/lsm/grid.zig +549 -0
  54. package/src/tigerbeetle/src/lsm/groove.zig +1002 -0
  55. package/src/tigerbeetle/src/lsm/k_way_merge.zig +474 -0
  56. package/src/tigerbeetle/src/lsm/level_iterator.zig +315 -0
  57. package/src/tigerbeetle/src/lsm/manifest.zig +580 -0
  58. package/src/tigerbeetle/src/lsm/manifest_level.zig +925 -0
  59. package/src/tigerbeetle/src/lsm/manifest_log.zig +953 -0
  60. package/src/tigerbeetle/src/lsm/node_pool.zig +231 -0
  61. package/src/tigerbeetle/src/lsm/posted_groove.zig +387 -0
  62. package/src/tigerbeetle/src/lsm/segmented_array.zig +1318 -0
  63. package/src/tigerbeetle/src/lsm/segmented_array_benchmark.zig +148 -0
  64. package/src/tigerbeetle/src/lsm/segmented_array_fuzz.zig +9 -0
  65. package/src/tigerbeetle/src/lsm/set_associative_cache.zig +894 -0
  66. package/src/tigerbeetle/src/lsm/table.zig +967 -0
  67. package/src/tigerbeetle/src/lsm/table_immutable.zig +203 -0
  68. package/src/tigerbeetle/src/lsm/table_iterator.zig +306 -0
  69. package/src/tigerbeetle/src/lsm/table_mutable.zig +174 -0
  70. package/src/tigerbeetle/src/lsm/test.zig +423 -0
  71. package/src/tigerbeetle/src/lsm/tree.zig +1090 -0
  72. package/src/tigerbeetle/src/lsm/tree_fuzz.zig +457 -0
  73. package/src/tigerbeetle/src/main.zig +141 -109
  74. package/src/tigerbeetle/src/message_bus.zig +49 -48
  75. package/src/tigerbeetle/src/message_pool.zig +22 -12
  76. package/src/tigerbeetle/src/ring_buffer.zig +126 -30
  77. package/src/tigerbeetle/src/simulator.zig +205 -140
  78. package/src/tigerbeetle/src/state_machine.zig +1268 -721
  79. package/src/tigerbeetle/src/static_allocator.zig +65 -0
  80. package/src/tigerbeetle/src/storage.zig +40 -14
  81. package/src/tigerbeetle/src/test/accounting/auditor.zig +577 -0
  82. package/src/tigerbeetle/src/test/accounting/workload.zig +819 -0
  83. package/src/tigerbeetle/src/test/cluster.zig +104 -88
  84. package/src/tigerbeetle/src/test/conductor.zig +365 -0
  85. package/src/tigerbeetle/src/test/fuzz.zig +121 -0
  86. package/src/tigerbeetle/src/test/id.zig +89 -0
  87. package/src/tigerbeetle/src/test/message_bus.zig +15 -24
  88. package/src/tigerbeetle/src/test/network.zig +26 -17
  89. package/src/tigerbeetle/src/test/priority_queue.zig +645 -0
  90. package/src/tigerbeetle/src/test/state_checker.zig +94 -68
  91. package/src/tigerbeetle/src/test/state_machine.zig +135 -69
  92. package/src/tigerbeetle/src/test/storage.zig +78 -28
  93. package/src/tigerbeetle/src/tigerbeetle.zig +19 -16
  94. package/src/tigerbeetle/src/unit_tests.zig +15 -0
  95. package/src/tigerbeetle/src/util.zig +51 -0
  96. package/src/tigerbeetle/src/vopr.zig +494 -0
  97. package/src/tigerbeetle/src/vopr_hub/README.md +58 -0
  98. package/src/tigerbeetle/src/vopr_hub/SETUP.md +199 -0
  99. package/src/tigerbeetle/src/vopr_hub/go.mod +3 -0
  100. package/src/tigerbeetle/src/vopr_hub/main.go +1022 -0
  101. package/src/tigerbeetle/src/vopr_hub/scheduler/go.mod +3 -0
  102. package/src/tigerbeetle/src/vopr_hub/scheduler/main.go +403 -0
  103. package/src/tigerbeetle/src/vsr/client.zig +34 -7
  104. package/src/tigerbeetle/src/vsr/journal.zig +164 -174
  105. package/src/tigerbeetle/src/vsr/replica.zig +1602 -651
  106. package/src/tigerbeetle/src/vsr/superblock.zig +1761 -0
  107. package/src/tigerbeetle/src/vsr/superblock_client_table.zig +255 -0
  108. package/src/tigerbeetle/src/vsr/superblock_free_set.zig +644 -0
  109. package/src/tigerbeetle/src/vsr/superblock_manifest.zig +561 -0
  110. package/src/tigerbeetle/src/vsr.zig +118 -170
  111. package/src/tigerbeetle/scripts/vopr.bat +0 -48
  112. package/src/tigerbeetle/scripts/vopr.sh +0 -33
@@ -2,9 +2,7 @@ const std = @import("std");
2
2
  const builtin = @import("builtin");
3
3
  const assert = std.debug.assert;
4
4
 
5
- pub const config = @import("config.zig");
6
-
7
- pub const Account = packed struct {
5
+ pub const Account = extern struct {
8
6
  id: u128,
9
7
  /// Opaque third-party identifier to link this account (many-to-one) to an external entity.
10
8
  user_data: u128,
@@ -22,6 +20,7 @@ pub const Account = packed struct {
22
20
 
23
21
  comptime {
24
22
  assert(@sizeOf(Account) == 128);
23
+ assert(@bitSizeOf(Account) == @sizeOf(Account) * 8);
25
24
  }
26
25
 
27
26
  pub fn debits_exceed_credits(self: *const Account, amount: u64) bool {
@@ -56,7 +55,7 @@ pub const AccountFlags = packed struct {
56
55
  }
57
56
  };
58
57
 
59
- pub const Transfer = packed struct {
58
+ pub const Transfer = extern struct {
60
59
  id: u128,
61
60
  debit_account_id: u128,
62
61
  credit_account_id: u128,
@@ -76,6 +75,7 @@ pub const Transfer = packed struct {
76
75
 
77
76
  comptime {
78
77
  assert(@sizeOf(Transfer) == 128);
78
+ assert(@bitSizeOf(Transfer) == @sizeOf(Transfer) * 8);
79
79
  }
80
80
  };
81
81
 
@@ -94,43 +94,43 @@ pub const TransferFlags = packed struct {
94
94
  pub const CreateAccountResult = enum(u32) {
95
95
  ok,
96
96
  linked_event_failed,
97
+ linked_event_chain_open,
97
98
 
98
99
  reserved_flag,
99
100
  reserved_field,
100
101
 
101
102
  id_must_not_be_zero,
103
+ id_must_not_be_int_max,
102
104
  ledger_must_not_be_zero,
103
105
  code_must_not_be_zero,
106
+ debits_pending_must_be_zero,
107
+ debits_posted_must_be_zero,
108
+ credits_pending_must_be_zero,
109
+ credits_posted_must_be_zero,
104
110
 
105
111
  mutually_exclusive_flags,
106
112
 
107
- overflows_debits,
108
- overflows_credits,
109
-
110
- exceeds_credits,
111
- exceeds_debits,
112
-
113
113
  exists_with_different_flags,
114
114
  exists_with_different_user_data,
115
115
  exists_with_different_ledger,
116
116
  exists_with_different_code,
117
- exists_with_different_debits_pending,
118
- exists_with_different_debits_posted,
119
- exists_with_different_credits_pending,
120
- exists_with_different_credits_posted,
121
117
  exists,
122
118
  };
123
119
 
124
120
  pub const CreateTransferResult = enum(u32) {
125
121
  ok,
126
122
  linked_event_failed,
123
+ linked_event_chain_open,
127
124
 
128
125
  reserved_flag,
129
126
  reserved_field,
130
127
 
131
128
  id_must_not_be_zero,
129
+ id_must_not_be_int_max,
132
130
  debit_account_id_must_not_be_zero,
131
+ debit_account_id_must_not_be_int_max,
133
132
  credit_account_id_must_not_be_zero,
133
+ credit_account_id_must_not_be_int_max,
134
134
  accounts_must_be_different,
135
135
 
136
136
  pending_id_must_be_zero,
@@ -171,6 +171,7 @@ pub const CreateTransferResult = enum(u32) {
171
171
  timeout_reserved_for_pending_transfer,
172
172
 
173
173
  pending_id_must_not_be_zero,
174
+ pending_id_must_not_be_int_max,
174
175
  pending_id_must_be_different,
175
176
 
176
177
  pending_transfer_not_found,
@@ -190,21 +191,23 @@ pub const CreateTransferResult = enum(u32) {
190
191
  pending_transfer_expired,
191
192
  };
192
193
 
193
- pub const CreateAccountsResult = packed struct {
194
+ pub const CreateAccountsResult = extern struct {
194
195
  index: u32,
195
196
  result: CreateAccountResult,
196
197
 
197
198
  comptime {
198
199
  assert(@sizeOf(CreateAccountsResult) == 8);
200
+ assert(@bitSizeOf(CreateAccountsResult) == @sizeOf(CreateAccountsResult) * 8);
199
201
  }
200
202
  };
201
203
 
202
- pub const CreateTransfersResult = packed struct {
204
+ pub const CreateTransfersResult = extern struct {
203
205
  index: u32,
204
206
  result: CreateTransferResult,
205
207
 
206
208
  comptime {
207
209
  assert(@sizeOf(CreateTransfersResult) == 8);
210
+ assert(@bitSizeOf(CreateTransfersResult) == @sizeOf(CreateTransfersResult) * 8);
208
211
  }
209
212
  };
210
213
 
@@ -2,6 +2,9 @@ test {
2
2
  _ = @import("vsr.zig");
3
3
  _ = @import("vsr/journal.zig");
4
4
  _ = @import("vsr/marzullo.zig");
5
+ _ = @import("vsr/superblock.zig");
6
+ _ = @import("vsr/superblock_manifest.zig");
7
+ _ = @import("vsr/superblock_free_set.zig");
5
8
  // TODO: clean up logging of clock test and enable it here.
6
9
  //_ = @import("vsr/clock.zig");
7
10
 
@@ -11,4 +14,16 @@ test {
11
14
  _ = @import("ring_buffer.zig");
12
15
 
13
16
  _ = @import("io.zig");
17
+
18
+ _ = @import("ewah.zig");
19
+ _ = @import("util.zig");
20
+
21
+ // TODO Add remaining unit tests from lsm namespace.
22
+ _ = @import("lsm/forest.zig");
23
+ _ = @import("lsm/manifest_level.zig");
24
+ _ = @import("lsm/segmented_array.zig");
25
+
26
+ _ = @import("test/id.zig");
27
+ _ = @import("test/accounting/auditor.zig");
28
+ _ = @import("test/accounting/workload.zig");
14
29
  }
@@ -0,0 +1,51 @@
1
+ const std = @import("std");
2
+ const assert = std.debug.assert;
3
+
4
+ // TODO copy_left(), copy_right()
5
+ // Inline wrappers for mem.copy() and mem.copyBackwards() that assert correctness IFF overlap.
6
+ // It's otherwise too easy to silently use mem.copy() or mem.copyBackwards() incorrectly.
7
+ // Does not assert that there is overlap, only what the direction of the copy should be if there is.
8
+ //
9
+ // TODO copy()
10
+ // Asserts that there is no overlap, or else copy_left() or copy_right() should have been used.
11
+ //
12
+ // TODO copy_exact(), copy_exact_left(), copy_exact_right()
13
+ // Even safer than the above, asserts that the source and target slices have the exact same length.
14
+
15
+ pub inline fn div_ceil(numerator: anytype, denominator: anytype) @TypeOf(numerator, denominator) {
16
+ comptime {
17
+ switch (@typeInfo(@TypeOf(numerator))) {
18
+ .Int => |int| assert(int.signedness == .unsigned),
19
+ .ComptimeInt => assert(numerator >= 0),
20
+ else => @compileError("div_ceil: invalid numerator type"),
21
+ }
22
+
23
+ switch (@typeInfo(@TypeOf(denominator))) {
24
+ .Int => |int| assert(int.signedness == .unsigned),
25
+ .ComptimeInt => assert(denominator > 0),
26
+ else => @compileError("div_ceil: invalid denominator type"),
27
+ }
28
+ }
29
+
30
+ assert(denominator > 0);
31
+
32
+ if (numerator == 0) return 0;
33
+ return @divFloor(numerator - 1, denominator) + 1;
34
+ }
35
+
36
+ test "div_ceil" {
37
+ // Comptime ints.
38
+ try std.testing.expectEqual(div_ceil(0, 8), 0);
39
+ try std.testing.expectEqual(div_ceil(1, 8), 1);
40
+ try std.testing.expectEqual(div_ceil(7, 8), 1);
41
+ try std.testing.expectEqual(div_ceil(8, 8), 1);
42
+ try std.testing.expectEqual(div_ceil(9, 8), 2);
43
+
44
+ // Unsized ints
45
+ const max = std.math.maxInt(u64);
46
+ try std.testing.expectEqual(div_ceil(@as(u64, 0), 8), 0);
47
+ try std.testing.expectEqual(div_ceil(@as(u64, 1), 8), 1);
48
+ try std.testing.expectEqual(div_ceil(@as(u64, max), 2), max / 2 + 1);
49
+ try std.testing.expectEqual(div_ceil(@as(u64, max) - 1, 2), max / 2);
50
+ try std.testing.expectEqual(div_ceil(@as(u64, max) - 2, 2), max / 2);
51
+ }
@@ -0,0 +1,494 @@
1
+ const std = @import("std");
2
+ const fmt = std.fmt;
3
+ const mem = std.mem;
4
+ const net = std.net;
5
+ const os = std.os;
6
+ const assert = std.debug.assert;
7
+ const log = std.log;
8
+
9
+ const simulator = @import("simulator.zig");
10
+ const vsr = @import("vsr.zig");
11
+
12
+ // TODO use DNS instead since hard-coding an IP address isn't ideal.
13
+ const default_send_address = net.Address.initIp4([4]u8{ 65, 21, 207, 251 }, 5555);
14
+
15
+ const usage = fmt.comptimePrint(
16
+ \\Usage:
17
+ \\
18
+ \\ vopr [-h | --help]
19
+ \\
20
+ \\ vopr [--send] [--simulations=<count>]
21
+ \\
22
+ \\ vopr [--send] --seed=<int> [--build-mode=<mode>]
23
+ \\
24
+ \\Options:
25
+ \\
26
+ \\ -h, --help
27
+ \\ Print this help message and exit.
28
+ \\
29
+ \\ --seed=<integer>
30
+ \\ Set the seed to a provided 64-bit unsigned integer.
31
+ \\ By default the VOPR will run a specified seed in debug mode.
32
+ \\ If this option is omitted, a series of random seeds will be generated.
33
+ \\
34
+ \\ --send[=<address>]
35
+ \\ When set, the send option opts in to send any bugs found by the VOPR to the VOPR Hub.
36
+ \\ The VOPR Hub will then automatically create a GitHub issue if it can verify the bug.
37
+ \\ The VOPR Hub's address is already present as the default address.
38
+ \\ You can optionally supply an IPv4 address for the VOPR Hub if needed.
39
+ \\ If this option is omitted, any bugs that are found will replay locally in Debug mode.
40
+ \\
41
+ \\ --build-mode=<mode>
42
+ \\ Set the build mode for the VOPR. Accepts either ReleaseSafe or Debug.
43
+ \\ By default when no seed is provided the VOPR will run in ReleaseSafe mode.
44
+ \\ By default when a seed is provided the VOPR will run in Debug mode.
45
+ \\ Debug mode is only a valid build mode if a seed is also provided.
46
+ \\
47
+ \\ --simulations=<integer>
48
+ \\ Set the number of times for the simulator to run when using randomly generated seeds.
49
+ \\ By default 1000 random seeds will be generated.
50
+ \\ This flag can only be used with ReleaseSafe mode and when no seed has been specified.
51
+ \\
52
+ \\Example:
53
+ \\
54
+ \\ vopr --seed=123 --send=127.0.0.1:5555 --build-mode=ReleaseSafe
55
+ \\ vopr --simulations=10 --send --build-mode=Debug
56
+ \\
57
+ , .{});
58
+
59
+ // The Report struct contains all the information to be sent to the VOPR Hub.
60
+ const Report = struct {
61
+ checksum: [16]u8,
62
+ bug: u8,
63
+ seed: [8]u8,
64
+ commit: [20]u8,
65
+ };
66
+
67
+ const Flags = struct {
68
+ seed: ?u64,
69
+ send_address: ?net.Address, // A null value indicates that the "send" flag is not set.
70
+ build_mode: std.builtin.Mode,
71
+ simulations: u32,
72
+ };
73
+
74
+ const Bug = enum(u8) {
75
+ crash = 127, // Any assertion crash will be given an exit code of 127 by default.
76
+ liveness = 128,
77
+ correctness = 129,
78
+ };
79
+
80
+ pub fn main() void {
81
+ var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
82
+ defer arena.deinit();
83
+
84
+ const allocator = arena.allocator();
85
+
86
+ const args = parse_args(allocator) catch |err| {
87
+ fatal("unable to parse the VOPR's arguments: {}", .{err});
88
+ };
89
+
90
+ if (args.send_address != null) {
91
+ check_git_status(allocator);
92
+ }
93
+
94
+ // If a seed is provided as an argument then replay the seed, otherwise test a 1,000 seeds:
95
+ if (args.seed) |seed| {
96
+ // Build in fast ReleaseSafe mode if required, useful where you don't need debug logging:
97
+ if (args.build_mode != .Debug) {
98
+ // Build the simulator binary
99
+ build_simulator(allocator, .ReleaseSafe);
100
+ log.debug("Replaying seed {} in ReleaseSafe mode...\n", .{seed});
101
+ _ = run_simulator(allocator, seed, .ReleaseSafe, args.send_address);
102
+ } else {
103
+ // Build the simulator binary
104
+ build_simulator(allocator, .Debug);
105
+ log.debug(
106
+ "Replaying seed {} in Debug mode with full debug logging enabled...\n",
107
+ .{seed},
108
+ );
109
+ _ = run_simulator(allocator, seed, .Debug, args.send_address);
110
+ }
111
+ } else if (args.build_mode == .Debug) {
112
+ fatal("no seed provided: the VOPR must be run with --mode=ReleaseSafe", .{});
113
+ } else {
114
+ // Build the simulator binary
115
+ build_simulator(allocator, .ReleaseSafe);
116
+ // Run the simulator with randomly generated seeds.
117
+ var i: u32 = 0;
118
+ while (i < args.simulations) : (i += 1) {
119
+ const seed_random = std.crypto.random.int(u64);
120
+ const exit_code = run_simulator(
121
+ allocator,
122
+ seed_random,
123
+ .ReleaseSafe,
124
+ args.send_address,
125
+ );
126
+ if (exit_code != null) {
127
+ // If a seed fails exit the loop.
128
+ break;
129
+ }
130
+ }
131
+ }
132
+ }
133
+
134
+ // Builds the simulator binary.
135
+ fn build_simulator(
136
+ allocator: mem.Allocator,
137
+ mode: std.builtin.Mode,
138
+ ) void {
139
+ const mode_str = switch (mode) {
140
+ .Debug => "-ODebug",
141
+ .ReleaseSafe => "-OReleaseSafe",
142
+ else => unreachable,
143
+ };
144
+
145
+ const exec_result = std.ChildProcess.exec(.{
146
+ .allocator = allocator,
147
+ .argv = &.{ "zig/zig", "build-exe", "./src/simulator.zig", mode_str },
148
+ }) catch |err| {
149
+ fatal("unable to build the simulator binary. Error: {}", .{err});
150
+ };
151
+ defer allocator.free(exec_result.stdout);
152
+ defer allocator.free(exec_result.stderr);
153
+
154
+ switch (exec_result.term) {
155
+ .Exited => |code| {
156
+ if (code != 0) {
157
+ fatal("unable to build the simulator binary. Term: {}\n", .{exec_result.term});
158
+ }
159
+ },
160
+ else => {
161
+ fatal("unable to build the simulator binary. Term: {}\n", .{exec_result.term});
162
+ },
163
+ }
164
+ }
165
+
166
+ // Runs the simulator as a child process.
167
+ // Reruns the simulator in Debug mode if a seed fails in ReleaseSafe mode.
168
+ fn run_simulator(
169
+ allocator: mem.Allocator,
170
+ seed: u64,
171
+ mode: std.builtin.Mode,
172
+ send_address: ?net.Address,
173
+ ) ?Bug {
174
+ var seed_str = std.ArrayList(u8).init(allocator);
175
+ defer seed_str.deinit();
176
+
177
+ fmt.formatInt(seed, 10, .lower, .{}, seed_str.writer()) catch |err| switch (err) {
178
+ error.OutOfMemory => fatal("unable to format seed as an int. Error: {}", .{err}),
179
+ };
180
+
181
+ // The child process executes zig run instead of zig build. Otherwise the build process is
182
+ // interposed between the VOPR and the simulator and its exit code is returned instead of the
183
+ // simulator's exit code.
184
+ const exit_code = run_child_process(
185
+ allocator,
186
+ &.{ "./simulator", seed_str.items },
187
+ );
188
+
189
+ const result = switch (exit_code) {
190
+ 0 => null,
191
+ 127 => Bug.crash,
192
+ 128 => Bug.liveness,
193
+ 129 => Bug.correctness,
194
+ else => {
195
+ log.debug("unexpected simulator exit code: {}\n", .{exit_code});
196
+ @panic("unexpected simulator exit code.");
197
+ },
198
+ };
199
+
200
+ if (result) |bug| {
201
+ if (send_address) |hub_address| {
202
+ var report: Report = create_report(allocator, bug, seed);
203
+ send_report(report, hub_address);
204
+ }
205
+
206
+ if (mode == .ReleaseSafe) {
207
+ log.debug("simulator exited with exit code {}.\n", .{@enumToInt(bug)});
208
+ log.debug("rerunning seed {} in Debug mode.\n", .{seed});
209
+ // Build the simulator binary in Debug mode instead.
210
+ build_simulator(allocator, .Debug);
211
+ assert(bug == run_simulator(allocator, seed, .Debug, null).?);
212
+ }
213
+ }
214
+ return result;
215
+ }
216
+
217
+ // Initializes and executes the simulator as a child process.
218
+ // Terminates the VOPR if the simulator fails to run or exits without an exit code.
219
+ fn run_child_process(allocator: mem.Allocator, argv: []const []const u8) u8 {
220
+ const child_process = std.ChildProcess.init(argv, allocator) catch |err| {
221
+ fatal("unable to initialize simulator as a child process. Error: {}", .{err});
222
+ };
223
+ defer child_process.deinit();
224
+
225
+ child_process.stdout = std.io.getStdOut();
226
+ child_process.stderr = std.io.getStdErr();
227
+
228
+ // Using spawn instead of exec because spawn allows output to be streamed instead of buffered.
229
+ const term = child_process.spawnAndWait() catch |err| {
230
+ fatal("unable to run the simulator as a child process. Error: {}", .{err});
231
+ };
232
+
233
+ switch (term) {
234
+ .Exited => |code| {
235
+ log.debug("exit with code: {}\n", .{code});
236
+ return code;
237
+ },
238
+ .Signal => |code| {
239
+ switch (code) {
240
+ 6 => {
241
+ log.debug("exit with signal: {}. Indicates a crash bug.\n", .{code});
242
+ return @enumToInt(Bug.crash);
243
+ },
244
+ else => {
245
+ fatal("the simulator exited with an unexpected signal. Term: {}\n", .{term});
246
+ },
247
+ }
248
+ },
249
+ else => {
250
+ fatal("the simulator exited without an exit code. Term: {}\n", .{term});
251
+ },
252
+ }
253
+ }
254
+
255
+ fn check_git_status(allocator: mem.Allocator) void {
256
+ // Running git status to determine whether there is any uncommitted code or local changes.
257
+ var args = [2][]const u8{ "git", "status" };
258
+ var exec_result = std.ChildProcess.exec(.{
259
+ .allocator = allocator,
260
+ .argv = &args,
261
+ }) catch |err| {
262
+ fatal("unable to determine TigerBeetle's git status. Error: {}", .{err});
263
+ };
264
+ defer allocator.free(exec_result.stdout);
265
+ defer allocator.free(exec_result.stderr);
266
+
267
+ var git_status = exec_result.stdout;
268
+ var code_committed = mem.containsAtLeast(
269
+ u8,
270
+ git_status,
271
+ 1,
272
+ "nothing to commit, working tree clean",
273
+ );
274
+ var code_up_to_date = (mem.containsAtLeast(
275
+ u8,
276
+ git_status,
277
+ 1,
278
+ "Your branch is up to date",
279
+ ) or
280
+ mem.containsAtLeast(
281
+ u8,
282
+ git_status,
283
+ 1,
284
+ "HEAD detached at",
285
+ ));
286
+ if (code_committed and code_up_to_date) {
287
+ log.debug("All code has been committed and pushed.\n", .{});
288
+ } else {
289
+ fatal(
290
+ "the VOPR cannot run with --send when your branch is ahead or there's uncommited code",
291
+ .{},
292
+ );
293
+ }
294
+ }
295
+
296
+ // Sends a bug report to the VOPR Hub.
297
+ // The VOPR Hub will attempt to verify the bug and automatically create a GitHub issue.
298
+ fn send_report(report: Report, address: net.Address) void {
299
+ // Bug type
300
+ assert(report.bug == 1 or report.bug == 2 or report.bug == 3);
301
+
302
+ const vopr_message_size = 45;
303
+ var message: [vopr_message_size]u8 = undefined;
304
+ message[0..16].* = report.checksum;
305
+ message[16] = report.bug;
306
+ message[17..25].* = report.seed;
307
+ message[25..vopr_message_size].* = report.commit;
308
+
309
+ const stream = net.tcpConnectToAddress(address) catch |err| {
310
+ fatal("unable to create a connection to the VOPR Hub. Error: {}", .{err});
311
+ };
312
+
313
+ log.debug("Connected to VOPR Hub.\n", .{});
314
+
315
+ var writer = stream.writer();
316
+ writer.writeAll(&message) catch |err| {
317
+ fatal("unable to send the report to the VOPR Hub. Error: {}", .{err});
318
+ };
319
+
320
+ // Receive reply
321
+ var reply: [1]u8 = undefined;
322
+ var reader = stream.reader();
323
+ var bytes_read = reader.readAll(&reply) catch |err| {
324
+ fatal("unable to read a reply from the VOPR Hub. Error: {}", .{err});
325
+ };
326
+ if (bytes_read > 0) {
327
+ log.debug("Confirmation received from VOPR Hub: {s}.\n", .{reply});
328
+ } else {
329
+ log.debug("No reply received from VOPR Hub.\n", .{});
330
+ }
331
+ }
332
+
333
+ // Creating a single report struct that contains all information required for the VOPR Hub.
334
+ fn create_report(allocator: mem.Allocator, bug: Bug, seed: u64) Report {
335
+ log.debug("Collecting VOPR bug and seed, and the current git commit hash.\n", .{});
336
+
337
+ // Setting the bug type.
338
+ var bug_type: u8 = undefined;
339
+ switch (bug) {
340
+ .correctness => bug_type = 1,
341
+ .liveness => bug_type = 2,
342
+ .crash => bug_type = 3,
343
+ }
344
+
345
+ // Running git log to extract the current TigerBeetle git commit hash from stdout.
346
+ var args = [3][]const u8{ "git", "log", "-1" };
347
+ var exec_result = std.ChildProcess.exec(.{
348
+ .allocator = allocator,
349
+ .argv = &args,
350
+ }) catch |err| {
351
+ fatal("unable to extract TigerBeetle's git commit hash. Error: {}", .{err});
352
+ };
353
+ defer allocator.free(exec_result.stdout);
354
+ defer allocator.free(exec_result.stderr);
355
+
356
+ var git_log = exec_result.stdout;
357
+ log.debug("git commit that was retrieved: {s}\n", .{git_log[7..47].*});
358
+
359
+ var commit_string = git_log[7..47].*;
360
+ var commit_byte_array: [20]u8 = undefined;
361
+ _ = fmt.hexToBytes(&commit_byte_array, &commit_string) catch |err| {
362
+ fatal("unable to cast the git commit hash to hex. Error: {}", .{err});
363
+ };
364
+
365
+ // Zig stores value as Little Endian when VOPR Hub is expecting Big Endian.
366
+ assert(@import("builtin").target.cpu.arch.endian() == .Little);
367
+
368
+ var message = Report{
369
+ .checksum = undefined,
370
+ .bug = bug_type,
371
+ .seed = @bitCast([8]u8, @byteSwap(u64, seed)),
372
+ .commit = commit_byte_array,
373
+ };
374
+
375
+ var hash: [32]u8 = undefined;
376
+ std.crypto.hash.sha2.Sha256.hash(std.mem.asBytes(&message)[@sizeOf(u128)..45], hash[0..], .{});
377
+ mem.copy(u8, message.checksum[0..], hash[0..message.checksum.len]);
378
+
379
+ return message;
380
+ }
381
+
382
+ /// Format and print an error message followed by the usage string to stderr,
383
+ /// then exit with an exit code of 1.
384
+ fn fatal(comptime fmt_string: []const u8, args: anytype) noreturn {
385
+ const stderr = std.io.getStdErr().writer();
386
+ stderr.print("error: " ++ fmt_string ++ "\n", args) catch {};
387
+ os.exit(1);
388
+ }
389
+
390
+ /// Parse e.g. `--seed=123` into 123 with error handling.
391
+ fn parse_flag(comptime flag: []const u8, arg: [:0]const u8) [:0]const u8 {
392
+ const value = arg[flag.len..];
393
+ if (value.len < 2) {
394
+ fatal("{s} argument requires a value", .{flag});
395
+ }
396
+ if (value[0] != '=') {
397
+ fatal("expected '=' after {s} but found '{c}'", .{ flag, value[0] });
398
+ }
399
+ return value[1..];
400
+ }
401
+
402
+ // Parses the VOPR arguments to set flag values, otherwise uses default flag values.
403
+ fn parse_args(allocator: mem.Allocator) !Flags {
404
+ // Set default values
405
+ var seed: ?u64 = null;
406
+ var send_address: ?net.Address = null;
407
+ var build_mode: ?std.builtin.Mode = null;
408
+ var simulations: u32 = 1000;
409
+
410
+ var args = try std.process.argsWithAllocator(allocator);
411
+ defer args.deinit();
412
+
413
+ // Keep track of the args from the ArgIterator above that were allocated
414
+ // then free them all at the end of the scope.
415
+ var args_allocated = std.ArrayList([:0]const u8).init(allocator);
416
+ defer {
417
+ for (args_allocated.items) |arg| allocator.free(arg);
418
+ args_allocated.deinit();
419
+ }
420
+
421
+ // Skip argv[0] which is the name of this executable
422
+ assert(args.skip());
423
+
424
+ while (args.next(allocator)) |arg_next| {
425
+ const arg = try arg_next;
426
+ try args_allocated.append(arg);
427
+
428
+ if (mem.startsWith(u8, arg, "--seed")) {
429
+ const seed_string = parse_flag("--seed", arg);
430
+ seed = simulator.parse_seed(seed_string);
431
+ // If a seed is supplied Debug becomes the default mode.
432
+ if (build_mode == null) {
433
+ build_mode = .Debug;
434
+ }
435
+ } else if (mem.startsWith(u8, arg, "--send")) {
436
+ if (mem.eql(u8, arg, "--send")) {
437
+ // If --send is set and no address is supplied then use default address.
438
+ send_address = default_send_address;
439
+ } else {
440
+ const str_address = parse_flag("--send", arg);
441
+ send_address = try vsr.parse_address(str_address);
442
+ }
443
+ } else if (mem.startsWith(u8, arg, "--build-mode")) {
444
+ if (mem.eql(u8, parse_flag("--build-mode", arg), "ReleaseSafe")) {
445
+ build_mode = .ReleaseSafe;
446
+ } else if (mem.eql(u8, parse_flag("--build-mode", arg), "Debug")) {
447
+ build_mode = .Debug;
448
+ } else {
449
+ fatal(
450
+ "unsupported build mode: {s}. Use either ReleaseSafe or Debug mode.",
451
+ .{arg},
452
+ );
453
+ }
454
+ } else if (mem.startsWith(u8, arg, "--simulations")) {
455
+ const num_simulations_string = parse_flag("--simulations", arg);
456
+ simulations = std.fmt.parseUnsigned(
457
+ u32,
458
+ num_simulations_string,
459
+ 10,
460
+ ) catch |err| switch (err) {
461
+ error.Overflow => @panic(
462
+ "the number of simulations exceeds a 16-bit unsigned integer",
463
+ ),
464
+ error.InvalidCharacter => @panic(
465
+ "the number of simulations contains an invalid character",
466
+ ),
467
+ };
468
+ } else if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
469
+ std.io.getStdOut().writeAll(usage) catch os.exit(1);
470
+ os.exit(0);
471
+ } else if (mem.startsWith(u8, arg, "--")) {
472
+ fatal("unexpected argument: '{s}'", .{arg});
473
+ } else {
474
+ fatal("unexpected argument: '{s}' (must start with '--')", .{arg});
475
+ }
476
+ }
477
+
478
+ // Build mode is set last to ensure that if a seed is passed to the VOPR the Debug default
479
+ // doesn't override a user specified mode.
480
+ if (build_mode == null) {
481
+ build_mode = .ReleaseSafe;
482
+ }
483
+
484
+ if (seed == null and build_mode.? != .ReleaseSafe) {
485
+ fatal("random seeds must be run in ReleaseSafe mode", .{});
486
+ }
487
+
488
+ return Flags{
489
+ .seed = seed,
490
+ .send_address = send_address,
491
+ .build_mode = build_mode.?,
492
+ .simulations = simulations,
493
+ };
494
+ }