tigerbeetle-node 0.4.2 → 0.5.2

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