tigerbeetle-node 0.11.5 → 0.11.6

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 (46) hide show
  1. package/dist/.client.node.sha256 +1 -1
  2. package/dist/index.d.ts +41 -42
  3. package/dist/index.js +41 -42
  4. package/dist/index.js.map +1 -1
  5. package/package.json +2 -2
  6. package/src/index.ts +0 -1
  7. package/src/tigerbeetle/scripts/benchmark.bat +6 -1
  8. package/src/tigerbeetle/scripts/benchmark.sh +1 -1
  9. package/src/tigerbeetle/src/c/tb_client.h +42 -43
  10. package/src/tigerbeetle/src/cli.zig +32 -8
  11. package/src/tigerbeetle/src/config.zig +24 -3
  12. package/src/tigerbeetle/src/constants.zig +8 -5
  13. package/src/tigerbeetle/src/lsm/compaction.zig +9 -43
  14. package/src/tigerbeetle/src/lsm/forest_fuzz.zig +2 -7
  15. package/src/tigerbeetle/src/lsm/groove.zig +3 -0
  16. package/src/tigerbeetle/src/lsm/manifest_level.zig +1 -0
  17. package/src/tigerbeetle/src/lsm/manifest_log_fuzz.zig +17 -9
  18. package/src/tigerbeetle/src/lsm/merge_iterator.zig +106 -0
  19. package/src/tigerbeetle/src/lsm/posted_groove.zig +1 -0
  20. package/src/tigerbeetle/src/lsm/segmented_array.zig +1 -0
  21. package/src/tigerbeetle/src/lsm/table.zig +14 -0
  22. package/src/tigerbeetle/src/lsm/table_iterator.zig +2 -2
  23. package/src/tigerbeetle/src/lsm/table_mutable.zig +49 -15
  24. package/src/tigerbeetle/src/lsm/test.zig +8 -4
  25. package/src/tigerbeetle/src/lsm/tree_fuzz.zig +302 -263
  26. package/src/tigerbeetle/src/main.zig +22 -25
  27. package/src/tigerbeetle/src/message_pool.zig +2 -1
  28. package/src/tigerbeetle/src/simulator.zig +22 -79
  29. package/src/tigerbeetle/src/{test/accounting → state_machine}/auditor.zig +8 -8
  30. package/src/tigerbeetle/src/{test/accounting → state_machine}/workload.zig +108 -48
  31. package/src/tigerbeetle/src/state_machine.zig +20 -14
  32. package/src/tigerbeetle/src/test/cluster.zig +11 -11
  33. package/src/tigerbeetle/src/test/conductor.zig +2 -3
  34. package/src/tigerbeetle/src/test/id.zig +10 -0
  35. package/src/tigerbeetle/src/test/state_machine.zig +151 -46
  36. package/src/tigerbeetle/src/tigerbeetle.zig +0 -1
  37. package/src/tigerbeetle/src/unit_tests.zig +2 -2
  38. package/src/tigerbeetle/src/vsr/client.zig +5 -5
  39. package/src/tigerbeetle/src/vsr/clock.zig +2 -2
  40. package/src/tigerbeetle/src/vsr/journal.zig +537 -487
  41. package/src/tigerbeetle/src/vsr/replica.zig +324 -314
  42. package/src/tigerbeetle/src/vsr/replica_format.zig +7 -4
  43. package/src/tigerbeetle/src/vsr/superblock.zig +76 -31
  44. package/src/tigerbeetle/src/vsr/superblock_fuzz.zig +10 -5
  45. package/src/tigerbeetle/src/vsr/superblock_quorums_fuzz.zig +3 -3
  46. package/src/tigerbeetle/src/vsr.zig +5 -5
@@ -45,18 +45,7 @@ pub fn main() !void {
45
45
 
46
46
  switch (parse_args) {
47
47
  .format => |*args| try Command.format(allocator, args.cluster, args.replica, args.path),
48
- .start => |*args| try Command.start(
49
- &arena,
50
- args.addresses,
51
- .{
52
- // TODO Tune lsm_forest_node_count better.
53
- .lsm_forest_node_count = 4096,
54
- .cache_entries_accounts = args.cache_accounts,
55
- .cache_entries_transfers = args.cache_transfers,
56
- .cache_entries_posted = args.cache_transfers_posted,
57
- },
58
- args.path,
59
- ),
48
+ .start => |*args| try Command.start(&arena, args),
60
49
  .version => |*args| try Command.version(allocator, args.verbose),
61
50
  }
62
51
  }
@@ -113,43 +102,51 @@ const Command = struct {
113
102
 
114
103
  var superblock = try SuperBlock.init(
115
104
  allocator,
116
- &command.storage,
117
- &command.message_pool,
105
+ .{
106
+ .storage = &command.storage,
107
+ .storage_size_limit = data_file_size_min,
108
+ .message_pool = &command.message_pool,
109
+ },
118
110
  );
119
111
  defer superblock.deinit(allocator);
120
112
 
121
113
  try vsr.format(Storage, allocator, cluster, replica, &command.storage, &superblock);
122
114
  }
123
115
 
124
- pub fn start(
125
- arena: *std.heap.ArenaAllocator,
126
- addresses: []std.net.Address,
127
- options: StateMachine.Options,
128
- path: [:0]const u8,
129
- ) !void {
116
+ pub fn start(arena: *std.heap.ArenaAllocator, args: *const cli.Command.Start) !void {
130
117
  var tracer_allocator = if (constants.tracer_backend == .tracy)
131
118
  tracer.TracerAllocator.init(arena.allocator())
132
119
  else
133
120
  arena;
134
121
 
122
+ // TODO Panic if the data file's size is larger that args.storage_size_limit.
123
+ // (Here or in Replica.open()?).
124
+
135
125
  const allocator = tracer_allocator.allocator();
136
126
 
137
127
  try tracer.init(allocator);
138
128
  defer tracer.deinit(allocator);
139
129
 
140
130
  var command: Command = undefined;
141
- try command.init(allocator, path, false);
131
+ try command.init(allocator, args.path, false);
142
132
  defer command.deinit(allocator);
143
133
 
144
134
  var replica: Replica = undefined;
145
135
  replica.open(allocator, .{
146
- .replica_count = @intCast(u8, addresses.len),
136
+ .replica_count = @intCast(u8, args.addresses.len),
137
+ .storage_size_limit = args.storage_size_limit,
147
138
  .storage = &command.storage,
148
139
  .message_pool = &command.message_pool,
149
140
  .time = .{},
150
- .state_machine_options = options,
141
+ .state_machine_options = .{
142
+ // TODO Tune lsm_forest_node_count better.
143
+ .lsm_forest_node_count = 4096,
144
+ .cache_entries_accounts = args.cache_accounts,
145
+ .cache_entries_transfers = args.cache_transfers,
146
+ .cache_entries_posted = args.cache_transfers_posted,
147
+ },
151
148
  .message_bus_options = .{
152
- .configuration = addresses,
149
+ .configuration = args.addresses,
153
150
  .io = &command.io,
154
151
  },
155
152
  }) catch |err| switch (err) {
@@ -178,7 +175,7 @@ const Command = struct {
178
175
  log_main.info("{}: cluster={}: listening on {}", .{
179
176
  replica.replica,
180
177
  replica.cluster,
181
- addresses[replica.replica],
178
+ args.addresses[replica.replica],
182
179
  });
183
180
 
184
181
  while (true) {
@@ -22,7 +22,8 @@ const message_size_max_padded = constants.message_size_max + constants.sector_si
22
22
  pub const messages_max_replica = messages_max: {
23
23
  var sum: usize = 0;
24
24
 
25
- sum += constants.journal_iops_read_max + constants.journal_iops_write_max; // Journal I/O
25
+ sum += constants.journal_iops_read_max; // Journal reads
26
+ sum += constants.journal_iops_write_max; // Journal writes
26
27
  sum += constants.clients_max; // SuperBlock.client_table
27
28
  sum += 1; // Replica.loopback_queue
28
29
  sum += constants.pipeline_max; // Replica.pipeline
@@ -17,9 +17,7 @@ const StateChecker = @import("test/state_checker.zig").StateChecker;
17
17
  const StorageChecker = @import("test/storage_checker.zig").StorageChecker;
18
18
  const PartitionMode = @import("test/packet_simulator.zig").PartitionMode;
19
19
  const MessageBus = @import("test/message_bus.zig").MessageBus;
20
- const auditor = @import("test/accounting/auditor.zig");
21
- const Workload = @import("test/accounting/workload.zig").WorkloadType(StateMachine);
22
- const Conductor = @import("test/conductor.zig").ConductorType(Client, MessageBus, StateMachine, Workload);
20
+ const Conductor = @import("test/conductor.zig").ConductorType(Client, MessageBus, StateMachine);
23
21
  const IdPermutation = @import("test/id.zig").IdPermutation;
24
22
 
25
23
  /// The `log` namespace in this root file is required to implement our custom `log` function.
@@ -88,16 +86,17 @@ pub fn main() !void {
88
86
  const idle_on_probability = random.uintLessThan(u8, 20);
89
87
  const idle_off_probability = 10 + random.uintLessThan(u8, 10);
90
88
 
91
- // TODO: When block recovery and state transfer are implemented, remove this flag to allow
92
- // crashes to coexist with WAL wraps.
89
+ // The maximum number of transitions from calling `client.request()`, not including
90
+ // `register` messages.
93
91
  const requests_committed_max: usize = constants.journal_slot_count * 3;
94
92
 
95
93
  const cluster_options: ClusterOptions = .{
96
94
  .cluster = cluster_id,
97
95
  .replica_count = replica_count,
98
96
  .client_count = client_count,
99
- // TODO Compute an upper-bound for this based on requests_committed_max.
100
- .grid_size_max = 1024 * 1024 * 256,
97
+ .storage_size_limit = vsr.sector_floor(
98
+ constants.storage_size_max - random.uintLessThan(u64, constants.storage_size_max / 10),
99
+ ),
101
100
  .seed = random.int(u64),
102
101
  .on_change_state = on_replica_change_state,
103
102
  .on_compact = on_replica_compact,
@@ -132,11 +131,7 @@ pub fn main() !void {
132
131
  .write_latency_mean = 3 + random.uintLessThan(u16, 100),
133
132
  .read_fault_probability = random.uintLessThan(u8, 10),
134
133
  .write_fault_probability = random.uintLessThan(u8, 10),
135
- // TODO Allow WAL faults on crash when replica_count=1 when redundant-header-repair
136
- // is implemented after recovering with decision=fix. Otherwise we can end up with
137
- // multiple crashes faulting first a redundant headers, then a prepare, upgrading
138
- // a decision=fix to decision=vsr.
139
- .crash_fault_probability = if (replica_count == 1) 0 else 80 + random.uintLessThan(u8, 21),
134
+ .crash_fault_probability = 80 + random.uintLessThan(u8, 21),
140
135
  .faulty_superblock = true,
141
136
  },
142
137
  .health_options = .{
@@ -145,62 +140,18 @@ pub fn main() !void {
145
140
  .restart_probability = 0.0001,
146
141
  .restart_stability = random.uintLessThan(u32, 1_000),
147
142
  },
148
- .state_machine_options = .{
149
- // TODO What should these fields be set to? Can they be randomized (and with what constraints)?
150
- .lsm_forest_node_count = 4096,
151
- .cache_entries_accounts = 2048,
152
- .cache_entries_transfers = 2048,
153
- .cache_entries_posted = 2048,
143
+ // TODO(dj) SimulatorType(StateMachine).init(state_machine_options)
144
+ .state_machine_options = switch (constants.state_machine) {
145
+ .testing => .{},
146
+ .accounting => .{
147
+ .lsm_forest_node_count = 4096,
148
+ .cache_entries_accounts = if (random.boolean()) 0 else 2048,
149
+ .cache_entries_transfers = 0,
150
+ .cache_entries_posted = if (random.boolean()) 0 else 2048,
151
+ },
154
152
  },
155
153
  };
156
154
 
157
- const workload_options: Workload.Options = .{
158
- .auditor_options = .{
159
- .accounts_max = 2 + random.uintLessThan(usize, 128),
160
- .account_id_permutation = random_id_permutation(random),
161
- .client_count = client_count,
162
- .transfers_pending_max = 256,
163
- .in_flight_max = Conductor.stalled_queue_capacity,
164
- },
165
- .transfer_id_permutation = random_id_permutation(random),
166
- .operations = .{
167
- .create_accounts = 1 + random.uintLessThan(usize, 10),
168
- .create_transfers = 1 + random.uintLessThan(usize, 100),
169
- .lookup_accounts = 1 + random.uintLessThan(usize, 20),
170
- .lookup_transfers = 1 + random.uintLessThan(usize, 20),
171
- },
172
- .create_account_invalid_probability = 1,
173
- .create_transfer_invalid_probability = 1,
174
- .create_transfer_limit_probability = random.uintLessThan(u8, 101),
175
- .create_transfer_pending_probability = 1 + random.uintLessThan(u8, 100),
176
- .create_transfer_post_probability = 1 + random.uintLessThan(u8, 50),
177
- .create_transfer_void_probability = 1 + random.uintLessThan(u8, 50),
178
- .lookup_account_invalid_probability = 1,
179
- .lookup_transfer = .{
180
- .delivered = 1 + random.uintLessThan(usize, 10),
181
- .sending = 1 + random.uintLessThan(usize, 10),
182
- },
183
- .lookup_transfer_span_mean = 10 + random.uintLessThan(usize, 1000),
184
- .account_limit_probability = random.uintLessThan(u8, 80),
185
- .linked_valid_probability = random.uintLessThan(u8, 101),
186
- // 100% chance because this only applies to consecutive invalid transfers, which are rare.
187
- .linked_invalid_probability = 100,
188
- // TODO(Timeouts): When timeouts are implemented in the StateMachine, change this to the
189
- // (commented out) value so that timeouts can actually trigger.
190
- .pending_timeout_mean = std.math.maxInt(u64) / 2,
191
- // .pending_timeout_mean = 1 + random.uintLessThan(usize, 1_000_000_000 / 4),
192
- .accounts_batch_size_min = 0,
193
- .accounts_batch_size_span = 1 + random.uintLessThan(
194
- usize,
195
- StateMachine.constants.batch_max.create_accounts,
196
- ),
197
- .transfers_batch_size_min = 0,
198
- .transfers_batch_size_span = 1 + random.uintLessThan(
199
- usize,
200
- StateMachine.constants.batch_max.create_transfers,
201
- ),
202
- };
203
-
204
155
  output.info(
205
156
  \\
206
157
  \\ SEED={}
@@ -266,7 +217,12 @@ pub fn main() !void {
266
217
  cluster = try Cluster.create(allocator, random, cluster_options);
267
218
  defer cluster.destroy();
268
219
 
269
- var workload = try Workload.init(allocator, random, workload_options);
220
+ const workload_options = StateMachine.Workload.Options.generate(random, .{
221
+ .client_count = client_count,
222
+ .in_flight_max = Conductor.stalled_queue_capacity,
223
+ });
224
+
225
+ var workload = try StateMachine.Workload.init(allocator, random, workload_options);
270
226
  defer workload.deinit(allocator);
271
227
 
272
228
  var conductor = try Conductor.init(allocator, random, &workload, .{
@@ -318,9 +274,6 @@ pub fn main() !void {
318
274
  storage.faulty = replica_normal_min <= i;
319
275
  }
320
276
 
321
- // The maximum number of transitions from calling `client.request()`, not including
322
- // `register` messages.
323
- // TODO When storage is supported, run more transitions than fit in the journal.
324
277
  var tick: u64 = 0;
325
278
  while (tick < ticks_max) : (tick += 1) {
326
279
  const health_options = &cluster.options.health_options;
@@ -461,16 +414,6 @@ fn random_partition_mode(random: std.rand.Random) PartitionMode {
461
414
  return @intToEnum(PartitionMode, enumAsInt);
462
415
  }
463
416
 
464
- fn random_id_permutation(random: std.rand.Random) IdPermutation {
465
- return switch (random.uintLessThan(usize, 4)) {
466
- 0 => .{ .identity = {} },
467
- 1 => .{ .inversion = {} },
468
- 2 => .{ .zigzag = {} },
469
- 3 => .{ .random = random.int(u64) },
470
- else => unreachable,
471
- };
472
- }
473
-
474
417
  pub fn parse_seed(bytes: []const u8) u64 {
475
418
  return std.fmt.parseUnsigned(u64, bytes, 10) catch |err| switch (err) {
476
419
  error.Overflow => @panic("seed exceeds a 64-bit unsigned integer"),
@@ -6,16 +6,16 @@ const std = @import("std");
6
6
  const assert = std.debug.assert;
7
7
  const log = std.log.scoped(.test_auditor);
8
8
 
9
- const constants = @import("../../constants.zig");
10
- const tb = @import("../../tigerbeetle.zig");
11
- const vsr = @import("../../vsr.zig");
12
- const RingBuffer = @import("../../ring_buffer.zig").RingBuffer;
13
- const IdPermutation = @import("../id.zig").IdPermutation;
9
+ const constants = @import("../constants.zig");
10
+ const tb = @import("../tigerbeetle.zig");
11
+ const vsr = @import("../vsr.zig");
12
+ const RingBuffer = @import("../ring_buffer.zig").RingBuffer;
13
+ const IdPermutation = @import("../test/id.zig").IdPermutation;
14
14
 
15
15
  // TODO(zig) This won't be necessary in Zig 0.10.
16
- const PriorityQueue = @import("../priority_queue.zig").PriorityQueue;
17
- const Storage = @import("../storage.zig").Storage;
18
- const StateMachine = @import("../../state_machine.zig").StateMachineType(Storage, .{
16
+ const PriorityQueue = @import("../test/priority_queue.zig").PriorityQueue;
17
+ const Storage = @import("../test/storage.zig").Storage;
18
+ const StateMachine = @import("../state_machine.zig").StateMachineType(Storage, .{
19
19
  .message_body_size_max = constants.message_body_size_max,
20
20
  });
21
21
 
@@ -21,18 +21,16 @@ const std = @import("std");
21
21
  const assert = std.debug.assert;
22
22
  const log = std.log.scoped(.test_workload);
23
23
 
24
- const constants = @import("../../constants.zig");
25
- const tb = @import("../../tigerbeetle.zig");
26
- const vsr = @import("../../vsr.zig");
27
- const accounting_auditor = @import("./auditor.zig");
24
+ const constants = @import("../constants.zig");
25
+ const tb = @import("../tigerbeetle.zig");
26
+ const vsr = @import("../vsr.zig");
27
+ const accounting_auditor = @import("auditor.zig");
28
28
  const Auditor = accounting_auditor.AccountingAuditor;
29
- const IdPermutation = @import("../id.zig").IdPermutation;
30
- const fuzz = @import("../fuzz.zig");
29
+ const IdPermutation = @import("../test/id.zig").IdPermutation;
30
+ const fuzz = @import("../test/fuzz.zig");
31
31
 
32
32
  // TODO(zig) This won't be necessary in Zig 0.10.
33
- const PriorityQueue = @import("../priority_queue.zig").PriorityQueue;
34
-
35
- // TODO Test linked create_accounts.
33
+ const PriorityQueue = @import("../test/priority_queue.zig").PriorityQueue;
36
34
 
37
35
  const TransferOutcome = enum {
38
36
  /// The transfer is guaranteed to commit.
@@ -177,44 +175,7 @@ pub fn WorkloadType(comptime AccountingStateMachine: type) type {
177
175
  return struct {
178
176
  const Self = @This();
179
177
 
180
- pub const Options = struct {
181
- auditor_options: Auditor.Options,
182
- transfer_id_permutation: IdPermutation,
183
-
184
- operations: std.enums.EnumFieldStruct(Action, usize, null),
185
-
186
- create_account_invalid_probability: u8, // ≤ 100
187
- create_transfer_invalid_probability: u8, // ≤ 100
188
- create_transfer_limit_probability: u8, // ≤ 100
189
- create_transfer_pending_probability: u8, // ≤ 100
190
- create_transfer_post_probability: u8, // ≤ 100
191
- create_transfer_void_probability: u8, // ≤ 100
192
- lookup_account_invalid_probability: u8, // ≤ 100
193
-
194
- lookup_transfer: std.enums.EnumFieldStruct(enum {
195
- /// Query a transfer that has either been committed or rejected.
196
- delivered,
197
- /// Query a transfer whose `create_transfers` is in-flight.
198
- sending,
199
- }, usize, null),
200
-
201
- // Size of timespan for querying, measured in transfers
202
- lookup_transfer_span_mean: usize,
203
-
204
- account_limit_probability: u8, // ≤ 100
205
-
206
- /// This probability is only checked for consecutive guaranteed-successful transfers.
207
- linked_valid_probability: u8,
208
- /// This probability is only checked for consecutive invalid transfers.
209
- linked_invalid_probability: u8,
210
-
211
- pending_timeout_mean: u64,
212
-
213
- accounts_batch_size_min: usize,
214
- accounts_batch_size_span: usize, // inclusive
215
- transfers_batch_size_min: usize,
216
- transfers_batch_size_span: usize, // inclusive
217
- };
178
+ pub const Options = OptionsType(AccountingStateMachine, Action);
218
179
 
219
180
  random: std.rand.Random,
220
181
  auditor: Auditor,
@@ -303,7 +264,11 @@ pub fn WorkloadType(comptime AccountingStateMachine: type) type {
303
264
  }
304
265
 
305
266
  /// A client may build multiple requests to queue up while another is in-flight.
306
- pub fn build_request(self: *Self, client_index: usize, body: []align(@alignOf(vsr.Header)) u8) struct {
267
+ pub fn build_request(
268
+ self: *Self,
269
+ client_index: usize,
270
+ body: []align(@alignOf(vsr.Header)) u8,
271
+ ) struct {
307
272
  operation: Operation,
308
273
  size: usize,
309
274
  } {
@@ -785,6 +750,101 @@ pub fn WorkloadType(comptime AccountingStateMachine: type) type {
785
750
  };
786
751
  }
787
752
 
753
+ fn OptionsType(comptime StateMachine: type, comptime Action: type) type {
754
+ return struct {
755
+ const Options = @This();
756
+
757
+ auditor_options: Auditor.Options,
758
+ transfer_id_permutation: IdPermutation,
759
+
760
+ operations: std.enums.EnumFieldStruct(Action, usize, null),
761
+
762
+ create_account_invalid_probability: u8, // ≤ 100
763
+ create_transfer_invalid_probability: u8, // ≤ 100
764
+ create_transfer_limit_probability: u8, // ≤ 100
765
+ create_transfer_pending_probability: u8, // ≤ 100
766
+ create_transfer_post_probability: u8, // ≤ 100
767
+ create_transfer_void_probability: u8, // ≤ 100
768
+ lookup_account_invalid_probability: u8, // ≤ 100
769
+
770
+ lookup_transfer: std.enums.EnumFieldStruct(enum {
771
+ /// Query a transfer that has either been committed or rejected.
772
+ delivered,
773
+ /// Query a transfer whose `create_transfers` is in-flight.
774
+ sending,
775
+ }, usize, null),
776
+
777
+ // Size of timespan for querying, measured in transfers
778
+ lookup_transfer_span_mean: usize,
779
+
780
+ account_limit_probability: u8, // ≤ 100
781
+
782
+ /// This probability is only checked for consecutive guaranteed-successful transfers.
783
+ linked_valid_probability: u8,
784
+ /// This probability is only checked for consecutive invalid transfers.
785
+ linked_invalid_probability: u8,
786
+
787
+ pending_timeout_mean: u64,
788
+
789
+ accounts_batch_size_min: usize,
790
+ accounts_batch_size_span: usize, // inclusive
791
+ transfers_batch_size_min: usize,
792
+ transfers_batch_size_span: usize, // inclusive
793
+
794
+ pub fn generate(random: std.rand.Random, options: struct {
795
+ client_count: usize,
796
+ in_flight_max: usize,
797
+ }) Options {
798
+ return .{
799
+ .auditor_options = .{
800
+ .accounts_max = 2 + random.uintLessThan(usize, 128),
801
+ .account_id_permutation = IdPermutation.generate(random),
802
+ .client_count = options.client_count,
803
+ .transfers_pending_max = 256,
804
+ .in_flight_max = options.in_flight_max,
805
+ },
806
+ .transfer_id_permutation = IdPermutation.generate(random),
807
+ .operations = .{
808
+ .create_accounts = 1 + random.uintLessThan(usize, 10),
809
+ .create_transfers = 1 + random.uintLessThan(usize, 100),
810
+ .lookup_accounts = 1 + random.uintLessThan(usize, 20),
811
+ .lookup_transfers = 1 + random.uintLessThan(usize, 20),
812
+ },
813
+ .create_account_invalid_probability = 1,
814
+ .create_transfer_invalid_probability = 1,
815
+ .create_transfer_limit_probability = random.uintLessThan(u8, 101),
816
+ .create_transfer_pending_probability = 1 + random.uintLessThan(u8, 100),
817
+ .create_transfer_post_probability = 1 + random.uintLessThan(u8, 50),
818
+ .create_transfer_void_probability = 1 + random.uintLessThan(u8, 50),
819
+ .lookup_account_invalid_probability = 1,
820
+ .lookup_transfer = .{
821
+ .delivered = 1 + random.uintLessThan(usize, 10),
822
+ .sending = 1 + random.uintLessThan(usize, 10),
823
+ },
824
+ .lookup_transfer_span_mean = 10 + random.uintLessThan(usize, 1000),
825
+ .account_limit_probability = random.uintLessThan(u8, 80),
826
+ .linked_valid_probability = random.uintLessThan(u8, 101),
827
+ // 100% chance because this only applies to consecutive invalid transfers, which are rare.
828
+ .linked_invalid_probability = 100,
829
+ // TODO(Timeouts): When timeouts are implemented in the StateMachine, change this to the
830
+ // (commented out) value so that timeouts can actually trigger.
831
+ .pending_timeout_mean = std.math.maxInt(u64) / 2,
832
+ // .pending_timeout_mean = 1 + random.uintLessThan(usize, 1_000_000_000 / 4),
833
+ .accounts_batch_size_min = 0,
834
+ .accounts_batch_size_span = 1 + random.uintLessThan(
835
+ usize,
836
+ StateMachine.constants.batch_max.create_accounts,
837
+ ),
838
+ .transfers_batch_size_min = 0,
839
+ .transfers_batch_size_span = 1 + random.uintLessThan(
840
+ usize,
841
+ StateMachine.constants.batch_max.create_transfers,
842
+ ),
843
+ };
844
+ }
845
+ };
846
+ }
847
+
788
848
  /// Sample from a discrete distribution.
789
849
  /// Use integers instead of floating-point numbers to avoid nondeterminism on different hardware.
790
850
  fn sample_distribution(
@@ -8,6 +8,7 @@ const tracer = @import("tracer.zig");
8
8
 
9
9
  const tb = @import("tigerbeetle.zig");
10
10
  const snapshot_latest = @import("lsm/tree.zig").snapshot_latest;
11
+ const WorkloadType = @import("state_machine/workload.zig").WorkloadType;
11
12
 
12
13
  const Account = tb.Account;
13
14
  const AccountFlags = tb.AccountFlags;
@@ -26,7 +27,6 @@ pub fn StateMachineType(comptime Storage: type, comptime constants_: struct {
26
27
  }) type {
27
28
  return struct {
28
29
  const StateMachine = @This();
29
-
30
30
  const Grid = @import("lsm/grid.zig").GridType(Storage);
31
31
  const GrooveType = @import("lsm/groove.zig").GrooveType;
32
32
  const ForestType = @import("lsm/forest.zig").ForestType;
@@ -49,6 +49,8 @@ pub fn StateMachineType(comptime Storage: type, comptime constants_: struct {
49
49
  );
50
50
  const PostedGroove = @import("lsm/posted_groove.zig").PostedGrooveType(Storage);
51
51
 
52
+ pub const Workload = WorkloadType(StateMachine);
53
+
52
54
  pub const Forest = ForestType(Storage, .{
53
55
  .accounts = AccountsGroove,
54
56
  .transfers = TransfersGroove,
@@ -180,13 +182,13 @@ pub fn StateMachineType(comptime Storage: type, comptime constants_: struct {
180
182
  /// Returns the header's timestamp.
181
183
  pub fn prepare(self: *StateMachine, operation: Operation, input: []u8) u64 {
182
184
  switch (operation) {
185
+ .reserved => unreachable,
183
186
  .root => unreachable,
184
187
  .register => {},
185
188
  .create_accounts => self.prepare_timestamps(.create_accounts, input),
186
189
  .create_transfers => self.prepare_timestamps(.create_transfers, input),
187
190
  .lookup_accounts => {},
188
191
  .lookup_transfers => {},
189
- else => unreachable,
190
192
  }
191
193
  return self.prepare_timestamp;
192
194
  }
@@ -660,10 +662,7 @@ pub fn StateMachineType(comptime Storage: type, comptime constants_: struct {
660
662
  if (t.credit_account_id == t.debit_account_id) return .accounts_must_be_different;
661
663
 
662
664
  if (t.pending_id != 0) return .pending_id_must_be_zero;
663
- if (t.flags.pending) {
664
- // Otherwise, reserved amounts may never be released.
665
- if (t.timeout == 0) return .pending_transfer_must_timeout;
666
- } else {
665
+ if (!t.flags.pending) {
667
666
  if (t.timeout != 0) return .timeout_reserved_for_pending_transfer;
668
667
  }
669
668
 
@@ -822,8 +821,9 @@ pub fn StateMachineType(comptime Storage: type, comptime constants_: struct {
822
821
  }
823
822
 
824
823
  assert(p.timestamp < t.timestamp);
825
- assert(p.timeout > 0);
826
- if (p.timestamp + p.timeout <= t.timestamp) return .pending_transfer_expired;
824
+ if (p.timeout > 0) {
825
+ if (p.timestamp + p.timeout <= t.timestamp) return .pending_transfer_expired;
826
+ }
827
827
 
828
828
  self.forest.grooves.transfers.put_no_clobber(&Transfer{
829
829
  .id = t.id,
@@ -1113,6 +1113,7 @@ test "sum_overflows" {
1113
1113
  const TestContext = struct {
1114
1114
  const Storage = @import("test/storage.zig").Storage;
1115
1115
  const MessagePool = @import("message_pool.zig").MessagePool;
1116
+ const data_file_size_min = @import("vsr/superblock.zig").data_file_size_min;
1116
1117
  const SuperBlock = @import("vsr/superblock.zig").SuperBlockType(Storage);
1117
1118
  const Grid = @import("lsm/grid.zig").GridType(Storage);
1118
1119
  const StateMachine = StateMachineType(Storage, .{
@@ -1147,7 +1148,11 @@ const TestContext = struct {
1147
1148
  };
1148
1149
  errdefer ctx.message_pool.deinit(allocator);
1149
1150
 
1150
- ctx.superblock = try SuperBlock.init(allocator, &ctx.storage, &ctx.message_pool);
1151
+ ctx.superblock = try SuperBlock.init(allocator, .{
1152
+ .storage = &ctx.storage,
1153
+ .storage_size_limit = data_file_size_min,
1154
+ .message_pool = &ctx.message_pool,
1155
+ });
1151
1156
  errdefer ctx.superblock.deinit(allocator);
1152
1157
 
1153
1158
  // Pretend that the superblock is open so that the Forest can initialize.
@@ -1622,7 +1627,6 @@ test "create_transfers/lookup_transfers" {
1622
1627
  \\ transfer T1 A8 -0 _ _ T1 _ L0 C0 _ PEN _ _ _ 0 credit_account_id_must_not_be_int_max
1623
1628
  \\ transfer T1 A8 A8 _ _ T1 _ L0 C0 _ PEN _ _ _ 0 accounts_must_be_different
1624
1629
  \\ transfer T1 A8 A9 _ _ T1 _ L0 C0 _ PEN _ _ _ 0 pending_id_must_be_zero
1625
- \\ transfer T1 A8 A9 _ _ _ _ L0 C0 _ PEN _ _ _ 0 pending_transfer_must_timeout
1626
1630
  \\ transfer T1 A8 A9 _ _ _ -0 L0 C0 _ _ _ _ _ 0 timeout_reserved_for_pending_transfer
1627
1631
  \\ transfer T1 A8 A9 _ _ _ -0 L0 C0 _ PEN _ _ _ 0 ledger_must_not_be_zero
1628
1632
  \\ transfer T1 A8 A9 _ _ _ -0 L9 C0 _ PEN _ _ _ 0 code_must_not_be_zero
@@ -1680,11 +1684,12 @@ test "create/lookup 2-phase transfers" {
1680
1684
  \\ transfer T3 A1 A2 _ _ _ 50 L1 C1 _ PEN _ _ _ 15 ok
1681
1685
  \\ transfer T4 A1 A2 _ _ _ 1 L1 C1 _ PEN _ _ _ 15 ok
1682
1686
  \\ transfer T5 A1 A2 U9 _ _ 50 L1 C1 _ PEN _ _ _ 7 ok
1687
+ \\ transfer T6 A1 A2 _ _ _ 0 L1 C1 _ PEN _ _ _ 1 ok
1683
1688
  \\ commit create_transfers
1684
1689
 
1685
1690
  // Check balances before resolving.
1686
- \\ lookup_account A1 52 15 0 0
1687
- \\ lookup_account A2 0 0 52 15
1691
+ \\ lookup_account A1 53 15 0 0
1692
+ \\ lookup_account A2 0 0 53 15
1688
1693
  \\ commit lookup_accounts
1689
1694
 
1690
1695
  // Second phase.
@@ -1715,11 +1720,12 @@ test "create/lookup 2-phase transfers" {
1715
1720
  \\ transfer T102 A1 A2 U1 _ T3 _ L1 C1 _ _ POS _ _ 13 pending_transfer_already_voided
1716
1721
  \\ transfer T102 A1 A2 U1 _ T4 _ L1 C1 _ _ _ VOI _ 15 pending_transfer_expired
1717
1722
  \\ transfer T105 A0 A0 U0 _ T5 _ L0 C0 _ _ POS _ _ _ ok
1723
+ \\ transfer T106 A0 A0 U0 _ T6 _ L1 C1 _ _ POS _ _ 0 ok
1718
1724
  \\ commit create_transfers
1719
1725
 
1720
1726
  // Check balances after resolving.
1721
- \\ lookup_account A1 15 35 0 0
1722
- \\ lookup_account A2 0 0 15 35
1727
+ \\ lookup_account A1 15 36 0 0
1728
+ \\ lookup_account A2 0 0 15 36
1723
1729
  \\ commit lookup_accounts
1724
1730
  );
1725
1731
  }
@@ -11,7 +11,7 @@ const Message = MessagePool.Message;
11
11
  const Network = @import("network.zig").Network;
12
12
  const NetworkOptions = @import("network.zig").NetworkOptions;
13
13
 
14
- pub const StateMachine = @import("../state_machine.zig").StateMachineType(Storage, .{
14
+ pub const StateMachine = constants.StateMachineType(Storage, .{
15
15
  .message_body_size_max = constants.message_body_size_max,
16
16
  });
17
17
  const MessageBus = @import("message_bus.zig").MessageBus;
@@ -29,7 +29,7 @@ pub const ClusterOptions = struct {
29
29
  cluster: u32,
30
30
  replica_count: u8,
31
31
  client_count: u8,
32
- grid_size_max: usize,
32
+ storage_size_limit: u64,
33
33
 
34
34
  seed: u64,
35
35
  on_change_state: fn (replica: *const Replica) void,
@@ -76,8 +76,8 @@ pub const Cluster = struct {
76
76
  pub fn create(allocator: mem.Allocator, prng: std.rand.Random, options: ClusterOptions) !*Cluster {
77
77
  assert(options.replica_count > 0);
78
78
  assert(options.client_count > 0);
79
- assert(options.grid_size_max > 0);
80
- assert(options.grid_size_max % constants.sector_size == 0);
79
+ assert(options.storage_size_limit % constants.sector_size == 0);
80
+ assert(options.storage_size_limit <= constants.storage_size_max);
81
81
  assert(options.health_options.crash_probability < 1.0);
82
82
  assert(options.health_options.crash_probability >= 0.0);
83
83
  assert(options.health_options.restart_probability < 1.0);
@@ -136,17 +136,17 @@ pub const Cluster = struct {
136
136
  var storage_options = options.storage_options;
137
137
  storage_options.replica_index = @intCast(u8, replica_index);
138
138
  storage_options.faulty_wal_areas = faulty_wal_areas[replica_index];
139
- storage.* = try Storage.init(
140
- allocator,
141
- superblock_zone_size + constants.journal_size_max + options.grid_size_max,
142
- storage_options,
143
- );
139
+ storage.* = try Storage.init(allocator, options.storage_size_limit, storage_options);
144
140
  }
145
141
  errdefer for (cluster.storages) |*storage| storage.deinit(allocator);
146
142
 
147
143
  // Format each replica's storage (equivalent to "tigerbeetle format ...").
148
144
  for (cluster.storages) |*storage, replica_index| {
149
- var superblock = try SuperBlock.init(allocator, storage, &cluster.pools[replica_index]);
145
+ var superblock = try SuperBlock.init(allocator, .{
146
+ .storage = storage,
147
+ .message_pool = &cluster.pools[replica_index],
148
+ .storage_size_limit = options.storage_size_limit,
149
+ });
150
150
  defer superblock.deinit(allocator);
151
151
 
152
152
  try vsr.format(
@@ -330,6 +330,7 @@ pub const Cluster = struct {
330
330
  .{
331
331
  .replica_count = @intCast(u8, cluster.replicas.len),
332
332
  .storage = &cluster.storages[replica_index],
333
+ .storage_size_limit = cluster.options.storage_size_limit,
333
334
  .message_pool = &cluster.pools[replica_index],
334
335
  .time = time,
335
336
  .state_machine_options = cluster.options.state_machine_options,
@@ -339,7 +340,6 @@ pub const Cluster = struct {
339
340
  assert(replica.cluster == cluster.options.cluster);
340
341
  assert(replica.replica == replica_index);
341
342
  assert(replica.replica_count == cluster.replicas.len);
342
- assert(replica.status == .recovering);
343
343
 
344
344
  replica.on_change_state = cluster.options.on_change_state;
345
345
  replica.on_compact = cluster.options.on_compact;