tigerbeetle-node 0.3.3 → 0.5.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 (71) hide show
  1. package/README.md +21 -7
  2. package/dist/benchmark.js +1 -1
  3. package/dist/benchmark.js.map +1 -1
  4. package/dist/index.d.ts +22 -20
  5. package/dist/index.js +40 -18
  6. package/dist/index.js.map +1 -1
  7. package/dist/test.js +13 -1
  8. package/dist/test.js.map +1 -1
  9. package/package.json +12 -12
  10. package/scripts/postinstall.sh +2 -2
  11. package/src/benchmark.ts +4 -4
  12. package/src/index.ts +35 -9
  13. package/src/node.zig +139 -28
  14. package/src/test.ts +19 -5
  15. package/src/tigerbeetle/scripts/benchmark.sh +10 -3
  16. package/src/tigerbeetle/scripts/install.sh +2 -2
  17. package/src/tigerbeetle/scripts/install_zig.bat +109 -0
  18. package/src/tigerbeetle/scripts/install_zig.sh +21 -4
  19. package/src/tigerbeetle/scripts/vopr.bat +48 -0
  20. package/src/tigerbeetle/scripts/vopr.sh +33 -0
  21. package/src/tigerbeetle/src/benchmark.zig +74 -42
  22. package/src/tigerbeetle/src/cli.zig +136 -83
  23. package/src/tigerbeetle/src/config.zig +80 -26
  24. package/src/tigerbeetle/src/demo.zig +101 -78
  25. package/src/tigerbeetle/src/demo_01_create_accounts.zig +2 -7
  26. package/src/tigerbeetle/src/demo_02_lookup_accounts.zig +2 -7
  27. package/src/tigerbeetle/src/demo_03_create_transfers.zig +2 -7
  28. package/src/tigerbeetle/src/demo_04_create_transfers_two_phase_commit.zig +2 -5
  29. package/src/tigerbeetle/src/demo_05_accept_transfers.zig +2 -7
  30. package/src/tigerbeetle/src/demo_06_reject_transfers.zig +2 -7
  31. package/src/tigerbeetle/src/demo_07_lookup_transfers.zig +8 -0
  32. package/src/tigerbeetle/src/fifo.zig +20 -11
  33. package/src/tigerbeetle/src/io.zig +35 -22
  34. package/src/tigerbeetle/src/io_darwin.zig +701 -0
  35. package/src/tigerbeetle/src/main.zig +72 -25
  36. package/src/tigerbeetle/src/message_bus.zig +379 -456
  37. package/src/tigerbeetle/src/message_pool.zig +3 -3
  38. package/src/tigerbeetle/src/ring_buffer.zig +192 -37
  39. package/src/tigerbeetle/src/simulator.zig +317 -0
  40. package/src/tigerbeetle/src/state_machine.zig +846 -38
  41. package/src/tigerbeetle/src/storage.zig +488 -90
  42. package/src/tigerbeetle/src/test/cluster.zig +221 -0
  43. package/src/tigerbeetle/src/test/message_bus.zig +92 -0
  44. package/src/tigerbeetle/src/test/network.zig +182 -0
  45. package/src/tigerbeetle/src/test/packet_simulator.zig +371 -0
  46. package/src/tigerbeetle/src/test/state_checker.zig +142 -0
  47. package/src/tigerbeetle/src/test/state_machine.zig +71 -0
  48. package/src/tigerbeetle/src/test/storage.zig +375 -0
  49. package/src/tigerbeetle/src/test/time.zig +84 -0
  50. package/src/tigerbeetle/src/tigerbeetle.zig +6 -3
  51. package/src/tigerbeetle/src/time.zig +65 -0
  52. package/src/tigerbeetle/src/unit_tests.zig +14 -0
  53. package/src/tigerbeetle/src/vsr/client.zig +519 -0
  54. package/src/tigerbeetle/src/vsr/clock.zig +829 -0
  55. package/src/tigerbeetle/src/vsr/journal.zig +1368 -0
  56. package/src/tigerbeetle/src/vsr/marzullo.zig +306 -0
  57. package/src/tigerbeetle/src/vsr/replica.zig +4248 -0
  58. package/src/tigerbeetle/src/vsr.zig +601 -0
  59. package/src/tigerbeetle/LICENSE +0 -177
  60. package/src/tigerbeetle/README.md +0 -116
  61. package/src/tigerbeetle/src/client.zig +0 -319
  62. package/src/tigerbeetle/src/concurrent_ranges.zig +0 -162
  63. package/src/tigerbeetle/src/fixed_array_list.zig +0 -53
  64. package/src/tigerbeetle/src/io_async.zig +0 -600
  65. package/src/tigerbeetle/src/journal.zig +0 -567
  66. package/src/tigerbeetle/src/test_client.zig +0 -41
  67. package/src/tigerbeetle/src/test_main.zig +0 -118
  68. package/src/tigerbeetle/src/test_message_bus.zig +0 -132
  69. package/src/tigerbeetle/src/vr/journal.zig +0 -672
  70. package/src/tigerbeetle/src/vr/replica.zig +0 -3061
  71. package/src/tigerbeetle/src/vr.zig +0 -374
@@ -8,22 +8,25 @@ const HashMapAccounts = std.AutoHashMap(u128, Account);
8
8
  const HashMapTransfers = std.AutoHashMap(u128, Transfer);
9
9
  const HashMapCommits = std.AutoHashMap(u128, Commit);
10
10
 
11
- pub const Operation = packed enum(u8) {
12
- // We reserve command "0" to detect an accidental zero byte being interpreted as an operation:
13
- reserved,
14
- init,
15
-
16
- create_accounts,
17
- create_transfers,
18
- commit_transfers,
19
- lookup_accounts,
20
-
21
- pub fn jsonStringify(self: Command, options: StringifyOptions, writer: anytype) !void {
22
- try std.fmt.format(writer, "\"{}\"", .{@tagName(self)});
23
- }
24
- };
25
-
26
11
  pub const StateMachine = struct {
12
+ pub const Operation = enum(u8) {
13
+ /// Operations reserved by VR protocol (for all state machines):
14
+ reserved,
15
+ init,
16
+ register,
17
+
18
+ /// Operations exported by TigerBeetle:
19
+ create_accounts,
20
+ create_transfers,
21
+ commit_transfers,
22
+ lookup_accounts,
23
+ lookup_transfers,
24
+
25
+ pub fn jsonStringify(self: Command, options: StringifyOptions, writer: anytype) !void {
26
+ try std.fmt.format(writer, "\"{}\"", .{@tagName(self)});
27
+ }
28
+ };
29
+
27
30
  allocator: *std.mem.Allocator,
28
31
  prepare_timestamp: u64,
29
32
  commit_timestamp: u64,
@@ -74,6 +77,7 @@ pub const StateMachine = struct {
74
77
  .create_transfers => Transfer,
75
78
  .commit_transfers => Commit,
76
79
  .lookup_accounts => u128,
80
+ .lookup_transfers => u128,
77
81
  else => unreachable,
78
82
  };
79
83
  }
@@ -84,25 +88,36 @@ pub const StateMachine = struct {
84
88
  .create_transfers => CreateTransfersResult,
85
89
  .commit_transfers => CommitTransfersResult,
86
90
  .lookup_accounts => Account,
91
+ .lookup_transfers => Transfer,
87
92
  else => unreachable,
88
93
  };
89
94
  }
90
95
 
91
- pub fn prepare(self: *StateMachine, operation: Operation, input: []u8) void {
96
+ pub fn prepare(self: *StateMachine, realtime: i64, operation: Operation, input: []u8) void {
92
97
  switch (operation) {
93
- .create_accounts => self.prepare_timestamps(.create_accounts, input),
94
- .create_transfers => self.prepare_timestamps(.create_transfers, input),
95
- .commit_transfers => self.prepare_timestamps(.commit_transfers, input),
98
+ .init => unreachable,
99
+ .register => {},
100
+ .create_accounts => self.prepare_timestamps(realtime, .create_accounts, input),
101
+ .create_transfers => self.prepare_timestamps(realtime, .create_transfers, input),
102
+ .commit_transfers => self.prepare_timestamps(realtime, .commit_transfers, input),
96
103
  .lookup_accounts => {},
104
+ .lookup_transfers => {},
97
105
  else => unreachable,
98
106
  }
99
107
  }
100
108
 
101
- fn prepare_timestamps(self: *StateMachine, comptime operation: Operation, input: []u8) void {
109
+ fn prepare_timestamps(
110
+ self: *StateMachine,
111
+ realtime: i64,
112
+ comptime operation: Operation,
113
+ input: []u8,
114
+ ) void {
102
115
  // Guard against the wall clock going backwards by taking the max with timestamps issued:
103
116
  self.prepare_timestamp = std.math.max(
104
- self.prepare_timestamp,
105
- @intCast(u64, std.time.nanoTimestamp()),
117
+ // The cluster `commit_timestamp` may be ahead of our `prepare_timestamp` because this
118
+ // may be our first prepare as a recently elected leader:
119
+ std.math.max(self.prepare_timestamp, self.commit_timestamp) + 1,
120
+ @intCast(u64, realtime),
106
121
  );
107
122
  assert(self.prepare_timestamp > self.commit_timestamp);
108
123
  var sum_reserved_timestamps: usize = 0;
@@ -120,16 +135,19 @@ pub const StateMachine = struct {
120
135
 
121
136
  pub fn commit(
122
137
  self: *StateMachine,
138
+ client: u128,
123
139
  operation: Operation,
124
140
  input: []const u8,
125
141
  output: []u8,
126
142
  ) usize {
127
143
  return switch (operation) {
128
- .init => 0,
144
+ .init => unreachable,
145
+ .register => 0,
129
146
  .create_accounts => self.execute(.create_accounts, input, output),
130
147
  .create_transfers => self.execute(.create_transfers, input, output),
131
148
  .commit_transfers => self.execute(.commit_transfers, input, output),
132
149
  .lookup_accounts => self.execute_lookup_accounts(input, output),
150
+ .lookup_transfers => self.execute_lookup_transfers(input, output),
133
151
  else => unreachable,
134
152
  };
135
153
  }
@@ -140,7 +158,7 @@ pub const StateMachine = struct {
140
158
  input: []const u8,
141
159
  output: []u8,
142
160
  ) usize {
143
- comptime assert(operation != .lookup_accounts);
161
+ comptime assert(operation != .lookup_accounts and operation != .lookup_transfers);
144
162
 
145
163
  const events = std.mem.bytesAsSlice(Event(operation), input);
146
164
  var results = std.mem.bytesAsSlice(Result(operation), output);
@@ -242,8 +260,8 @@ pub const StateMachine = struct {
242
260
 
243
261
  fn execute_lookup_accounts(self: *StateMachine, input: []const u8, output: []u8) usize {
244
262
  const batch = std.mem.bytesAsSlice(u128, input);
245
- var output_len = @divFloor(output.len, @sizeOf(Account)) * @sizeOf(Account);
246
- var results = std.mem.bytesAsSlice(Account, output[0..output_len]);
263
+ const output_len = @divFloor(output.len, @sizeOf(Account)) * @sizeOf(Account);
264
+ const results = std.mem.bytesAsSlice(Account, output[0..output_len]);
247
265
  var results_count: usize = 0;
248
266
  for (batch) |id, index| {
249
267
  if (self.get_account(id)) |result| {
@@ -254,6 +272,20 @@ pub const StateMachine = struct {
254
272
  return results_count * @sizeOf(Account);
255
273
  }
256
274
 
275
+ fn execute_lookup_transfers(self: *StateMachine, input: []const u8, output: []u8) usize {
276
+ const batch = std.mem.bytesAsSlice(u128, input);
277
+ const output_len = @divFloor(output.len, @sizeOf(Transfer)) * @sizeOf(Transfer);
278
+ const results = std.mem.bytesAsSlice(Transfer, output[0..output_len]);
279
+ var results_count: usize = 0;
280
+ for (batch) |id, index| {
281
+ if (self.get_transfer(id)) |result| {
282
+ results[results_count] = result.*;
283
+ results_count += 1;
284
+ }
285
+ }
286
+ return results_count * @sizeOf(Transfer);
287
+ }
288
+
257
289
  fn create_account(self: *StateMachine, a: Account) CreateAccountResult {
258
290
  assert(a.timestamp > self.commit_timestamp);
259
291
 
@@ -326,8 +358,7 @@ pub const StateMachine = struct {
326
358
  const exists = insert.value_ptr.*;
327
359
  if (exists.debit_account_id != t.debit_account_id) {
328
360
  return .exists_with_different_debit_account_id;
329
- }
330
- if (exists.credit_account_id != t.credit_account_id) {
361
+ } else if (exists.credit_account_id != t.credit_account_id) {
331
362
  return .exists_with_different_credit_account_id;
332
363
  }
333
364
  if (exists.amount != t.amount) return .exists_with_different_amount;
@@ -424,6 +455,7 @@ pub const StateMachine = struct {
424
455
  }
425
456
 
426
457
  fn commit_transfer_rollback(self: *StateMachine, c: Commit) void {
458
+ assert(self.get_commit(c.id) != null);
427
459
  var t = self.get_transfer(c.id).?;
428
460
  var dr = self.get_account(t.debit_account_id).?;
429
461
  var cr = self.get_account(t.credit_account_id).?;
@@ -503,6 +535,161 @@ fn equal_48_bytes(a: [48]u8, b: [48]u8) bool {
503
535
 
504
536
  const testing = std.testing;
505
537
 
538
+ test "create/lookup accounts" {
539
+ var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
540
+ defer arena.deinit();
541
+ const allocator = &arena.allocator;
542
+
543
+ const Vector = struct { result: CreateAccountResult, object: Account };
544
+
545
+ const vectors = [_]Vector{
546
+ Vector{
547
+ .result = .reserved_flag_padding,
548
+ .object = std.mem.zeroInit(Account, .{
549
+ .id = 1,
550
+ .timestamp = 1,
551
+ .flags = .{ .padding = 1 },
552
+ }),
553
+ },
554
+ Vector{
555
+ .result = .reserved_field,
556
+ .object = std.mem.zeroInit(Account, .{
557
+ .id = 2,
558
+ .timestamp = 1,
559
+ .reserved = [_]u8{1} ** 48,
560
+ }),
561
+ },
562
+ Vector{
563
+ .result = .exceeds_credits,
564
+ .object = std.mem.zeroInit(Account, .{
565
+ .id = 3,
566
+ .timestamp = 1,
567
+ .debits_reserved = 10,
568
+ .flags = .{ .debits_must_not_exceed_credits = true },
569
+ }),
570
+ },
571
+ Vector{
572
+ .result = .exceeds_credits,
573
+ .object = std.mem.zeroInit(Account, .{
574
+ .id = 4,
575
+ .timestamp = 1,
576
+ .debits_accepted = 10,
577
+ .flags = .{ .debits_must_not_exceed_credits = true },
578
+ }),
579
+ },
580
+ Vector{
581
+ .result = .exceeds_debits,
582
+ .object = std.mem.zeroInit(Account, .{
583
+ .id = 5,
584
+ .timestamp = 1,
585
+ .credits_reserved = 10,
586
+ .flags = .{ .credits_must_not_exceed_debits = true },
587
+ }),
588
+ },
589
+ Vector{
590
+ .result = .exceeds_debits,
591
+ .object = std.mem.zeroInit(Account, .{
592
+ .id = 6,
593
+ .timestamp = 1,
594
+ .credits_accepted = 10,
595
+ .flags = .{ .credits_must_not_exceed_debits = true },
596
+ }),
597
+ },
598
+ Vector{
599
+ .result = .ok,
600
+ .object = std.mem.zeroInit(Account, .{
601
+ .id = 7,
602
+ .timestamp = 1,
603
+ }),
604
+ },
605
+ Vector{
606
+ .result = .exists,
607
+ .object = std.mem.zeroInit(Account, .{
608
+ .id = 7,
609
+ .timestamp = 2,
610
+ }),
611
+ },
612
+ Vector{
613
+ .result = .ok,
614
+ .object = std.mem.zeroInit(Account, .{
615
+ .id = 8,
616
+ .timestamp = 2,
617
+ .user_data = 'U',
618
+ .unit = 9,
619
+ }),
620
+ },
621
+ Vector{
622
+ .result = .exists_with_different_unit,
623
+ .object = std.mem.zeroInit(Account, .{
624
+ .id = 8,
625
+ .timestamp = 3,
626
+ .user_data = 'U',
627
+ .unit = 10,
628
+ }),
629
+ },
630
+ Vector{
631
+ .result = .ok,
632
+ .object = std.mem.zeroInit(Account, .{
633
+ .id = 9,
634
+ .timestamp = 3,
635
+ .code = 9,
636
+ .user_data = 'U',
637
+ }),
638
+ },
639
+ Vector{
640
+ .result = .exists_with_different_code,
641
+ .object = std.mem.zeroInit(Account, .{
642
+ .id = 9,
643
+ .timestamp = 4,
644
+ .code = 10,
645
+ .user_data = 'D',
646
+ }),
647
+ },
648
+ Vector{
649
+ .result = .ok,
650
+ .object = std.mem.zeroInit(Account, .{
651
+ .id = 10,
652
+ .timestamp = 4,
653
+ .flags = .{ .credits_must_not_exceed_debits = true },
654
+ }),
655
+ },
656
+ Vector{
657
+ .result = .exists_with_different_flags,
658
+ .object = std.mem.zeroInit(Account, .{
659
+ .id = 10,
660
+ .timestamp = 5,
661
+ .flags = .{ .debits_must_not_exceed_credits = true },
662
+ }),
663
+ },
664
+ Vector{
665
+ .result = .ok,
666
+ .object = std.mem.zeroInit(Account, .{
667
+ .id = 11,
668
+ .timestamp = 5,
669
+ .user_data = 'U',
670
+ }),
671
+ },
672
+ Vector{
673
+ .result = .exists_with_different_user_data,
674
+ .object = std.mem.zeroInit(Account, .{
675
+ .id = 11,
676
+ .timestamp = 6,
677
+ .user_data = 'D',
678
+ }),
679
+ },
680
+ };
681
+
682
+ var state_machine = try StateMachine.init(allocator, vectors.len, 0, 0);
683
+ defer state_machine.deinit();
684
+
685
+ for (vectors) |vector| {
686
+ try testing.expectEqual(vector.result, state_machine.create_account(vector.object));
687
+ if (vector.result == .ok) {
688
+ try testing.expectEqual(vector.object, state_machine.get_account(vector.object.id).?.*);
689
+ }
690
+ }
691
+ }
692
+
506
693
  test "linked accounts" {
507
694
  var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
508
695
  defer arena.deinit();
@@ -512,21 +699,22 @@ test "linked accounts" {
512
699
  const transfers_max = 0;
513
700
  const commits_max = 0;
514
701
 
515
- var state_machine = try StateMachine.init(allocator, accounts_max, transfers_max, commits_max);
516
- defer state_machine.deinit();
517
-
518
702
  var accounts = [_]Account{
519
703
  // An individual event (successful):
520
704
  std.mem.zeroInit(Account, .{ .id = 7, .code = 200 }),
521
705
 
522
706
  // A chain of 4 events (the last event in the chain closes the chain with linked=false):
523
- std.mem.zeroInit(Account, .{ .id = 0, .flags = .{ .linked = true } }), // Commit/rollback.
524
- std.mem.zeroInit(Account, .{ .id = 1, .flags = .{ .linked = true } }), // Commit/rollback.
525
- std.mem.zeroInit(Account, .{ .id = 0, .flags = .{ .linked = true } }), // Fail with .exists.
526
- std.mem.zeroInit(Account, .{ .id = 2 }), // Fail without committing.
707
+ // Commit/rollback:
708
+ std.mem.zeroInit(Account, .{ .id = 0, .flags = .{ .linked = true } }),
709
+ // Commit/rollback:
710
+ std.mem.zeroInit(Account, .{ .id = 1, .flags = .{ .linked = true } }),
711
+ // Fail with .exists:
712
+ std.mem.zeroInit(Account, .{ .id = 0, .flags = .{ .linked = true } }),
713
+ // Fail without committing.
714
+ std.mem.zeroInit(Account, .{ .id = 2 }),
527
715
 
528
716
  // An individual event (successful):
529
- // This should not see any effect from the failed chain above.
717
+ // This should not see any effect from the failed chain above:
530
718
  std.mem.zeroInit(Account, .{ .id = 0, .code = 200 }),
531
719
 
532
720
  // A chain of 2 events (the first event fails the chain):
@@ -545,11 +733,14 @@ test "linked accounts" {
545
733
  std.mem.zeroInit(Account, .{ .id = 3 }),
546
734
  };
547
735
 
736
+ var state_machine = try StateMachine.init(allocator, accounts.len, transfers_max, commits_max);
737
+ defer state_machine.deinit();
738
+
548
739
  const input = std.mem.asBytes(&accounts);
549
740
  const output = try allocator.alloc(u8, 4096);
550
741
 
551
- state_machine.prepare(.create_accounts, input);
552
- const size = state_machine.commit(.create_accounts, input, output);
742
+ state_machine.prepare(0, .create_accounts, input);
743
+ const size = state_machine.commit(0, .create_accounts, input, output);
553
744
  const results = std.mem.bytesAsSlice(CreateAccountsResult, output[0..size]);
554
745
 
555
746
  try testing.expectEqualSlices(
@@ -580,6 +771,623 @@ test "linked accounts" {
580
771
  // All our rollback handlers appear to be commutative.
581
772
  }
582
773
 
774
+ test "create/lookup/rollback transfers" {
775
+ var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
776
+ defer arena.deinit();
777
+ const allocator = &arena.allocator;
778
+
779
+ var accounts = [_]Account{
780
+ std.mem.zeroInit(Account, .{ .id = 1 }),
781
+ std.mem.zeroInit(Account, .{ .id = 2 }),
782
+ std.mem.zeroInit(Account, .{ .id = 3, .unit = 1 }),
783
+ std.mem.zeroInit(Account, .{ .id = 4, .unit = 2 }),
784
+ std.mem.zeroInit(Account, .{ .id = 5, .flags = .{ .debits_must_not_exceed_credits = true } }),
785
+ std.mem.zeroInit(Account, .{ .id = 6, .flags = .{ .credits_must_not_exceed_debits = true } }),
786
+ std.mem.zeroInit(Account, .{ .id = 7 }),
787
+ std.mem.zeroInit(Account, .{ .id = 8 }),
788
+ };
789
+
790
+ var state_machine = try StateMachine.init(allocator, accounts.len, 1, 0);
791
+ defer state_machine.deinit();
792
+
793
+ const input = std.mem.asBytes(&accounts);
794
+ const output = try allocator.alloc(u8, 4096);
795
+
796
+ state_machine.prepare(0, .create_accounts, input);
797
+ const size = state_machine.commit(0, .create_accounts, input, output);
798
+ const results = std.mem.bytesAsSlice(CreateAccountsResult, output[0..size]);
799
+
800
+ for (accounts) |account, i| {
801
+ try testing.expectEqual(accounts[i], state_machine.get_account(accounts[i].id).?.*);
802
+ }
803
+
804
+ const Vector = struct { result: CreateTransferResult, object: Transfer };
805
+
806
+ const timestamp: u64 = (state_machine.commit_timestamp + 1);
807
+ const vectors = [_]Vector{
808
+ Vector{
809
+ .result = .amount_is_zero,
810
+ .object = std.mem.zeroInit(Transfer, .{
811
+ .id = 1,
812
+ .timestamp = timestamp,
813
+ }),
814
+ },
815
+ Vector{
816
+ .result = .reserved_flag_padding,
817
+ .object = std.mem.zeroInit(Transfer, .{
818
+ .id = 2,
819
+ .timestamp = timestamp,
820
+ .flags = .{ .padding = 1 },
821
+ }),
822
+ },
823
+ Vector{
824
+ .result = .two_phase_commit_must_timeout,
825
+ .object = std.mem.zeroInit(Transfer, .{
826
+ .id = 3,
827
+ .timestamp = timestamp,
828
+ .flags = .{ .two_phase_commit = true },
829
+ }),
830
+ },
831
+ Vector{
832
+ .result = .timeout_reserved_for_two_phase_commit,
833
+ .object = std.mem.zeroInit(Transfer, .{
834
+ .id = 4,
835
+ .timestamp = timestamp,
836
+ .timeout = 1,
837
+ }),
838
+ },
839
+ Vector{
840
+ .result = .reserved_field,
841
+ .object = std.mem.zeroInit(Transfer, .{
842
+ .id = 5,
843
+ .timestamp = timestamp,
844
+ .flags = .{ .condition = false },
845
+ .reserved = [_]u8{1} ** 32,
846
+ }),
847
+ },
848
+ Vector{
849
+ .result = .accounts_are_the_same,
850
+ .object = std.mem.zeroInit(Transfer, .{
851
+ .id = 6,
852
+ .timestamp = timestamp,
853
+ .amount = 10,
854
+ .debit_account_id = 1,
855
+ .credit_account_id = 1,
856
+ }),
857
+ },
858
+ Vector{
859
+ .result = .debit_account_not_found,
860
+ .object = std.mem.zeroInit(Transfer, .{
861
+ .id = 7,
862
+ .timestamp = timestamp,
863
+ .amount = 10,
864
+ .debit_account_id = 100,
865
+ .credit_account_id = 1,
866
+ }),
867
+ },
868
+ Vector{
869
+ .result = .credit_account_not_found,
870
+ .object = std.mem.zeroInit(Transfer, .{
871
+ .id = 8,
872
+ .timestamp = timestamp,
873
+ .amount = 10,
874
+ .debit_account_id = 1,
875
+ .credit_account_id = 100,
876
+ }),
877
+ },
878
+ Vector{
879
+ .result = .accounts_have_different_units,
880
+ .object = std.mem.zeroInit(Transfer, .{
881
+ .id = 9,
882
+ .timestamp = timestamp,
883
+ .amount = 10,
884
+ .debit_account_id = 3,
885
+ .credit_account_id = 4,
886
+ }),
887
+ },
888
+ Vector{
889
+ .result = .exceeds_credits,
890
+ .object = std.mem.zeroInit(Transfer, .{
891
+ .id = 10,
892
+ .timestamp = timestamp,
893
+ .amount = 1000,
894
+ .debit_account_id = 5,
895
+ .credit_account_id = 1,
896
+ }),
897
+ },
898
+ Vector{
899
+ .result = .exceeds_debits,
900
+ .object = std.mem.zeroInit(Transfer, .{
901
+ .id = 11,
902
+ .timestamp = timestamp,
903
+ .amount = 1000,
904
+ .debit_account_id = 1,
905
+ .credit_account_id = 6,
906
+ }),
907
+ },
908
+ Vector{
909
+ .result = .ok,
910
+ .object = std.mem.zeroInit(Transfer, .{
911
+ .id = 12,
912
+ .timestamp = timestamp,
913
+ .amount = 10,
914
+ .debit_account_id = 7,
915
+ .credit_account_id = 8,
916
+ }),
917
+ },
918
+ Vector{
919
+ .result = .exists,
920
+ .object = std.mem.zeroInit(Transfer, .{
921
+ .id = 12,
922
+ .timestamp = timestamp + 1,
923
+ .amount = 10,
924
+ .debit_account_id = 7,
925
+ .credit_account_id = 8,
926
+ }),
927
+ },
928
+ Vector{
929
+ .result = .exists_with_different_debit_account_id,
930
+ .object = std.mem.zeroInit(Transfer, .{
931
+ .id = 12,
932
+ .timestamp = timestamp + 1,
933
+ .amount = 10,
934
+ .debit_account_id = 8,
935
+ .credit_account_id = 7,
936
+ }),
937
+ },
938
+ Vector{
939
+ .result = .exists_with_different_credit_account_id,
940
+ .object = std.mem.zeroInit(Transfer, .{
941
+ .id = 12,
942
+ .timestamp = timestamp + 1,
943
+ .amount = 10,
944
+ .debit_account_id = 7,
945
+ .credit_account_id = 1,
946
+ }),
947
+ },
948
+ Vector{
949
+ .result = .exists_with_different_amount,
950
+ .object = std.mem.zeroInit(Transfer, .{
951
+ .id = 12,
952
+ .timestamp = timestamp + 1,
953
+ .amount = 11,
954
+ .debit_account_id = 7,
955
+ .credit_account_id = 8,
956
+ }),
957
+ },
958
+ Vector{
959
+ .result = .exists_with_different_flags,
960
+ .object = std.mem.zeroInit(Transfer, .{
961
+ .id = 12,
962
+ .timestamp = timestamp + 1,
963
+ .amount = 10,
964
+ .debit_account_id = 7,
965
+ .credit_account_id = 8,
966
+ .flags = .{ .condition = true },
967
+ }),
968
+ },
969
+ Vector{
970
+ .result = .exists_with_different_user_data,
971
+ .object = std.mem.zeroInit(Transfer, .{
972
+ .id = 12,
973
+ .timestamp = timestamp + 1,
974
+ .amount = 10,
975
+ .debit_account_id = 7,
976
+ .credit_account_id = 8,
977
+ .user_data = 'A',
978
+ }),
979
+ },
980
+ Vector{
981
+ .result = .ok,
982
+ .object = std.mem.zeroInit(Transfer, .{
983
+ .id = 13,
984
+ .timestamp = timestamp + 1,
985
+ .amount = 10,
986
+ .debit_account_id = 7,
987
+ .credit_account_id = 8,
988
+ .flags = .{ .condition = true },
989
+ .reserved = [_]u8{1} ** 32,
990
+ }),
991
+ },
992
+ Vector{
993
+ .result = .exists_with_different_reserved_field,
994
+ .object = std.mem.zeroInit(Transfer, .{
995
+ .id = 13,
996
+ .timestamp = timestamp + 2,
997
+ .amount = 10,
998
+ .debit_account_id = 7,
999
+ .credit_account_id = 8,
1000
+ .flags = .{ .condition = true },
1001
+ .reserved = [_]u8{2} ** 32,
1002
+ }),
1003
+ },
1004
+ Vector{
1005
+ .result = .timeout_reserved_for_two_phase_commit,
1006
+ .object = std.mem.zeroInit(Transfer, .{
1007
+ .id = 13,
1008
+ .timestamp = timestamp + 2,
1009
+ .amount = 10,
1010
+ .debit_account_id = 7,
1011
+ .credit_account_id = 8,
1012
+ .flags = .{ .condition = true },
1013
+ .reserved = [_]u8{1} ** 32,
1014
+ .timeout = 10,
1015
+ }),
1016
+ },
1017
+ Vector{
1018
+ .result = .two_phase_commit_must_timeout,
1019
+ .object = std.mem.zeroInit(Transfer, .{
1020
+ .id = 14,
1021
+ .timestamp = timestamp + 2,
1022
+ .amount = 10,
1023
+ .debit_account_id = 7,
1024
+ .credit_account_id = 8,
1025
+ .flags = .{ .two_phase_commit = true },
1026
+ .timeout = 0,
1027
+ }),
1028
+ },
1029
+ Vector{
1030
+ .result = .ok,
1031
+ .object = std.mem.zeroInit(Transfer, .{
1032
+ .id = 15,
1033
+ .timestamp = timestamp + 2,
1034
+ .amount = 10,
1035
+ .debit_account_id = 7,
1036
+ .credit_account_id = 8,
1037
+ .flags = .{ .two_phase_commit = true },
1038
+ .timeout = 20,
1039
+ }),
1040
+ },
1041
+ Vector{
1042
+ .result = .exists_with_different_timeout,
1043
+ .object = std.mem.zeroInit(Transfer, .{
1044
+ .id = 15,
1045
+ .timestamp = timestamp + 3,
1046
+ .amount = 10,
1047
+ .debit_account_id = 7,
1048
+ .credit_account_id = 8,
1049
+ .flags = .{ .two_phase_commit = true },
1050
+ .timeout = 25,
1051
+ }),
1052
+ },
1053
+ };
1054
+
1055
+ for (vectors) |vector| {
1056
+ try testing.expectEqual(vector.result, state_machine.create_transfer(vector.object));
1057
+ if (vector.result == .ok) {
1058
+ try testing.expectEqual(vector.object, state_machine.get_transfer(vector.object.id).?.*);
1059
+ }
1060
+ }
1061
+
1062
+ // 2 phase commit [reserved]:
1063
+ try testing.expectEqual(@as(u64, 10), state_machine.get_account(7).?.*.debits_reserved);
1064
+ try testing.expectEqual(@as(u64, 0), state_machine.get_account(7).?.*.credits_reserved);
1065
+ try testing.expectEqual(@as(u64, 10), state_machine.get_account(8).?.*.credits_reserved);
1066
+ try testing.expectEqual(@as(u64, 0), state_machine.get_account(8).?.*.debits_reserved);
1067
+ // 1 phase commit [accepted]:
1068
+ try testing.expectEqual(@as(u64, 20), state_machine.get_account(7).?.*.debits_accepted);
1069
+ try testing.expectEqual(@as(u64, 0), state_machine.get_account(7).?.*.credits_accepted);
1070
+ try testing.expectEqual(@as(u64, 20), state_machine.get_account(8).?.*.credits_accepted);
1071
+ try testing.expectEqual(@as(u64, 0), state_machine.get_account(8).?.*.debits_accepted);
1072
+
1073
+ // Rollback transfer with id [12], amount of 10:
1074
+ state_machine.create_transfer_rollback(state_machine.get_transfer(vectors[11].object.id).?.*);
1075
+ try testing.expectEqual(@as(u64, 10), state_machine.get_account(7).?.*.debits_accepted);
1076
+ try testing.expectEqual(@as(u64, 0), state_machine.get_account(7).?.*.credits_accepted);
1077
+ try testing.expectEqual(@as(u64, 10), state_machine.get_account(8).?.*.credits_accepted);
1078
+ try testing.expectEqual(@as(u64, 0), state_machine.get_account(8).?.*.debits_accepted);
1079
+ try testing.expect(state_machine.get_transfer(vectors[11].object.id) == null);
1080
+
1081
+ // Rollback transfer with id [15], amount of 10:
1082
+ state_machine.create_transfer_rollback(state_machine.get_transfer(vectors[22].object.id).?.*);
1083
+ try testing.expectEqual(@as(u64, 0), state_machine.get_account(7).?.*.debits_reserved);
1084
+ try testing.expectEqual(@as(u64, 0), state_machine.get_account(8).?.*.credits_reserved);
1085
+ try testing.expect(state_machine.get_transfer(vectors[22].object.id) == null);
1086
+ }
1087
+
1088
+ test "create/lookup/rollback commits" {
1089
+ var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
1090
+ defer arena.deinit();
1091
+ const allocator = &arena.allocator;
1092
+
1093
+ const Vector = struct { result: CommitTransferResult, object: Commit };
1094
+
1095
+ var accounts = [_]Account{
1096
+ std.mem.zeroInit(Account, .{ .id = 1 }),
1097
+ std.mem.zeroInit(Account, .{ .id = 2 }),
1098
+ std.mem.zeroInit(Account, .{ .id = 3 }),
1099
+ std.mem.zeroInit(Account, .{ .id = 4 }),
1100
+ };
1101
+
1102
+ var transfers = [_]Transfer{
1103
+ std.mem.zeroInit(Transfer, .{
1104
+ .id = 1,
1105
+ .amount = 15,
1106
+ .debit_account_id = 1,
1107
+ .credit_account_id = 2,
1108
+ }),
1109
+ std.mem.zeroInit(Transfer, .{
1110
+ .id = 2,
1111
+ .amount = 15,
1112
+ .debit_account_id = 1,
1113
+ .credit_account_id = 2,
1114
+ .flags = .{ .two_phase_commit = true },
1115
+ .timeout = 25,
1116
+ }),
1117
+ std.mem.zeroInit(Transfer, .{
1118
+ .id = 3,
1119
+ .amount = 15,
1120
+ .debit_account_id = 1,
1121
+ .credit_account_id = 2,
1122
+ .flags = .{ .two_phase_commit = true },
1123
+ .timeout = 25,
1124
+ }),
1125
+ std.mem.zeroInit(Transfer, .{
1126
+ .id = 4,
1127
+ .amount = 15,
1128
+ .debit_account_id = 1,
1129
+ .credit_account_id = 2,
1130
+ .flags = .{ .two_phase_commit = true },
1131
+ .timeout = 1,
1132
+ }),
1133
+ std.mem.zeroInit(Transfer, .{
1134
+ .id = 5,
1135
+ .amount = 15,
1136
+ .debit_account_id = 1,
1137
+ .credit_account_id = 2,
1138
+ .flags = .{
1139
+ .two_phase_commit = true,
1140
+ .condition = true,
1141
+ },
1142
+ .timeout = 25,
1143
+ }),
1144
+ std.mem.zeroInit(Transfer, .{
1145
+ .id = 6,
1146
+ .amount = 15,
1147
+ .debit_account_id = 1,
1148
+ .credit_account_id = 2,
1149
+ .flags = .{
1150
+ .two_phase_commit = true,
1151
+ .condition = false,
1152
+ },
1153
+ .timeout = 25,
1154
+ }),
1155
+ std.mem.zeroInit(Transfer, .{
1156
+ .id = 7,
1157
+ .amount = 15,
1158
+ .debit_account_id = 3,
1159
+ .credit_account_id = 4,
1160
+ .flags = .{ .two_phase_commit = true },
1161
+ .timeout = 25,
1162
+ }),
1163
+ };
1164
+
1165
+ var state_machine = try StateMachine.init(allocator, accounts.len, transfers.len, 1);
1166
+ defer state_machine.deinit();
1167
+
1168
+ const input = std.mem.asBytes(&accounts);
1169
+ const output = try allocator.alloc(u8, 4096);
1170
+
1171
+ // Accounts:
1172
+ state_machine.prepare(0, .create_accounts, input);
1173
+ const size = state_machine.commit(0, .create_accounts, input, output);
1174
+ const results = std.mem.bytesAsSlice(CreateAccountsResult, output[0..size]);
1175
+
1176
+ for (accounts) |account, i| {
1177
+ try testing.expectEqual(accounts[i], state_machine.get_account(accounts[i].id).?.*);
1178
+ }
1179
+
1180
+ // Transfers:
1181
+ const object_transfers = std.mem.asBytes(&transfers);
1182
+ const output_transfers = try allocator.alloc(u8, 4096);
1183
+
1184
+ state_machine.prepare(0, .create_transfers, object_transfers);
1185
+ const size_transfers = state_machine.commit(0, .create_transfers, object_transfers, output_transfers);
1186
+ const results_transfers = std.mem.bytesAsSlice(CreateTransfersResult, output_transfers[0..size_transfers]);
1187
+
1188
+ for (transfers) |transfer, i| {
1189
+ try testing.expectEqual(transfers[i], state_machine.get_transfer(transfers[i].id).?.*);
1190
+ }
1191
+
1192
+ // Commits:
1193
+ const timestamp: u64 = (state_machine.commit_timestamp + 1);
1194
+ const vectors = [_]Vector{
1195
+ Vector{
1196
+ .result = .reserved_field,
1197
+ .object = std.mem.zeroInit(Commit, .{
1198
+ .id = 1,
1199
+ .timestamp = timestamp,
1200
+ .reserved = [_]u8{1} ** 32,
1201
+ }),
1202
+ },
1203
+ Vector{
1204
+ .result = .reserved_flag_padding,
1205
+ .object = std.mem.zeroInit(Commit, .{
1206
+ .id = 1,
1207
+ .timestamp = timestamp,
1208
+ .flags = .{ .padding = 1 },
1209
+ }),
1210
+ },
1211
+ Vector{
1212
+ .result = .transfer_not_found,
1213
+ .object = std.mem.zeroInit(Commit, .{
1214
+ .id = 777,
1215
+ .timestamp = timestamp,
1216
+ }),
1217
+ },
1218
+ Vector{
1219
+ .result = .transfer_not_two_phase_commit,
1220
+ .object = std.mem.zeroInit(Commit, .{
1221
+ .id = 1,
1222
+ .timestamp = timestamp,
1223
+ }),
1224
+ },
1225
+ Vector{
1226
+ .result = .ok,
1227
+ .object = std.mem.zeroInit(Commit, .{
1228
+ .id = 2,
1229
+ .timestamp = timestamp,
1230
+ }),
1231
+ },
1232
+ Vector{
1233
+ .result = .already_committed_but_accepted,
1234
+ .object = std.mem.zeroInit(Commit, .{
1235
+ .id = 2,
1236
+ .timestamp = timestamp + 1,
1237
+ .flags = .{ .reject = true },
1238
+ }),
1239
+ },
1240
+ Vector{
1241
+ .result = .already_committed,
1242
+ .object = std.mem.zeroInit(Commit, .{
1243
+ .id = 2,
1244
+ .timestamp = timestamp + 1,
1245
+ }),
1246
+ },
1247
+ Vector{
1248
+ .result = .ok,
1249
+ .object = std.mem.zeroInit(Commit, .{
1250
+ .id = 3,
1251
+ .timestamp = timestamp + 1,
1252
+ .flags = .{ .reject = true },
1253
+ }),
1254
+ },
1255
+ Vector{
1256
+ .result = .already_committed_but_rejected,
1257
+ .object = std.mem.zeroInit(Commit, .{
1258
+ .id = 3,
1259
+ .timestamp = timestamp + 2,
1260
+ }),
1261
+ },
1262
+ Vector{
1263
+ .result = .transfer_expired,
1264
+ .object = std.mem.zeroInit(Commit, .{
1265
+ .id = 4,
1266
+ .timestamp = timestamp + 2,
1267
+ }),
1268
+ },
1269
+ Vector{
1270
+ .result = .condition_requires_preimage,
1271
+ .object = std.mem.zeroInit(Commit, .{
1272
+ .id = 5,
1273
+ .timestamp = timestamp + 2,
1274
+ }),
1275
+ },
1276
+ Vector{
1277
+ .result = .preimage_invalid,
1278
+ .object = std.mem.zeroInit(Commit, .{
1279
+ .id = 5,
1280
+ .timestamp = timestamp + 2,
1281
+ .flags = .{ .preimage = true },
1282
+ .reserved = [_]u8{1} ** 32,
1283
+ }),
1284
+ },
1285
+ Vector{
1286
+ .result = .preimage_requires_condition,
1287
+ .object = std.mem.zeroInit(Commit, .{
1288
+ .id = 6,
1289
+ .timestamp = timestamp + 2,
1290
+ .flags = .{ .preimage = true },
1291
+ }),
1292
+ },
1293
+ };
1294
+
1295
+ // Test balances BEFORE commit
1296
+ // Account 1:
1297
+ const account_1_before = state_machine.get_account(1).?.*;
1298
+ try testing.expectEqual(@as(u64, 15), account_1_before.debits_accepted);
1299
+ try testing.expectEqual(@as(u64, 75), account_1_before.debits_reserved);
1300
+ try testing.expectEqual(@as(u64, 0), account_1_before.credits_accepted);
1301
+ try testing.expectEqual(@as(u64, 0), account_1_before.credits_reserved);
1302
+ // Account 2:
1303
+ const account_2_before = state_machine.get_account(2).?.*;
1304
+ try testing.expectEqual(@as(u64, 0), account_2_before.debits_accepted);
1305
+ try testing.expectEqual(@as(u64, 0), account_2_before.debits_reserved);
1306
+ try testing.expectEqual(@as(u64, 15), account_2_before.credits_accepted);
1307
+ try testing.expectEqual(@as(u64, 75), account_2_before.credits_reserved);
1308
+
1309
+ for (vectors) |vector| {
1310
+ try testing.expectEqual(vector.result, state_machine.commit_transfer(vector.object));
1311
+ if (vector.result == .ok) {
1312
+ try testing.expectEqual(vector.object, state_machine.get_commit(vector.object.id).?.*);
1313
+ }
1314
+ }
1315
+
1316
+ // Test balances AFTER commit
1317
+ // Account 1:
1318
+ const account_1_after = state_machine.get_account(1).?.*;
1319
+ try testing.expectEqual(@as(u64, 30), account_1_after.debits_accepted);
1320
+ // +15 (acceptance applied):
1321
+ try testing.expectEqual(@as(u64, 45), account_1_after.debits_reserved);
1322
+ // -15 (reserved moved):
1323
+ try testing.expectEqual(@as(u64, 0), account_1_after.credits_accepted);
1324
+ try testing.expectEqual(@as(u64, 0), account_1_after.credits_reserved);
1325
+ // Account 2:
1326
+ const account_2_after = state_machine.get_account(2).?.*;
1327
+ try testing.expectEqual(@as(u64, 0), account_2_after.debits_accepted);
1328
+ try testing.expectEqual(@as(u64, 0), account_2_after.debits_reserved);
1329
+ // +15 (acceptance applied):
1330
+ try testing.expectEqual(@as(u64, 30), account_2_after.credits_accepted);
1331
+ // -15 (reserved moved):
1332
+ try testing.expectEqual(@as(u64, 45), account_2_after.credits_reserved);
1333
+
1334
+ // Test COMMIT with invalid debit/credit accounts
1335
+ state_machine.create_account_rollback(accounts[3]);
1336
+ try testing.expect(state_machine.get_account(accounts[3].id) == null);
1337
+ try testing.expectEqual(
1338
+ state_machine.commit_transfer(std.mem.zeroInit(Commit, .{
1339
+ .id = 7,
1340
+ .timestamp = timestamp + 2,
1341
+ })),
1342
+ .credit_account_not_found,
1343
+ );
1344
+ state_machine.create_account_rollback(accounts[2]);
1345
+ try testing.expect(state_machine.get_account(accounts[2].id) == null);
1346
+ try testing.expectEqual(
1347
+ state_machine.commit_transfer(std.mem.zeroInit(Commit, .{
1348
+ .id = 7,
1349
+ .timestamp = timestamp + 2,
1350
+ })),
1351
+ .debit_account_not_found,
1352
+ );
1353
+
1354
+ // Rollback [id=2] not rejected:
1355
+ state_machine.commit_transfer_rollback(vectors[4].object);
1356
+
1357
+ // Account 1:
1358
+ const account_1_rollback = state_machine.get_account(1).?.*;
1359
+ // -15 (rollback):
1360
+ try testing.expectEqual(@as(u64, 15), account_1_rollback.debits_accepted);
1361
+ try testing.expectEqual(@as(u64, 60), account_1_rollback.debits_reserved);
1362
+ try testing.expectEqual(@as(u64, 0), account_1_rollback.credits_accepted);
1363
+ try testing.expectEqual(@as(u64, 0), account_1_rollback.credits_reserved);
1364
+ // Account 2:
1365
+ const account_2_rollback = state_machine.get_account(2).?.*;
1366
+ try testing.expectEqual(@as(u64, 0), account_2_rollback.debits_accepted);
1367
+ try testing.expectEqual(@as(u64, 0), account_2_rollback.debits_reserved);
1368
+ // -15 (rollback):
1369
+ try testing.expectEqual(@as(u64, 15), account_2_rollback.credits_accepted);
1370
+ try testing.expectEqual(@as(u64, 60), account_2_rollback.credits_reserved);
1371
+
1372
+ // Rollback [id=3] rejected:
1373
+ state_machine.commit_transfer_rollback(vectors[7].object);
1374
+ // Account 1:
1375
+ const account_1_rollback_reject = state_machine.get_account(1).?.*;
1376
+ try testing.expectEqual(@as(u64, 15), account_1_rollback_reject.debits_accepted);
1377
+ // Remains unchanged:
1378
+ try testing.expectEqual(@as(u64, 75), account_1_rollback_reject.debits_reserved);
1379
+ // +15 rolled back:
1380
+ try testing.expectEqual(@as(u64, 0), account_1_rollback_reject.credits_accepted);
1381
+ try testing.expectEqual(@as(u64, 0), account_1_rollback_reject.credits_reserved);
1382
+ // Account 2:
1383
+ const account_2_rollback_reject = state_machine.get_account(2).?.*;
1384
+ try testing.expectEqual(@as(u64, 0), account_2_rollback_reject.debits_accepted);
1385
+ try testing.expectEqual(@as(u64, 0), account_2_rollback_reject.debits_reserved);
1386
+ try testing.expectEqual(@as(u64, 15), account_2_rollback_reject.credits_accepted);
1387
+ // +15 rolled back"
1388
+ try testing.expectEqual(@as(u64, 75), account_2_rollback_reject.credits_reserved);
1389
+ }
1390
+
583
1391
  fn test_routine_zeroed(comptime len: usize) !void {
584
1392
  const routine = switch (len) {
585
1393
  32 => zeroed_32_bytes,