tigerbeetle-node 0.4.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.
@@ -20,6 +20,7 @@ pub const StateMachine = struct {
20
20
  create_transfers,
21
21
  commit_transfers,
22
22
  lookup_accounts,
23
+ lookup_transfers,
23
24
 
24
25
  pub fn jsonStringify(self: Command, options: StringifyOptions, writer: anytype) !void {
25
26
  try std.fmt.format(writer, "\"{}\"", .{@tagName(self)});
@@ -76,6 +77,7 @@ pub const StateMachine = struct {
76
77
  .create_transfers => Transfer,
77
78
  .commit_transfers => Commit,
78
79
  .lookup_accounts => u128,
80
+ .lookup_transfers => u128,
79
81
  else => unreachable,
80
82
  };
81
83
  }
@@ -86,6 +88,7 @@ pub const StateMachine = struct {
86
88
  .create_transfers => CreateTransfersResult,
87
89
  .commit_transfers => CommitTransfersResult,
88
90
  .lookup_accounts => Account,
91
+ .lookup_transfers => Transfer,
89
92
  else => unreachable,
90
93
  };
91
94
  }
@@ -98,6 +101,7 @@ pub const StateMachine = struct {
98
101
  .create_transfers => self.prepare_timestamps(realtime, .create_transfers, input),
99
102
  .commit_transfers => self.prepare_timestamps(realtime, .commit_transfers, input),
100
103
  .lookup_accounts => {},
104
+ .lookup_transfers => {},
101
105
  else => unreachable,
102
106
  }
103
107
  }
@@ -143,6 +147,7 @@ pub const StateMachine = struct {
143
147
  .create_transfers => self.execute(.create_transfers, input, output),
144
148
  .commit_transfers => self.execute(.commit_transfers, input, output),
145
149
  .lookup_accounts => self.execute_lookup_accounts(input, output),
150
+ .lookup_transfers => self.execute_lookup_transfers(input, output),
146
151
  else => unreachable,
147
152
  };
148
153
  }
@@ -153,7 +158,7 @@ pub const StateMachine = struct {
153
158
  input: []const u8,
154
159
  output: []u8,
155
160
  ) usize {
156
- comptime assert(operation != .lookup_accounts);
161
+ comptime assert(operation != .lookup_accounts and operation != .lookup_transfers);
157
162
 
158
163
  const events = std.mem.bytesAsSlice(Event(operation), input);
159
164
  var results = std.mem.bytesAsSlice(Result(operation), output);
@@ -255,8 +260,8 @@ pub const StateMachine = struct {
255
260
 
256
261
  fn execute_lookup_accounts(self: *StateMachine, input: []const u8, output: []u8) usize {
257
262
  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]);
263
+ const output_len = @divFloor(output.len, @sizeOf(Account)) * @sizeOf(Account);
264
+ const results = std.mem.bytesAsSlice(Account, output[0..output_len]);
260
265
  var results_count: usize = 0;
261
266
  for (batch) |id, index| {
262
267
  if (self.get_account(id)) |result| {
@@ -267,6 +272,20 @@ pub const StateMachine = struct {
267
272
  return results_count * @sizeOf(Account);
268
273
  }
269
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
+
270
289
  fn create_account(self: *StateMachine, a: Account) CreateAccountResult {
271
290
  assert(a.timestamp > self.commit_timestamp);
272
291
 
@@ -339,8 +358,7 @@ pub const StateMachine = struct {
339
358
  const exists = insert.value_ptr.*;
340
359
  if (exists.debit_account_id != t.debit_account_id) {
341
360
  return .exists_with_different_debit_account_id;
342
- }
343
- if (exists.credit_account_id != t.credit_account_id) {
361
+ } else if (exists.credit_account_id != t.credit_account_id) {
344
362
  return .exists_with_different_credit_account_id;
345
363
  }
346
364
  if (exists.amount != t.amount) return .exists_with_different_amount;
@@ -437,6 +455,7 @@ pub const StateMachine = struct {
437
455
  }
438
456
 
439
457
  fn commit_transfer_rollback(self: *StateMachine, c: Commit) void {
458
+ assert(self.get_commit(c.id) != null);
440
459
  var t = self.get_transfer(c.id).?;
441
460
  var dr = self.get_account(t.debit_account_id).?;
442
461
  var cr = self.get_account(t.credit_account_id).?;
@@ -516,6 +535,161 @@ fn equal_48_bytes(a: [48]u8, b: [48]u8) bool {
516
535
 
517
536
  const testing = std.testing;
518
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
+
519
693
  test "linked accounts" {
520
694
  var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
521
695
  defer arena.deinit();
@@ -525,21 +699,22 @@ test "linked accounts" {
525
699
  const transfers_max = 0;
526
700
  const commits_max = 0;
527
701
 
528
- var state_machine = try StateMachine.init(allocator, accounts_max, transfers_max, commits_max);
529
- defer state_machine.deinit();
530
-
531
702
  var accounts = [_]Account{
532
703
  // An individual event (successful):
533
704
  std.mem.zeroInit(Account, .{ .id = 7, .code = 200 }),
534
705
 
535
706
  // 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.
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 }),
540
715
 
541
716
  // An individual event (successful):
542
- // This should not see any effect from the failed chain above.
717
+ // This should not see any effect from the failed chain above:
543
718
  std.mem.zeroInit(Account, .{ .id = 0, .code = 200 }),
544
719
 
545
720
  // A chain of 2 events (the first event fails the chain):
@@ -558,10 +733,12 @@ test "linked accounts" {
558
733
  std.mem.zeroInit(Account, .{ .id = 3 }),
559
734
  };
560
735
 
736
+ var state_machine = try StateMachine.init(allocator, accounts.len, transfers_max, commits_max);
737
+ defer state_machine.deinit();
738
+
561
739
  const input = std.mem.asBytes(&accounts);
562
740
  const output = try allocator.alloc(u8, 4096);
563
741
 
564
- // Use a timestamp of 0 since this is just a test
565
742
  state_machine.prepare(0, .create_accounts, input);
566
743
  const size = state_machine.commit(0, .create_accounts, input, output);
567
744
  const results = std.mem.bytesAsSlice(CreateAccountsResult, output[0..size]);
@@ -594,6 +771,623 @@ test "linked accounts" {
594
771
  // All our rollback handlers appear to be commutative.
595
772
  }
596
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
+
597
1391
  fn test_routine_zeroed(comptime len: usize) !void {
598
1392
  const routine = switch (len) {
599
1393
  32 => zeroed_32_bytes,