tigerbeetle-node 0.9.0 → 0.9.143

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 (79) hide show
  1. package/README.md +580 -179
  2. package/dist/benchmark.js +44 -36
  3. package/dist/benchmark.js.map +1 -1
  4. package/dist/bin/aarch64-linux-gnu/client.node +0 -0
  5. package/dist/bin/aarch64-linux-musl/client.node +0 -0
  6. package/dist/bin/aarch64-macos/client.node +0 -0
  7. package/dist/bin/x86_64-linux-gnu/client.node +0 -0
  8. package/dist/bin/x86_64-linux-musl/client.node +0 -0
  9. package/dist/bin/x86_64-macos/client.node +0 -0
  10. package/dist/bin/x86_64-windows/client.node +0 -0
  11. package/dist/bindings.d.ts +141 -0
  12. package/dist/bindings.js +112 -0
  13. package/dist/bindings.js.map +1 -0
  14. package/dist/index.d.ts +2 -125
  15. package/dist/index.js +51 -101
  16. package/dist/index.js.map +1 -1
  17. package/dist/test.js +68 -54
  18. package/dist/test.js.map +1 -1
  19. package/package-lock.json +26 -0
  20. package/package.json +13 -22
  21. package/src/benchmark.ts +58 -49
  22. package/src/bindings.ts +631 -0
  23. package/src/index.ts +71 -163
  24. package/src/node.zig +169 -148
  25. package/src/test.ts +71 -57
  26. package/src/translate.zig +19 -36
  27. package/scripts/download_node_headers.sh +0 -25
  28. package/src/tigerbeetle/scripts/benchmark.bat +0 -46
  29. package/src/tigerbeetle/scripts/benchmark.sh +0 -55
  30. package/src/tigerbeetle/scripts/install.sh +0 -6
  31. package/src/tigerbeetle/scripts/install_zig.bat +0 -109
  32. package/src/tigerbeetle/scripts/install_zig.sh +0 -84
  33. package/src/tigerbeetle/scripts/lint.zig +0 -199
  34. package/src/tigerbeetle/scripts/upgrade_ubuntu_kernel.sh +0 -39
  35. package/src/tigerbeetle/scripts/vopr.bat +0 -48
  36. package/src/tigerbeetle/scripts/vopr.sh +0 -33
  37. package/src/tigerbeetle/scripts/vr_state_enumerate +0 -46
  38. package/src/tigerbeetle/src/benchmark.zig +0 -290
  39. package/src/tigerbeetle/src/cli.zig +0 -244
  40. package/src/tigerbeetle/src/config.zig +0 -239
  41. package/src/tigerbeetle/src/demo.zig +0 -125
  42. package/src/tigerbeetle/src/demo_01_create_accounts.zig +0 -35
  43. package/src/tigerbeetle/src/demo_02_lookup_accounts.zig +0 -7
  44. package/src/tigerbeetle/src/demo_03_create_transfers.zig +0 -24
  45. package/src/tigerbeetle/src/demo_04_create_pending_transfers.zig +0 -61
  46. package/src/tigerbeetle/src/demo_05_post_pending_transfers.zig +0 -37
  47. package/src/tigerbeetle/src/demo_06_void_pending_transfers.zig +0 -24
  48. package/src/tigerbeetle/src/demo_07_lookup_transfers.zig +0 -7
  49. package/src/tigerbeetle/src/fifo.zig +0 -104
  50. package/src/tigerbeetle/src/io/benchmark.zig +0 -213
  51. package/src/tigerbeetle/src/io/darwin.zig +0 -793
  52. package/src/tigerbeetle/src/io/linux.zig +0 -1038
  53. package/src/tigerbeetle/src/io/test.zig +0 -643
  54. package/src/tigerbeetle/src/io/windows.zig +0 -1161
  55. package/src/tigerbeetle/src/io.zig +0 -34
  56. package/src/tigerbeetle/src/main.zig +0 -144
  57. package/src/tigerbeetle/src/message_bus.zig +0 -1000
  58. package/src/tigerbeetle/src/message_pool.zig +0 -142
  59. package/src/tigerbeetle/src/ring_buffer.zig +0 -289
  60. package/src/tigerbeetle/src/simulator.zig +0 -417
  61. package/src/tigerbeetle/src/state_machine.zig +0 -2470
  62. package/src/tigerbeetle/src/storage.zig +0 -308
  63. package/src/tigerbeetle/src/test/cluster.zig +0 -351
  64. package/src/tigerbeetle/src/test/message_bus.zig +0 -93
  65. package/src/tigerbeetle/src/test/network.zig +0 -179
  66. package/src/tigerbeetle/src/test/packet_simulator.zig +0 -387
  67. package/src/tigerbeetle/src/test/state_checker.zig +0 -145
  68. package/src/tigerbeetle/src/test/state_machine.zig +0 -76
  69. package/src/tigerbeetle/src/test/storage.zig +0 -438
  70. package/src/tigerbeetle/src/test/time.zig +0 -84
  71. package/src/tigerbeetle/src/tigerbeetle.zig +0 -222
  72. package/src/tigerbeetle/src/time.zig +0 -113
  73. package/src/tigerbeetle/src/unit_tests.zig +0 -14
  74. package/src/tigerbeetle/src/vsr/client.zig +0 -505
  75. package/src/tigerbeetle/src/vsr/clock.zig +0 -812
  76. package/src/tigerbeetle/src/vsr/journal.zig +0 -2293
  77. package/src/tigerbeetle/src/vsr/marzullo.zig +0 -309
  78. package/src/tigerbeetle/src/vsr/replica.zig +0 -5015
  79. package/src/tigerbeetle/src/vsr.zig +0 -1017
@@ -1,2470 +0,0 @@
1
- const std = @import("std");
2
- const assert = std.debug.assert;
3
- const math = std.math;
4
- const mem = std.mem;
5
- const log = std.log.scoped(.state_machine);
6
-
7
- const tb = @import("tigerbeetle.zig");
8
-
9
- const Account = tb.Account;
10
- const AccountFlags = tb.AccountFlags;
11
-
12
- const Transfer = tb.Transfer;
13
- const TransferFlags = tb.TransferFlags;
14
-
15
- const CreateAccountsResult = tb.CreateAccountsResult;
16
- const CreateTransfersResult = tb.CreateTransfersResult;
17
-
18
- const CreateAccountResult = tb.CreateAccountResult;
19
- const CreateTransferResult = tb.CreateTransferResult;
20
- const LookupAccountResult = tb.LookupAccountResult;
21
-
22
- const HashMapAccounts = std.AutoHashMap(u128, Account);
23
- const HashMapTransfers = std.AutoHashMap(u128, Transfer);
24
- const HashMapPosted = std.AutoHashMap(u128, bool);
25
-
26
- pub const StateMachine = struct {
27
- pub const Operation = enum(u8) {
28
- /// Operations reserved by VR protocol (for all state machines):
29
- reserved,
30
- root,
31
- register,
32
-
33
- /// Operations exported by TigerBeetle:
34
- create_accounts,
35
- create_transfers,
36
- lookup_accounts,
37
- lookup_transfers,
38
- };
39
-
40
- allocator: mem.Allocator,
41
- prepare_timestamp: u64,
42
- commit_timestamp: u64,
43
- accounts: HashMapAccounts,
44
- transfers: HashMapTransfers,
45
- posted: HashMapPosted,
46
-
47
- pub fn init(
48
- allocator: mem.Allocator,
49
- accounts_max: usize,
50
- transfers_max: usize,
51
- transfers_pending_max: usize,
52
- ) !StateMachine {
53
- var accounts = HashMapAccounts.init(allocator);
54
- errdefer accounts.deinit();
55
- try accounts.ensureTotalCapacity(@intCast(u32, accounts_max));
56
-
57
- var transfers = HashMapTransfers.init(allocator);
58
- errdefer transfers.deinit();
59
- try transfers.ensureTotalCapacity(@intCast(u32, transfers_max));
60
-
61
- var posted = HashMapPosted.init(allocator);
62
- errdefer posted.deinit();
63
- try posted.ensureTotalCapacity(@intCast(u32, transfers_pending_max));
64
-
65
- return StateMachine{
66
- .allocator = allocator,
67
- .prepare_timestamp = 0,
68
- .commit_timestamp = 0,
69
- .accounts = accounts,
70
- .transfers = transfers,
71
- .posted = posted,
72
- };
73
- }
74
-
75
- pub fn deinit(self: *StateMachine) void {
76
- self.accounts.deinit();
77
- self.transfers.deinit();
78
- self.posted.deinit();
79
- }
80
-
81
- pub fn Event(comptime operation: Operation) type {
82
- return switch (operation) {
83
- .create_accounts => Account,
84
- .create_transfers => Transfer,
85
- .lookup_accounts => u128,
86
- .lookup_transfers => u128,
87
- else => unreachable,
88
- };
89
- }
90
-
91
- pub fn Result(comptime operation: Operation) type {
92
- return switch (operation) {
93
- .create_accounts => CreateAccountsResult,
94
- .create_transfers => CreateTransfersResult,
95
- .lookup_accounts => Account,
96
- .lookup_transfers => Transfer,
97
- else => unreachable,
98
- };
99
- }
100
-
101
- /// Returns the header's timestamp.
102
- pub fn prepare(self: *StateMachine, operation: Operation, input: []u8) u64 {
103
- switch (operation) {
104
- .root => unreachable,
105
- .register => {},
106
- .create_accounts => self.prepare_timestamps(.create_accounts, input),
107
- .create_transfers => self.prepare_timestamps(.create_transfers, input),
108
- .lookup_accounts => {},
109
- .lookup_transfers => {},
110
- else => unreachable,
111
- }
112
- return self.prepare_timestamp;
113
- }
114
-
115
- fn prepare_timestamps(
116
- self: *StateMachine,
117
- comptime operation: Operation,
118
- input: []u8,
119
- ) void {
120
- var sum_reserved_timestamps: usize = 0;
121
- var events = mem.bytesAsSlice(Event(operation), input);
122
- for (events) |*event| {
123
- sum_reserved_timestamps += event.timestamp;
124
- self.prepare_timestamp += 1;
125
- event.timestamp = self.prepare_timestamp;
126
- }
127
- // The client is responsible for ensuring that timestamps are reserved:
128
- // Use a single branch condition to detect non-zero reserved timestamps.
129
- // Summing then branching once is faster than branching every iteration of the loop.
130
- assert(sum_reserved_timestamps == 0);
131
- }
132
-
133
- pub fn commit(
134
- self: *StateMachine,
135
- client: u128,
136
- operation: Operation,
137
- input: []const u8,
138
- output: []u8,
139
- ) usize {
140
- _ = client;
141
- return switch (operation) {
142
- .root => unreachable,
143
- .register => 0,
144
- .create_accounts => self.execute(.create_accounts, input, output),
145
- .create_transfers => self.execute(.create_transfers, input, output),
146
- .lookup_accounts => self.execute_lookup_accounts(input, output),
147
- .lookup_transfers => self.execute_lookup_transfers(input, output),
148
- else => unreachable,
149
- };
150
- }
151
-
152
- fn execute(
153
- self: *StateMachine,
154
- comptime operation: Operation,
155
- input: []const u8,
156
- output: []u8,
157
- ) usize {
158
- comptime assert(operation != .lookup_accounts and operation != .lookup_transfers);
159
-
160
- const events = mem.bytesAsSlice(Event(operation), input);
161
- var results = mem.bytesAsSlice(Result(operation), output);
162
- var count: usize = 0;
163
-
164
- var chain: ?usize = null;
165
- var chain_broken = false;
166
-
167
- for (events) |*event, index| {
168
- if (event.flags.linked and chain == null) {
169
- chain = index;
170
- assert(chain_broken == false);
171
- }
172
- const result = if (chain_broken) .linked_event_failed else switch (operation) {
173
- .create_accounts => self.create_account(event),
174
- .create_transfers => self.create_transfer(event),
175
- else => unreachable,
176
- };
177
- log.debug("{s} {}/{}: {}: {}", .{
178
- @tagName(operation),
179
- index + 1,
180
- events.len,
181
- result,
182
- event,
183
- });
184
- if (result != .ok) {
185
- if (chain) |chain_start_index| {
186
- if (!chain_broken) {
187
- chain_broken = true;
188
- // Rollback events in LIFO order, excluding this event that broke the chain:
189
- self.rollback(operation, input, chain_start_index, index);
190
- // Add errors for rolled back events in FIFO order:
191
- var chain_index = chain_start_index;
192
- while (chain_index < index) : (chain_index += 1) {
193
- results[count] = .{
194
- .index = @intCast(u32, chain_index),
195
- .result = .linked_event_failed,
196
- };
197
- count += 1;
198
- }
199
- } else {
200
- assert(result == .linked_event_failed);
201
- }
202
- }
203
- results[count] = .{ .index = @intCast(u32, index), .result = result };
204
- count += 1;
205
- }
206
- if (!event.flags.linked and chain != null) {
207
- chain = null;
208
- chain_broken = false;
209
- }
210
- }
211
- // TODO client.zig: Validate that batch chains are always well-formed and closed.
212
- // This is programming error and we should raise an exception for this in the client ASAP.
213
- assert(chain == null);
214
- assert(chain_broken == false);
215
-
216
- return @sizeOf(Result(operation)) * count;
217
- }
218
-
219
- fn rollback(
220
- self: *StateMachine,
221
- comptime operation: Operation,
222
- input: []const u8,
223
- chain_start_index: usize,
224
- chain_error_index: usize,
225
- ) void {
226
- const events = mem.bytesAsSlice(Event(operation), input);
227
-
228
- // We commit events in FIFO order.
229
- // We must therefore rollback events in LIFO order with a reverse loop.
230
- // We do not rollback `self.commit_timestamp` to ensure that subsequent events are
231
- // timestamped correctly.
232
- var index = chain_error_index;
233
- while (index > chain_start_index) {
234
- index -= 1;
235
-
236
- assert(index >= chain_start_index);
237
- assert(index < chain_error_index);
238
- const event = events[index];
239
- assert(event.timestamp <= self.commit_timestamp);
240
-
241
- switch (operation) {
242
- .create_accounts => self.create_account_rollback(&event),
243
- .create_transfers => self.create_transfer_rollback(&event),
244
- else => unreachable,
245
- }
246
- log.debug("{s} {}/{}: rollback(): {}", .{
247
- @tagName(operation),
248
- index + 1,
249
- events.len,
250
- event,
251
- });
252
- }
253
- assert(index == chain_start_index);
254
- }
255
-
256
- fn execute_lookup_accounts(self: *StateMachine, input: []const u8, output: []u8) usize {
257
- const batch = mem.bytesAsSlice(u128, input);
258
- const output_len = @divFloor(output.len, @sizeOf(Account)) * @sizeOf(Account);
259
- const results = mem.bytesAsSlice(Account, output[0..output_len]);
260
- var results_count: usize = 0;
261
- for (batch) |id| {
262
- if (self.get_account(id)) |result| {
263
- results[results_count] = result.*;
264
- results_count += 1;
265
- }
266
- }
267
- return results_count * @sizeOf(Account);
268
- }
269
-
270
- fn execute_lookup_transfers(self: *StateMachine, input: []const u8, output: []u8) usize {
271
- const batch = mem.bytesAsSlice(u128, input);
272
- const output_len = @divFloor(output.len, @sizeOf(Transfer)) * @sizeOf(Transfer);
273
- const results = mem.bytesAsSlice(Transfer, output[0..output_len]);
274
- var results_count: usize = 0;
275
- for (batch) |id| {
276
- if (self.get_transfer(id)) |result| {
277
- results[results_count] = result.*;
278
- results_count += 1;
279
- }
280
- }
281
- return results_count * @sizeOf(Transfer);
282
- }
283
-
284
- fn create_account(self: *StateMachine, a: *const Account) CreateAccountResult {
285
- assert(a.timestamp > self.commit_timestamp);
286
-
287
- if (a.flags.padding != 0) return .reserved_flag;
288
- if (!zeroed_48_bytes(a.reserved)) return .reserved_field;
289
-
290
- if (a.id == 0) return .id_must_not_be_zero;
291
- if (a.ledger == 0) return .ledger_must_not_be_zero;
292
- if (a.code == 0) return .code_must_not_be_zero;
293
-
294
- if (a.flags.debits_must_not_exceed_credits and a.flags.credits_must_not_exceed_debits) {
295
- return .mutually_exclusive_flags;
296
- }
297
-
298
- if (sum_overflows(a.debits_pending, a.debits_posted)) return .overflows_debits;
299
- if (sum_overflows(a.credits_pending, a.credits_posted)) return .overflows_credits;
300
-
301
- // Opening balances may never exceed limits:
302
- if (a.debits_exceed_credits(0)) return .exceeds_credits;
303
- if (a.credits_exceed_debits(0)) return .exceeds_debits;
304
-
305
- if (self.get_account(a.id)) |e| return create_account_exists(a, e);
306
-
307
- self.accounts.putAssumeCapacityNoClobber(a.id, a.*);
308
-
309
- self.commit_timestamp = a.timestamp;
310
- return .ok;
311
- }
312
-
313
- fn create_account_rollback(self: *StateMachine, a: *const Account) void {
314
- assert(self.accounts.remove(a.id));
315
- }
316
-
317
- fn create_account_exists(a: *const Account, e: *const Account) CreateAccountResult {
318
- assert(a.id == e.id);
319
- if (@bitCast(u16, a.flags) != @bitCast(u16, e.flags)) return .exists_with_different_flags;
320
- if (a.user_data != e.user_data) return .exists_with_different_user_data;
321
- assert(zeroed_48_bytes(a.reserved) and zeroed_48_bytes(e.reserved));
322
- if (a.ledger != e.ledger) return .exists_with_different_ledger;
323
- if (a.code != e.code) return .exists_with_different_code;
324
- if (a.debits_pending != e.debits_pending) return .exists_with_different_debits_pending;
325
- if (a.debits_posted != e.debits_posted) return .exists_with_different_debits_posted;
326
- if (a.credits_pending != e.credits_pending) return .exists_with_different_credits_pending;
327
- if (a.credits_posted != e.credits_posted) return .exists_with_different_credits_posted;
328
- return .exists;
329
- }
330
-
331
- fn create_transfer(self: *StateMachine, t: *const Transfer) CreateTransferResult {
332
- assert(t.timestamp > self.commit_timestamp);
333
-
334
- if (t.flags.padding != 0) return .reserved_flag;
335
- if (t.reserved != 0) return .reserved_field;
336
-
337
- if (t.id == 0) return .id_must_not_be_zero;
338
-
339
- if (t.flags.post_pending_transfer or t.flags.void_pending_transfer) {
340
- return self.post_or_void_pending_transfer(t);
341
- }
342
-
343
- if (t.debit_account_id == 0) return .debit_account_id_must_not_be_zero;
344
- if (t.credit_account_id == 0) return .credit_account_id_must_not_be_zero;
345
- if (t.credit_account_id == t.debit_account_id) return .accounts_must_be_different;
346
-
347
- if (t.pending_id != 0) return .pending_id_must_be_zero;
348
- if (t.flags.pending) {
349
- // Otherwise, reserved amounts may never be released.
350
- if (t.timeout == 0) return .pending_transfer_must_timeout;
351
- } else {
352
- if (t.timeout != 0) return .timeout_reserved_for_pending_transfer;
353
- }
354
-
355
- if (t.ledger == 0) return .ledger_must_not_be_zero;
356
- if (t.code == 0) return .code_must_not_be_zero;
357
- if (t.amount == 0) return .amount_must_not_be_zero;
358
-
359
- // The etymology of the DR and CR abbreviations for debit/credit is interesting, either:
360
- // 1. derived from the Latin past participles of debitum/creditum, i.e. debere/credere,
361
- // 2. standing for debit record and credit record, or
362
- // 3. relating to debtor and creditor.
363
- // We use them to distinguish between `cr` (credit account), and `c` (commit).
364
- const dr = self.get_account(t.debit_account_id) orelse return .debit_account_not_found;
365
- const cr = self.get_account(t.credit_account_id) orelse return .credit_account_not_found;
366
- assert(dr.id == t.debit_account_id);
367
- assert(cr.id == t.credit_account_id);
368
- assert(t.timestamp > dr.timestamp);
369
- assert(t.timestamp > cr.timestamp);
370
-
371
- if (dr.ledger != cr.ledger) return .accounts_must_have_the_same_ledger;
372
- if (t.ledger != dr.ledger) return .transfer_must_have_the_same_ledger_as_accounts;
373
-
374
- // If the transfer already exists, then it must not influence the overflow or limit checks.
375
- if (self.get_transfer(t.id)) |e| return create_transfer_exists(t, e);
376
-
377
- if (t.flags.pending) {
378
- if (sum_overflows(t.amount, dr.debits_pending)) return .overflows_debits_pending;
379
- if (sum_overflows(t.amount, cr.credits_pending)) return .overflows_credits_pending;
380
- }
381
- if (sum_overflows(t.amount, dr.debits_posted)) return .overflows_debits_posted;
382
- if (sum_overflows(t.amount, cr.credits_posted)) return .overflows_credits_posted;
383
- // We assert that the sum of the pending and posted balances can never overflow:
384
- if (sum_overflows(t.amount, dr.debits_pending + dr.debits_posted)) {
385
- return .overflows_debits;
386
- }
387
- if (sum_overflows(t.amount, cr.credits_pending + cr.credits_posted)) {
388
- return .overflows_credits;
389
- }
390
-
391
- if (dr.debits_exceed_credits(t.amount)) return .exceeds_credits;
392
- if (cr.credits_exceed_debits(t.amount)) return .exceeds_debits;
393
-
394
- self.transfers.putAssumeCapacityNoClobber(t.id, t.*);
395
-
396
- if (t.flags.pending) {
397
- dr.debits_pending += t.amount;
398
- cr.credits_pending += t.amount;
399
- } else {
400
- dr.debits_posted += t.amount;
401
- cr.credits_posted += t.amount;
402
- }
403
-
404
- self.commit_timestamp = t.timestamp;
405
- return .ok;
406
- }
407
-
408
- fn create_transfer_rollback(self: *StateMachine, t: *const Transfer) void {
409
- if (t.flags.post_pending_transfer or t.flags.void_pending_transfer) {
410
- return self.post_or_void_pending_transfer_rollback(t);
411
- }
412
-
413
- const dr = self.get_account(t.debit_account_id).?;
414
- const cr = self.get_account(t.credit_account_id).?;
415
- assert(dr.id == t.debit_account_id);
416
- assert(cr.id == t.credit_account_id);
417
-
418
- if (t.flags.pending) {
419
- dr.debits_pending -= t.amount;
420
- cr.credits_pending -= t.amount;
421
- } else {
422
- dr.debits_posted -= t.amount;
423
- cr.credits_posted -= t.amount;
424
- }
425
- assert(self.transfers.remove(t.id));
426
- }
427
-
428
- fn create_transfer_exists(t: *const Transfer, e: *const Transfer) CreateTransferResult {
429
- assert(t.id == e.id);
430
- // The flags change the behavior of the remaining comparisons, so compare the flags first.
431
- if (@bitCast(u16, t.flags) != @bitCast(u16, e.flags)) return .exists_with_different_flags;
432
- if (t.debit_account_id != e.debit_account_id) {
433
- return .exists_with_different_debit_account_id;
434
- }
435
- if (t.credit_account_id != e.credit_account_id) {
436
- return .exists_with_different_credit_account_id;
437
- }
438
- if (t.user_data != e.user_data) return .exists_with_different_user_data;
439
- assert(t.reserved == 0 and e.reserved == 0);
440
- assert(t.pending_id == 0 and e.pending_id == 0); // We know that the flags are the same.
441
- if (t.timeout != e.timeout) return .exists_with_different_timeout;
442
- assert(t.ledger == e.ledger); // If the accounts are the same, the ledger must be the same.
443
- if (t.code != e.code) return .exists_with_different_code;
444
- if (t.amount != e.amount) return .exists_with_different_amount;
445
- return .exists;
446
- }
447
-
448
- fn post_or_void_pending_transfer(self: *StateMachine, t: *const Transfer) CreateTransferResult {
449
- assert(t.id != 0);
450
- assert(t.flags.padding == 0);
451
- assert(t.reserved == 0);
452
- assert(t.timestamp > self.commit_timestamp);
453
- assert(t.flags.post_pending_transfer or t.flags.void_pending_transfer);
454
-
455
- if (t.flags.post_pending_transfer and t.flags.void_pending_transfer) {
456
- return .cannot_post_and_void_pending_transfer;
457
- }
458
- if (t.flags.pending) return .pending_transfer_cannot_post_or_void_another;
459
- if (t.timeout != 0) return .timeout_reserved_for_pending_transfer;
460
-
461
- if (t.pending_id == 0) return .pending_id_must_not_be_zero;
462
- if (t.pending_id == t.id) return .pending_id_must_be_different;
463
-
464
- const p = self.get_transfer(t.pending_id) orelse return .pending_transfer_not_found;
465
- assert(p.id == t.pending_id);
466
- if (!p.flags.pending) return .pending_transfer_not_pending;
467
-
468
- const dr = self.get_account(p.debit_account_id).?;
469
- const cr = self.get_account(p.credit_account_id).?;
470
- assert(dr.id == p.debit_account_id);
471
- assert(cr.id == p.credit_account_id);
472
- assert(p.timestamp > dr.timestamp);
473
- assert(p.timestamp > cr.timestamp);
474
- assert(p.amount > 0);
475
-
476
- if (t.debit_account_id > 0 and t.debit_account_id != p.debit_account_id) {
477
- return .pending_transfer_has_different_debit_account_id;
478
- }
479
- if (t.credit_account_id > 0 and t.credit_account_id != p.credit_account_id) {
480
- return .pending_transfer_has_different_credit_account_id;
481
- }
482
- // The user_data field is allowed to differ across pending and posting/voiding transfers.
483
- if (t.ledger > 0 and t.ledger != p.ledger) return .pending_transfer_has_different_ledger;
484
- if (t.code > 0 and t.code != p.code) return .pending_transfer_has_different_code;
485
-
486
- const amount = if (t.amount > 0) t.amount else p.amount;
487
- if (amount > p.amount) return .exceeds_pending_transfer_amount;
488
-
489
- if (t.flags.void_pending_transfer and amount < p.amount) {
490
- return .pending_transfer_has_different_amount;
491
- }
492
-
493
- if (self.get_transfer(t.id)) |e| return post_or_void_pending_transfer_exists(t, e, p);
494
-
495
- if (self.get_posted(t.pending_id)) |posted| {
496
- if (posted) return .pending_transfer_already_posted;
497
- return .pending_transfer_already_voided;
498
- }
499
-
500
- assert(p.timestamp < t.timestamp);
501
- assert(p.timeout > 0);
502
- if (p.timestamp + p.timeout <= t.timestamp) return .pending_transfer_expired;
503
-
504
- self.transfers.putAssumeCapacityNoClobber(t.id, .{
505
- .id = t.id,
506
- .debit_account_id = p.debit_account_id,
507
- .credit_account_id = p.credit_account_id,
508
- .user_data = if (t.user_data > 0) t.user_data else p.user_data,
509
- .reserved = p.reserved,
510
- .ledger = p.ledger,
511
- .code = p.code,
512
- .pending_id = t.pending_id,
513
- .timeout = t.timeout,
514
- .timestamp = t.timestamp,
515
- .flags = t.flags,
516
- .amount = amount,
517
- });
518
-
519
- self.posted.putAssumeCapacityNoClobber(t.pending_id, t.flags.post_pending_transfer);
520
-
521
- dr.debits_pending -= p.amount;
522
- cr.credits_pending -= p.amount;
523
-
524
- if (t.flags.post_pending_transfer) {
525
- assert(amount > 0);
526
- assert(amount <= p.amount);
527
- dr.debits_posted += amount;
528
- cr.credits_posted += amount;
529
- }
530
-
531
- self.commit_timestamp = t.timestamp;
532
- return .ok;
533
- }
534
-
535
- fn post_or_void_pending_transfer_rollback(self: *StateMachine, t: *const Transfer) void {
536
- assert(t.id > 0);
537
- assert(t.flags.post_pending_transfer or t.flags.void_pending_transfer);
538
-
539
- assert(t.pending_id > 0);
540
- const p = self.get_transfer(t.pending_id).?;
541
- assert(p.id == t.pending_id);
542
- assert(p.debit_account_id > 0);
543
- assert(p.credit_account_id > 0);
544
-
545
- const dr = self.get_account(p.debit_account_id).?;
546
- const cr = self.get_account(p.credit_account_id).?;
547
- assert(dr.id == p.debit_account_id);
548
- assert(cr.id == p.credit_account_id);
549
-
550
- if (t.flags.post_pending_transfer) {
551
- const amount = if (t.amount > 0) t.amount else p.amount;
552
- assert(amount > 0);
553
- assert(amount <= p.amount);
554
- dr.debits_posted -= amount;
555
- cr.credits_posted -= amount;
556
- }
557
- dr.debits_pending += p.amount;
558
- cr.credits_pending += p.amount;
559
-
560
- assert(self.posted.remove(t.pending_id));
561
- assert(self.transfers.remove(t.id));
562
- }
563
-
564
- fn post_or_void_pending_transfer_exists(
565
- t: *const Transfer,
566
- e: *const Transfer,
567
- p: *const Transfer,
568
- ) CreateTransferResult {
569
- assert(p.flags.pending);
570
- assert(t.pending_id == p.id);
571
- assert(t.id != p.id);
572
- assert(t.id == e.id);
573
-
574
- // Do not assume that `e` is necessarily a posting or voiding transfer.
575
- if (@bitCast(u16, t.flags) != @bitCast(u16, e.flags)) {
576
- return .exists_with_different_flags;
577
- }
578
-
579
- // If `e` posted or voided a different pending transfer, then the accounts will differ.
580
- if (t.pending_id != e.pending_id) return .exists_with_different_pending_id;
581
-
582
- assert(e.flags.post_pending_transfer or e.flags.void_pending_transfer);
583
- assert(e.debit_account_id == p.debit_account_id);
584
- assert(e.credit_account_id == p.credit_account_id);
585
- assert(e.reserved == 0);
586
- assert(e.pending_id == p.id);
587
- assert(e.timeout == 0);
588
- assert(e.ledger == p.ledger);
589
- assert(e.code == p.code);
590
- assert(e.timestamp > p.timestamp);
591
-
592
- assert(t.flags.post_pending_transfer == e.flags.post_pending_transfer);
593
- assert(t.flags.void_pending_transfer == e.flags.void_pending_transfer);
594
- assert(t.debit_account_id == 0 or t.debit_account_id == e.debit_account_id);
595
- assert(t.credit_account_id == 0 or t.credit_account_id == e.credit_account_id);
596
- assert(t.reserved == 0);
597
- assert(t.timeout == 0);
598
- assert(t.ledger == 0 or t.ledger == e.ledger);
599
- assert(t.code == 0 or t.code == e.code);
600
- assert(t.timestamp > e.timestamp);
601
-
602
- if (t.user_data == 0) {
603
- if (e.user_data != p.user_data) return .exists_with_different_user_data;
604
- } else {
605
- if (t.user_data != e.user_data) return .exists_with_different_user_data;
606
- }
607
-
608
- if (t.amount == 0) {
609
- if (e.amount != p.amount) return .exists_with_different_amount;
610
- } else {
611
- if (t.amount != e.amount) return .exists_with_different_amount;
612
- }
613
-
614
- return .exists;
615
- }
616
-
617
- /// This is our core private method for changing balances.
618
- /// Returns a live pointer to an Account in the accounts hash map.
619
- /// This is intended to lookup an Account and modify balances directly by reference.
620
- /// This pointer is invalidated if the hash map is resized by another insert, e.g. if we get a
621
- /// pointer, insert another account without capacity, and then modify this pointer... BOOM!
622
- /// This is a sharp tool but replaces a lookup, copy and update with a single lookup.
623
- fn get_account(self: *const StateMachine, id: u128) ?*Account {
624
- return self.accounts.getPtr(id);
625
- }
626
-
627
- /// See the comment for get_account().
628
- fn get_transfer(self: *const StateMachine, id: u128) ?*Transfer {
629
- return self.transfers.getPtr(id);
630
- }
631
-
632
- /// Returns whether a pending transfer, if it exists, has already been posted or voided.
633
- fn get_posted(self: *const StateMachine, pending_id: u128) ?bool {
634
- return self.posted.get(pending_id);
635
- }
636
- };
637
-
638
- fn sum_overflows(a: u64, b: u64) bool {
639
- var c: u64 = undefined;
640
- return @addWithOverflow(u64, a, b, &c);
641
- }
642
-
643
- /// Optimizes for the common case, where the array is zeroed. Completely branchless.
644
- fn zeroed_32_bytes(a: [32]u8) bool {
645
- const x = @bitCast([4]u64, a);
646
- return (x[0] | x[1] | x[2] | x[3]) == 0;
647
- }
648
-
649
- fn zeroed_48_bytes(a: [48]u8) bool {
650
- const x = @bitCast([6]u64, a);
651
- return (x[0] | x[1] | x[2] | x[3] | x[4] | x[5]) == 0;
652
- }
653
-
654
- /// Optimizes for the common case, where the arrays are equal. Completely branchless.
655
- fn equal_32_bytes(a: [32]u8, b: [32]u8) bool {
656
- const x = @bitCast([4]u64, a);
657
- const y = @bitCast([4]u64, b);
658
-
659
- const c = (x[0] ^ y[0]) | (x[1] ^ y[1]);
660
- const d = (x[2] ^ y[2]) | (x[3] ^ y[3]);
661
-
662
- return (c | d) == 0;
663
- }
664
-
665
- fn equal_48_bytes(a: [48]u8, b: [48]u8) bool {
666
- const x = @bitCast([6]u64, a);
667
- const y = @bitCast([6]u64, b);
668
-
669
- const c = (x[0] ^ y[0]) | (x[1] ^ y[1]);
670
- const d = (x[2] ^ y[2]) | (x[3] ^ y[3]);
671
- const e = (x[4] ^ y[4]) | (x[5] ^ y[5]);
672
-
673
- return (c | d | e) == 0;
674
- }
675
-
676
- const testing = std.testing;
677
- const expect = testing.expect;
678
- const expectEqual = testing.expectEqual;
679
- const expectEqualSlices = testing.expectEqualSlices;
680
-
681
- test "sum_overflows" {
682
- try expectEqual(false, sum_overflows(math.maxInt(u64), 0));
683
- try expectEqual(false, sum_overflows(math.maxInt(u64) - 1, 1));
684
- try expectEqual(false, sum_overflows(1, math.maxInt(u64) - 1));
685
-
686
- try expectEqual(true, sum_overflows(math.maxInt(u64), 1));
687
- try expectEqual(true, sum_overflows(1, math.maxInt(u64)));
688
-
689
- try expectEqual(true, sum_overflows(math.maxInt(u64), math.maxInt(u64)));
690
- try expectEqual(true, sum_overflows(math.maxInt(u64), math.maxInt(u64)));
691
- }
692
-
693
- test "create/lookup/rollback accounts" {
694
- const Vector = struct { result: CreateAccountResult, object: Account };
695
-
696
- const vectors = [_]Vector{
697
- .{
698
- .result = .ok,
699
- .object = mem.zeroInit(Account, .{
700
- .id = 1,
701
- .user_data = 2,
702
- .ledger = 3,
703
- .code = 4,
704
- .debits_pending = 5,
705
- .debits_posted = 6,
706
- .credits_pending = 7,
707
- .credits_posted = 8,
708
- .timestamp = 1,
709
- }),
710
- },
711
- .{
712
- .result = .reserved_flag,
713
- .object = mem.zeroInit(Account, .{
714
- .id = 0,
715
- .user_data = 0,
716
- .reserved = [_]u8{1} ** 48,
717
- .ledger = 0,
718
- .code = 0,
719
- .flags = .{
720
- .padding = 1,
721
- .debits_must_not_exceed_credits = true,
722
- .credits_must_not_exceed_debits = true,
723
- },
724
- .debits_pending = math.maxInt(u64),
725
- .debits_posted = math.maxInt(u64),
726
- .credits_pending = math.maxInt(u64),
727
- .credits_posted = math.maxInt(u64),
728
- .timestamp = 2,
729
- }),
730
- },
731
- .{
732
- .result = .reserved_field,
733
- .object = mem.zeroInit(Account, .{
734
- .id = 0,
735
- .user_data = 0,
736
- .reserved = [_]u8{1} ** 48,
737
- .ledger = 0,
738
- .code = 0,
739
- .flags = .{
740
- .padding = 0,
741
- .debits_must_not_exceed_credits = true,
742
- .credits_must_not_exceed_debits = true,
743
- },
744
- .debits_pending = math.maxInt(u64),
745
- .debits_posted = math.maxInt(u64),
746
- .credits_pending = math.maxInt(u64),
747
- .credits_posted = math.maxInt(u64),
748
- .timestamp = 2,
749
- }),
750
- },
751
- .{
752
- .result = .id_must_not_be_zero,
753
- .object = mem.zeroInit(Account, .{
754
- .id = 0,
755
- .user_data = 0,
756
- .ledger = 0,
757
- .code = 0,
758
- .flags = .{
759
- .padding = 0,
760
- .debits_must_not_exceed_credits = true,
761
- .credits_must_not_exceed_debits = true,
762
- },
763
- .debits_pending = math.maxInt(u64),
764
- .debits_posted = math.maxInt(u64),
765
- .credits_pending = math.maxInt(u64),
766
- .credits_posted = math.maxInt(u64),
767
- .timestamp = 2,
768
- }),
769
- },
770
- .{
771
- .result = .ledger_must_not_be_zero,
772
- .object = mem.zeroInit(Account, .{
773
- .id = 1,
774
- .user_data = 20,
775
- .ledger = 0,
776
- .code = 0,
777
- .flags = .{
778
- .padding = 0,
779
- .debits_must_not_exceed_credits = true,
780
- .credits_must_not_exceed_debits = true,
781
- },
782
- .debits_pending = math.maxInt(u64),
783
- .debits_posted = math.maxInt(u64),
784
- .credits_pending = math.maxInt(u64),
785
- .credits_posted = math.maxInt(u64),
786
- .timestamp = 2,
787
- }),
788
- },
789
- .{
790
- .result = .code_must_not_be_zero,
791
- .object = mem.zeroInit(Account, .{
792
- .id = 1,
793
- .user_data = 20,
794
- .ledger = 30,
795
- .code = 0,
796
- .flags = .{
797
- .debits_must_not_exceed_credits = true,
798
- .credits_must_not_exceed_debits = true,
799
- },
800
- .debits_pending = math.maxInt(u64),
801
- .debits_posted = math.maxInt(u64),
802
- .credits_pending = math.maxInt(u64),
803
- .credits_posted = math.maxInt(u64),
804
- .timestamp = 2,
805
- }),
806
- },
807
- .{
808
- .result = .mutually_exclusive_flags,
809
- .object = mem.zeroInit(Account, .{
810
- .id = 1,
811
- .user_data = 20,
812
- .ledger = 30,
813
- .code = 40,
814
- .flags = .{
815
- .debits_must_not_exceed_credits = true,
816
- .credits_must_not_exceed_debits = true,
817
- },
818
- .debits_pending = math.maxInt(u64),
819
- .debits_posted = math.maxInt(u64),
820
- .credits_pending = math.maxInt(u64),
821
- .credits_posted = math.maxInt(u64),
822
- .timestamp = 2,
823
- }),
824
- },
825
- .{
826
- .result = .overflows_debits,
827
- .object = mem.zeroInit(Account, .{
828
- .id = 1,
829
- .user_data = 20,
830
- .ledger = 30,
831
- .code = 40,
832
- .flags = .{
833
- .debits_must_not_exceed_credits = true,
834
- },
835
- .debits_pending = math.maxInt(u64),
836
- .debits_posted = 60,
837
- .credits_pending = math.maxInt(u64),
838
- .credits_posted = 80,
839
- .timestamp = 2,
840
- }),
841
- },
842
- .{
843
- .result = .overflows_credits,
844
- .object = mem.zeroInit(Account, .{
845
- .id = 1,
846
- .user_data = 20,
847
- .ledger = 30,
848
- .code = 40,
849
- .flags = .{
850
- .credits_must_not_exceed_debits = true,
851
- },
852
- .debits_pending = 50,
853
- .debits_posted = 60,
854
- .credits_pending = math.maxInt(u64),
855
- .credits_posted = 80,
856
- .timestamp = 2,
857
- }),
858
- },
859
- .{
860
- .result = .exceeds_credits,
861
- .object = mem.zeroInit(Account, .{
862
- .id = 1,
863
- .user_data = 20,
864
- .ledger = 30,
865
- .code = 40,
866
- .flags = .{
867
- .debits_must_not_exceed_credits = true,
868
- },
869
- .debits_pending = 50,
870
- .debits_posted = 60,
871
- .credits_pending = 1,
872
- .credits_posted = 109,
873
- .timestamp = 2,
874
- }),
875
- },
876
- .{
877
- .result = .exceeds_debits,
878
- .object = mem.zeroInit(Account, .{
879
- .id = 1,
880
- .user_data = 20,
881
- .ledger = 30,
882
- .code = 40,
883
- .flags = .{
884
- .credits_must_not_exceed_debits = true,
885
- },
886
- .debits_pending = 50,
887
- .debits_posted = 60,
888
- .credits_pending = 1,
889
- .credits_posted = 109,
890
- .timestamp = 2,
891
- }),
892
- },
893
- .{
894
- .result = .exists_with_different_flags,
895
- .object = mem.zeroInit(Account, .{
896
- .id = 1,
897
- .user_data = 20,
898
- .ledger = 30,
899
- .code = 40,
900
- .flags = .{
901
- .credits_must_not_exceed_debits = true,
902
- },
903
- .debits_pending = 50,
904
- .debits_posted = 60,
905
- .credits_pending = 0,
906
- .credits_posted = 0,
907
- .timestamp = 2,
908
- }),
909
- },
910
- .{
911
- .result = .exists_with_different_user_data,
912
- .object = mem.zeroInit(Account, .{
913
- .id = 1,
914
- .user_data = 20,
915
- .ledger = 30,
916
- .code = 40,
917
- .debits_pending = 50,
918
- .debits_posted = 60,
919
- .credits_pending = 70,
920
- .credits_posted = 80,
921
- .timestamp = 2,
922
- }),
923
- },
924
- .{
925
- .result = .exists_with_different_ledger,
926
- .object = mem.zeroInit(Account, .{
927
- .id = 1,
928
- .user_data = 2,
929
- .ledger = 30,
930
- .code = 40,
931
- .debits_pending = 50,
932
- .debits_posted = 60,
933
- .credits_pending = 70,
934
- .credits_posted = 80,
935
- .timestamp = 2,
936
- }),
937
- },
938
- .{
939
- .result = .exists_with_different_code,
940
- .object = mem.zeroInit(Account, .{
941
- .id = 1,
942
- .user_data = 2,
943
- .ledger = 3,
944
- .code = 40,
945
- .debits_pending = 50,
946
- .debits_posted = 60,
947
- .credits_pending = 70,
948
- .credits_posted = 80,
949
- .timestamp = 2,
950
- }),
951
- },
952
- .{
953
- .result = .exists_with_different_debits_pending,
954
- .object = mem.zeroInit(Account, .{
955
- .id = 1,
956
- .user_data = 2,
957
- .ledger = 3,
958
- .code = 4,
959
- .debits_pending = 50,
960
- .debits_posted = 60,
961
- .credits_pending = 70,
962
- .credits_posted = 80,
963
- .timestamp = 2,
964
- }),
965
- },
966
- .{
967
- .result = .exists_with_different_debits_posted,
968
- .object = mem.zeroInit(Account, .{
969
- .id = 1,
970
- .user_data = 2,
971
- .ledger = 3,
972
- .code = 4,
973
- .debits_pending = 5,
974
- .debits_posted = 60,
975
- .credits_pending = 70,
976
- .credits_posted = 80,
977
- .timestamp = 2,
978
- }),
979
- },
980
- .{
981
- .result = .exists_with_different_credits_pending,
982
- .object = mem.zeroInit(Account, .{
983
- .id = 1,
984
- .user_data = 2,
985
- .ledger = 3,
986
- .code = 4,
987
- .debits_pending = 5,
988
- .debits_posted = 6,
989
- .credits_pending = 70,
990
- .credits_posted = 80,
991
- .timestamp = 2,
992
- }),
993
- },
994
- .{
995
- .result = .exists_with_different_credits_posted,
996
- .object = mem.zeroInit(Account, .{
997
- .id = 1,
998
- .user_data = 2,
999
- .ledger = 3,
1000
- .code = 4,
1001
- .debits_pending = 5,
1002
- .debits_posted = 6,
1003
- .credits_pending = 7,
1004
- .credits_posted = 80,
1005
- .timestamp = 2,
1006
- }),
1007
- },
1008
- .{
1009
- .result = .exists,
1010
- .object = mem.zeroInit(Account, .{
1011
- .id = 1,
1012
- .user_data = 2,
1013
- .ledger = 3,
1014
- .code = 4,
1015
- .debits_pending = 5,
1016
- .debits_posted = 6,
1017
- .credits_pending = 7,
1018
- .credits_posted = 8,
1019
- .timestamp = 2,
1020
- }),
1021
- },
1022
- };
1023
-
1024
- var state_machine = try StateMachine.init(testing.allocator, vectors.len, 0, 0);
1025
- defer state_machine.deinit();
1026
-
1027
- for (vectors) |vector, i| {
1028
- const result = state_machine.create_account(&vector.object);
1029
- expectEqual(vector.result, result) catch |err| {
1030
- print_test_vector(i, vector.result, result, vector.object, err);
1031
- return err;
1032
- };
1033
-
1034
- if (vector.result == .ok) {
1035
- try expectEqual(vector.object, state_machine.get_account(vector.object.id).?.*);
1036
- }
1037
- }
1038
-
1039
- state_machine.create_account_rollback(&vectors[0].object);
1040
- try expect(state_machine.get_account(vectors[0].object.id) == null);
1041
- }
1042
-
1043
- test "linked accounts" {
1044
- const accounts_max = 5;
1045
- const transfers_max = 0;
1046
- const transfers_pending_max = 0;
1047
-
1048
- var accounts = [_]Account{
1049
- // An individual event (successful):
1050
- mem.zeroInit(Account, .{ .id = 7, .code = 1, .ledger = 1 }),
1051
-
1052
- // A chain of 4 events (the last event in the chain closes the chain with linked=false):
1053
- // Commit/rollback.
1054
- mem.zeroInit(Account, .{ .id = 1, .code = 1, .ledger = 1, .flags = .{ .linked = true } }),
1055
- // Commit/rollback.
1056
- mem.zeroInit(Account, .{ .id = 2, .code = 1, .ledger = 1, .flags = .{ .linked = true } }),
1057
- // Fail with .exists.
1058
- mem.zeroInit(Account, .{ .id = 1, .code = 1, .ledger = 1, .flags = .{ .linked = true } }),
1059
- // Fail without committing.
1060
- mem.zeroInit(Account, .{ .id = 3, .code = 1, .ledger = 1 }),
1061
-
1062
- // An individual event (successful):
1063
- // This should not see any effect from the failed chain above.
1064
- mem.zeroInit(Account, .{ .id = 1, .code = 1, .ledger = 1 }),
1065
-
1066
- // A chain of 2 events (the first event fails the chain):
1067
- mem.zeroInit(Account, .{ .id = 1, .code = 2, .ledger = 1, .flags = .{ .linked = true } }),
1068
- mem.zeroInit(Account, .{ .id = 2, .code = 1, .ledger = 1 }),
1069
-
1070
- // An individual event (successful):
1071
- mem.zeroInit(Account, .{ .id = 2, .code = 1, .ledger = 1 }),
1072
-
1073
- // A chain of 2 events (the last event fails the chain):
1074
- mem.zeroInit(Account, .{ .id = 3, .code = 1, .ledger = 1, .flags = .{ .linked = true } }),
1075
- mem.zeroInit(Account, .{ .id = 1, .code = 1, .ledger = 2 }),
1076
-
1077
- // A chain of 2 events (successful):
1078
- mem.zeroInit(Account, .{ .id = 3, .code = 1, .ledger = 1, .flags = .{ .linked = true } }),
1079
- mem.zeroInit(Account, .{ .id = 4, .code = 1, .ledger = 1 }),
1080
- };
1081
-
1082
- var state_machine = try StateMachine.init(
1083
- testing.allocator,
1084
- accounts_max,
1085
- transfers_max,
1086
- transfers_pending_max,
1087
- );
1088
- defer state_machine.deinit();
1089
-
1090
- const input = mem.asBytes(&accounts);
1091
-
1092
- const output = try testing.allocator.alloc(u8, 4096);
1093
- defer testing.allocator.free(output);
1094
-
1095
- _ = state_machine.prepare(.create_accounts, input);
1096
- const size = state_machine.commit(0, .create_accounts, input, output);
1097
- const results = mem.bytesAsSlice(CreateAccountsResult, output[0..size]);
1098
-
1099
- try expectEqualSlices(
1100
- CreateAccountsResult,
1101
- &[_]CreateAccountsResult{
1102
- .{ .index = 1, .result = .linked_event_failed },
1103
- .{ .index = 2, .result = .linked_event_failed },
1104
- .{ .index = 3, .result = .exists },
1105
- .{ .index = 4, .result = .linked_event_failed },
1106
- .{ .index = 6, .result = .exists_with_different_flags },
1107
- .{ .index = 7, .result = .linked_event_failed },
1108
- .{ .index = 9, .result = .linked_event_failed },
1109
- .{ .index = 10, .result = .exists_with_different_ledger },
1110
- },
1111
- results,
1112
- );
1113
-
1114
- try expectEqual(accounts[0], state_machine.get_account(accounts[0].id).?.*);
1115
- try expectEqual(accounts[5], state_machine.get_account(accounts[5].id).?.*);
1116
- try expectEqual(accounts[8], state_machine.get_account(accounts[8].id).?.*);
1117
- try expectEqual(accounts[11], state_machine.get_account(accounts[11].id).?.*);
1118
- try expectEqual(accounts[12], state_machine.get_account(accounts[12].id).?.*);
1119
- try expectEqual(@as(u32, 5), state_machine.accounts.count());
1120
-
1121
- // TODO How can we test that events were in fact rolled back in LIFO order?
1122
- // All our rollback handlers appear to be commutative.
1123
- }
1124
-
1125
- // The goal is to ensure that:
1126
- // 1. all CreateTransferResult enums are covered, with
1127
- // 2. enums tested in the order that they are defined, for easier auditing of coverage, and that
1128
- // 3. state machine logic cannot be reordered in any way, breaking determinism.
1129
- test "create/lookup/rollback transfers" {
1130
- var accounts = [_]Account{
1131
- mem.zeroInit(Account, .{
1132
- .id = 1,
1133
- .ledger = 1,
1134
- .code = 1,
1135
- .debits_pending = 100,
1136
- .debits_posted = 200,
1137
- }),
1138
- mem.zeroInit(Account, .{ .id = 2, .ledger = 2, .code = 2 }),
1139
- mem.zeroInit(Account, .{
1140
- .id = 3,
1141
- .ledger = 1,
1142
- .code = 1,
1143
- .credits_pending = 110,
1144
- .credits_posted = 210,
1145
- }),
1146
- mem.zeroInit(Account, .{
1147
- .id = 4,
1148
- .ledger = 1,
1149
- .code = 1,
1150
- .flags = .{ .debits_must_not_exceed_credits = true },
1151
- .debits_pending = 20,
1152
- .debits_posted = math.maxInt(u64) - 500 - 200,
1153
- .credits_pending = 0,
1154
- .credits_posted = math.maxInt(u64) - 500,
1155
- }),
1156
- mem.zeroInit(Account, .{
1157
- .id = 5,
1158
- .ledger = 1,
1159
- .code = 1,
1160
- .flags = .{ .credits_must_not_exceed_debits = true },
1161
- .debits_pending = 0,
1162
- .debits_posted = math.maxInt(u64) - 1000,
1163
- .credits_pending = 10,
1164
- .credits_posted = math.maxInt(u64) - 1000 - 100,
1165
- }),
1166
- };
1167
-
1168
- var state_machine = try StateMachine.init(testing.allocator, accounts.len, 1, 0);
1169
- defer state_machine.deinit();
1170
-
1171
- const input = mem.asBytes(&accounts);
1172
-
1173
- const output = try testing.allocator.alloc(u8, 4096);
1174
- defer testing.allocator.free(output);
1175
-
1176
- _ = state_machine.prepare(.create_accounts, input);
1177
- const size = state_machine.commit(0, .create_accounts, input, output);
1178
-
1179
- const errors = mem.bytesAsSlice(CreateAccountsResult, output[0..size]);
1180
- try expect(errors.len == 0);
1181
-
1182
- for (accounts) |account| {
1183
- try expectEqual(account, state_machine.get_account(account.id).?.*);
1184
- }
1185
-
1186
- const Vector = struct { result: CreateTransferResult, object: Transfer };
1187
-
1188
- const timestamp: u64 = (state_machine.commit_timestamp + 1);
1189
- const vectors = [_]Vector{
1190
- .{
1191
- .result = .reserved_flag,
1192
- .object = mem.zeroInit(Transfer, .{
1193
- .id = 0,
1194
- .debit_account_id = 0,
1195
- .credit_account_id = 0,
1196
- .reserved = 1,
1197
- .pending_id = 1,
1198
- .timeout = 0,
1199
- .ledger = 0,
1200
- .code = 0,
1201
- .flags = .{ .pending = true, .padding = 1 },
1202
- .amount = 0,
1203
- .timestamp = timestamp,
1204
- }),
1205
- },
1206
- .{
1207
- .result = .reserved_field,
1208
- .object = mem.zeroInit(Transfer, .{
1209
- .id = 0,
1210
- .debit_account_id = 0,
1211
- .credit_account_id = 0,
1212
- .reserved = 1,
1213
- .pending_id = 1,
1214
- .timeout = 0,
1215
- .ledger = 0,
1216
- .code = 0,
1217
- .flags = .{ .pending = true },
1218
- .amount = 0,
1219
- .timestamp = timestamp,
1220
- }),
1221
- },
1222
- .{
1223
- .result = .id_must_not_be_zero,
1224
- .object = mem.zeroInit(Transfer, .{
1225
- .id = 0,
1226
- .debit_account_id = 0,
1227
- .credit_account_id = 0,
1228
- .pending_id = 1,
1229
- .timeout = 0,
1230
- .ledger = 0,
1231
- .code = 0,
1232
- .flags = .{ .pending = true },
1233
- .amount = 0,
1234
- .timestamp = timestamp,
1235
- }),
1236
- },
1237
- .{
1238
- .result = .debit_account_id_must_not_be_zero,
1239
- .object = mem.zeroInit(Transfer, .{
1240
- .id = 1,
1241
- .debit_account_id = 0,
1242
- .credit_account_id = 0,
1243
- .pending_id = 1,
1244
- .timeout = 0,
1245
- .ledger = 0,
1246
- .code = 0,
1247
- .flags = .{ .pending = true },
1248
- .amount = 0,
1249
- .timestamp = timestamp,
1250
- }),
1251
- },
1252
- .{
1253
- .result = .credit_account_id_must_not_be_zero,
1254
- .object = mem.zeroInit(Transfer, .{
1255
- .id = 1,
1256
- .debit_account_id = 100,
1257
- .credit_account_id = 0,
1258
- .pending_id = 1,
1259
- .timeout = 0,
1260
- .ledger = 0,
1261
- .code = 0,
1262
- .flags = .{ .pending = true },
1263
- .amount = 0,
1264
- .timestamp = timestamp,
1265
- }),
1266
- },
1267
- .{
1268
- .result = .accounts_must_be_different,
1269
- .object = mem.zeroInit(Transfer, .{
1270
- .id = 1,
1271
- .debit_account_id = 100,
1272
- .credit_account_id = 100,
1273
- .pending_id = 1,
1274
- .timeout = 0,
1275
- .ledger = 0,
1276
- .code = 0,
1277
- .flags = .{ .pending = true },
1278
- .amount = 0,
1279
- .timestamp = timestamp,
1280
- }),
1281
- },
1282
- .{
1283
- .result = .pending_id_must_be_zero,
1284
- .object = mem.zeroInit(Transfer, .{
1285
- .id = 1,
1286
- .debit_account_id = 100,
1287
- .credit_account_id = 200,
1288
- .pending_id = 1,
1289
- .timeout = 0,
1290
- .ledger = 0,
1291
- .code = 0,
1292
- .flags = .{ .pending = true },
1293
- .amount = 0,
1294
- .timestamp = timestamp,
1295
- }),
1296
- },
1297
- .{
1298
- .result = .pending_transfer_must_timeout,
1299
- .object = mem.zeroInit(Transfer, .{
1300
- .id = 1,
1301
- .debit_account_id = 100,
1302
- .credit_account_id = 200,
1303
- .timeout = 0,
1304
- .ledger = 0,
1305
- .code = 0,
1306
- .flags = .{ .pending = true },
1307
- .amount = 0,
1308
- .timestamp = timestamp,
1309
- }),
1310
- },
1311
- .{
1312
- .result = .timeout_reserved_for_pending_transfer,
1313
- .object = mem.zeroInit(Transfer, .{
1314
- .id = 1,
1315
- .debit_account_id = 100,
1316
- .credit_account_id = 200,
1317
- .timeout = 1,
1318
- .ledger = 0,
1319
- .code = 0,
1320
- .amount = 0,
1321
- .timestamp = timestamp,
1322
- }),
1323
- },
1324
- .{
1325
- .result = .ledger_must_not_be_zero,
1326
- .object = mem.zeroInit(Transfer, .{
1327
- .id = 1,
1328
- .debit_account_id = 100,
1329
- .credit_account_id = 200,
1330
- .timeout = 1,
1331
- .ledger = 0,
1332
- .code = 0,
1333
- .flags = .{ .pending = true },
1334
- .amount = 0,
1335
- .timestamp = timestamp,
1336
- }),
1337
- },
1338
- .{
1339
- .result = .code_must_not_be_zero,
1340
- .object = mem.zeroInit(Transfer, .{
1341
- .id = 1,
1342
- .debit_account_id = 100,
1343
- .credit_account_id = 200,
1344
- .timeout = 1,
1345
- .ledger = 100,
1346
- .code = 0,
1347
- .flags = .{ .pending = true },
1348
- .amount = 0,
1349
- .timestamp = timestamp,
1350
- }),
1351
- },
1352
- .{
1353
- .result = .amount_must_not_be_zero,
1354
- .object = mem.zeroInit(Transfer, .{
1355
- .id = 1,
1356
- .debit_account_id = 100,
1357
- .credit_account_id = 200,
1358
- .timeout = 1,
1359
- .ledger = 100,
1360
- .code = 1,
1361
- .flags = .{ .pending = true },
1362
- .amount = 0,
1363
- .timestamp = timestamp,
1364
- }),
1365
- },
1366
- .{
1367
- .result = .debit_account_not_found,
1368
- .object = mem.zeroInit(Transfer, .{
1369
- .id = 1,
1370
- .debit_account_id = 100,
1371
- .credit_account_id = 200,
1372
- .timeout = 1,
1373
- .ledger = 100,
1374
- .code = 1,
1375
- .flags = .{ .pending = true },
1376
- .amount = 100,
1377
- .timestamp = timestamp,
1378
- }),
1379
- },
1380
- .{
1381
- .result = .credit_account_not_found,
1382
- .object = mem.zeroInit(Transfer, .{
1383
- .id = 1,
1384
- .debit_account_id = 1,
1385
- .credit_account_id = 200,
1386
- .timeout = 1,
1387
- .ledger = 100,
1388
- .code = 1,
1389
- .flags = .{ .pending = true },
1390
- .amount = 100,
1391
- .timestamp = timestamp,
1392
- }),
1393
- },
1394
- .{
1395
- .result = .accounts_must_have_the_same_ledger,
1396
- .object = mem.zeroInit(Transfer, .{
1397
- .id = 1,
1398
- .debit_account_id = 1,
1399
- .credit_account_id = 2,
1400
- .ledger = 100,
1401
- .code = 1,
1402
- .amount = 1,
1403
- .timestamp = timestamp,
1404
- }),
1405
- },
1406
- .{
1407
- .result = .transfer_must_have_the_same_ledger_as_accounts,
1408
- .object = mem.zeroInit(Transfer, .{
1409
- .id = 1,
1410
- .debit_account_id = 1,
1411
- .credit_account_id = 3,
1412
- .ledger = 100,
1413
- .code = 1,
1414
- .amount = 1,
1415
- .timestamp = timestamp,
1416
- }),
1417
- },
1418
- .{
1419
- .result = .overflows_debits_pending,
1420
- .object = mem.zeroInit(Transfer, .{
1421
- .id = 1,
1422
- .debit_account_id = 1,
1423
- .credit_account_id = 3,
1424
- .timeout = 30000,
1425
- .ledger = 1,
1426
- .code = 1,
1427
- .flags = .{ .pending = true },
1428
- .amount = math.maxInt(u64) - accounts[1 - 1].debits_pending + 1,
1429
- .timestamp = timestamp,
1430
- }),
1431
- },
1432
- .{
1433
- .result = .overflows_credits_pending,
1434
- .object = mem.zeroInit(Transfer, .{
1435
- .id = 1,
1436
- .debit_account_id = 1,
1437
- .credit_account_id = 3,
1438
- .timeout = 30000,
1439
- .ledger = 1,
1440
- .code = 1,
1441
- .flags = .{ .pending = true },
1442
- .amount = math.maxInt(u64) - accounts[3 - 1].credits_pending + 1,
1443
- .timestamp = timestamp,
1444
- }),
1445
- },
1446
- .{
1447
- .result = .overflows_debits_posted,
1448
- .object = mem.zeroInit(Transfer, .{
1449
- .id = 1,
1450
- .debit_account_id = 1,
1451
- .credit_account_id = 3,
1452
- .ledger = 1,
1453
- .code = 1,
1454
- .amount = math.maxInt(u64) - accounts[1 - 1].debits_posted + 1,
1455
- .timestamp = timestamp,
1456
- }),
1457
- },
1458
- .{
1459
- .result = .overflows_credits_posted,
1460
- .object = mem.zeroInit(Transfer, .{
1461
- .id = 1,
1462
- .debit_account_id = 1,
1463
- .credit_account_id = 3,
1464
- .ledger = 1,
1465
- .code = 1,
1466
- .amount = math.maxInt(u64) - accounts[3 - 1].credits_posted + 1,
1467
- .timestamp = timestamp,
1468
- }),
1469
- },
1470
- .{
1471
- .result = .overflows_debits,
1472
- .object = mem.zeroInit(Transfer, .{
1473
- .id = 1,
1474
- .debit_account_id = 1,
1475
- .credit_account_id = 3,
1476
- .ledger = 1,
1477
- .code = 1,
1478
- .amount = math.maxInt(u64) -
1479
- accounts[1 - 1].debits_pending -
1480
- accounts[1 - 1].debits_posted + 1,
1481
- .timestamp = timestamp,
1482
- }),
1483
- },
1484
- .{
1485
- .result = .overflows_credits,
1486
- .object = mem.zeroInit(Transfer, .{
1487
- .id = 1,
1488
- .debit_account_id = 1,
1489
- .credit_account_id = 3,
1490
- .ledger = 1,
1491
- .code = 1,
1492
- .amount = math.maxInt(u64) -
1493
- accounts[3 - 1].credits_pending -
1494
- accounts[3 - 1].credits_posted + 1,
1495
- .timestamp = timestamp,
1496
- }),
1497
- },
1498
- .{
1499
- .result = .exceeds_credits,
1500
- .object = mem.zeroInit(Transfer, .{
1501
- .id = 1,
1502
- .debit_account_id = 4,
1503
- .credit_account_id = 5,
1504
- .ledger = 1,
1505
- .code = 1,
1506
- .amount = accounts[4 - 1].credits_posted -
1507
- accounts[4 - 1].debits_pending -
1508
- accounts[4 - 1].debits_posted + 1,
1509
- .timestamp = timestamp,
1510
- }),
1511
- },
1512
- .{
1513
- .result = .exceeds_debits,
1514
- .object = mem.zeroInit(Transfer, .{
1515
- .id = 1,
1516
- .debit_account_id = 4,
1517
- .credit_account_id = 5,
1518
- .ledger = 1,
1519
- .code = 1,
1520
- .amount = accounts[5 - 1].debits_posted -
1521
- accounts[5 - 1].credits_pending -
1522
- accounts[5 - 1].credits_posted + 1,
1523
- .timestamp = timestamp,
1524
- }),
1525
- },
1526
- .{
1527
- .result = .ok,
1528
- .object = mem.zeroInit(Transfer, .{
1529
- .id = 1,
1530
- .debit_account_id = 1,
1531
- .credit_account_id = 3,
1532
- .timeout = 10000,
1533
- .ledger = 1,
1534
- .code = 1,
1535
- .flags = .{ .pending = true },
1536
- .amount = 123,
1537
- .timestamp = timestamp + 1,
1538
- }),
1539
- },
1540
- .{
1541
- // Ensure that idempotence is only checked after validation.
1542
- .result = .transfer_must_have_the_same_ledger_as_accounts,
1543
- .object = mem.zeroInit(Transfer, .{
1544
- .id = 1,
1545
- .debit_account_id = 1,
1546
- .credit_account_id = 3,
1547
- .timeout = 10000,
1548
- .ledger = 2,
1549
- .code = 1,
1550
- .flags = .{ .pending = true },
1551
- .amount = 123,
1552
- .timestamp = timestamp + 2,
1553
- }),
1554
- },
1555
- .{
1556
- .result = .exists_with_different_flags,
1557
- .object = mem.zeroInit(Transfer, .{
1558
- .id = 1,
1559
- .debit_account_id = 1,
1560
- .credit_account_id = 3,
1561
- .user_data = 1,
1562
- .ledger = 1,
1563
- .code = 2,
1564
- .flags = .{ .pending = false },
1565
- .amount = math.maxInt(u64),
1566
- .timestamp = timestamp + 2,
1567
- }),
1568
- },
1569
- .{
1570
- .result = .exists_with_different_debit_account_id,
1571
- .object = mem.zeroInit(Transfer, .{
1572
- .id = 1,
1573
- .debit_account_id = 3,
1574
- .credit_account_id = 1,
1575
- .user_data = 1,
1576
- .timeout = 10000,
1577
- .ledger = 1,
1578
- .code = 2,
1579
- .flags = .{ .pending = true },
1580
- .amount = math.maxInt(u64),
1581
- .timestamp = timestamp + 2,
1582
- }),
1583
- },
1584
- .{
1585
- .result = .exists_with_different_credit_account_id,
1586
- .object = mem.zeroInit(Transfer, .{
1587
- .id = 1,
1588
- .debit_account_id = 1,
1589
- .credit_account_id = 4,
1590
- .user_data = 1,
1591
- .timeout = 10000,
1592
- .ledger = 1,
1593
- .code = 2,
1594
- .flags = .{ .pending = true },
1595
- .amount = math.maxInt(u64),
1596
- .timestamp = timestamp + 2,
1597
- }),
1598
- },
1599
- .{
1600
- .result = .exists_with_different_user_data,
1601
- .object = mem.zeroInit(Transfer, .{
1602
- .id = 1,
1603
- .debit_account_id = 1,
1604
- .credit_account_id = 3,
1605
- .user_data = 1,
1606
- .timeout = 10000,
1607
- .ledger = 1,
1608
- .code = 2,
1609
- .flags = .{ .pending = true },
1610
- .amount = math.maxInt(u64),
1611
- .timestamp = timestamp + 2,
1612
- }),
1613
- },
1614
- .{
1615
- .result = .exists_with_different_timeout,
1616
- .object = mem.zeroInit(Transfer, .{
1617
- .id = 1,
1618
- .debit_account_id = 1,
1619
- .credit_account_id = 3,
1620
- .timeout = 10001,
1621
- .ledger = 1,
1622
- .code = 2,
1623
- .flags = .{ .pending = true },
1624
- .amount = math.maxInt(u64),
1625
- .timestamp = timestamp + 2,
1626
- }),
1627
- },
1628
- .{
1629
- .result = .exists_with_different_code,
1630
- .object = mem.zeroInit(Transfer, .{
1631
- .id = 1,
1632
- .debit_account_id = 1,
1633
- .credit_account_id = 3,
1634
- .timeout = 10000,
1635
- .ledger = 1,
1636
- .code = 2,
1637
- .flags = .{ .pending = true },
1638
- .amount = math.maxInt(u64),
1639
- .timestamp = timestamp + 2,
1640
- }),
1641
- },
1642
- .{
1643
- .result = .exists_with_different_amount,
1644
- .object = mem.zeroInit(Transfer, .{
1645
- .id = 1,
1646
- .debit_account_id = 1,
1647
- .credit_account_id = 3,
1648
- .timeout = 10000,
1649
- .ledger = 1,
1650
- .code = 1,
1651
- .flags = .{ .pending = true },
1652
- .amount = math.maxInt(u64),
1653
- .timestamp = timestamp + 2,
1654
- }),
1655
- },
1656
- .{
1657
- .result = .exists,
1658
- .object = mem.zeroInit(Transfer, .{
1659
- .id = 1,
1660
- .debit_account_id = 1,
1661
- .credit_account_id = 3,
1662
- .timeout = 10000,
1663
- .ledger = 1,
1664
- .code = 1,
1665
- .flags = .{ .pending = true },
1666
- .amount = 123,
1667
- .timestamp = timestamp + 2,
1668
- }),
1669
- },
1670
- .{
1671
- .result = .ok,
1672
- .object = mem.zeroInit(Transfer, .{
1673
- .id = 2,
1674
- .debit_account_id = 3,
1675
- .credit_account_id = 1,
1676
- .ledger = 1,
1677
- .code = 2,
1678
- .amount = 7,
1679
- .timestamp = timestamp + 2,
1680
- }),
1681
- },
1682
- .{
1683
- .result = .ok,
1684
- .object = mem.zeroInit(Transfer, .{
1685
- .id = 3,
1686
- .debit_account_id = 1,
1687
- .credit_account_id = 3,
1688
- .ledger = 1,
1689
- .code = 2,
1690
- .amount = 3,
1691
- .timestamp = timestamp + 3,
1692
- }),
1693
- },
1694
- };
1695
-
1696
- for (vectors) |vector, i| {
1697
- const result = state_machine.create_transfer(&vector.object);
1698
- expectEqual(vector.result, result) catch |err| {
1699
- print_test_vector(i, vector.result, result, vector.object, err);
1700
- return err;
1701
- };
1702
- if (vector.result == .ok) {
1703
- try expectEqual(vector.object, state_machine.get_transfer(vector.object.id).?.*);
1704
- }
1705
- }
1706
-
1707
- // Transfer 3:
1708
- try test_account_balances(&state_machine, 1, 100 + 123, 200 + 3, 0, 7);
1709
- try test_account_balances(&state_machine, 3, 0, 7, 110 + 123, 210 + 3);
1710
- state_machine.create_transfer_rollback(state_machine.get_transfer(3).?);
1711
- try test_account_balances(&state_machine, 1, 100 + 123, 200, 0, 7);
1712
- try test_account_balances(&state_machine, 3, 0, 7, 110 + 123, 210);
1713
- try expect(state_machine.get_transfer(3) == null);
1714
-
1715
- // Transfer 2:
1716
- try test_account_balances(&state_machine, 1, 100 + 123, 200, 0, 7);
1717
- try test_account_balances(&state_machine, 3, 0, 7, 110 + 123, 210);
1718
- state_machine.create_transfer_rollback(state_machine.get_transfer(2).?);
1719
- try test_account_balances(&state_machine, 1, 100 + 123, 200, 0, 0);
1720
- try test_account_balances(&state_machine, 3, 0, 0, 110 + 123, 210);
1721
- try expect(state_machine.get_transfer(2) == null);
1722
-
1723
- // Transfer 1:
1724
- try test_account_balances(&state_machine, 1, 100 + 123, 200, 0, 0);
1725
- try test_account_balances(&state_machine, 3, 0, 0, 110 + 123, 210);
1726
- state_machine.create_transfer_rollback(state_machine.get_transfer(1).?);
1727
- try test_account_balances(&state_machine, 1, 100, 200, 0, 0);
1728
- try test_account_balances(&state_machine, 3, 0, 0, 110, 210);
1729
- try expect(state_machine.get_transfer(1) == null);
1730
-
1731
- for (accounts) |account| {
1732
- state_machine.create_account_rollback(&account);
1733
- try expect(state_machine.get_account(account.id) == null);
1734
- }
1735
- }
1736
-
1737
- test "create/lookup/rollback 2-phase transfers" {
1738
- var accounts = [_]Account{
1739
- mem.zeroInit(Account, .{ .id = 1, .ledger = 1, .code = 1 }),
1740
- mem.zeroInit(Account, .{ .id = 2, .ledger = 1, .code = 1 }),
1741
- };
1742
-
1743
- var transfers = [_]Transfer{
1744
- mem.zeroInit(Transfer, .{
1745
- .id = 1,
1746
- .debit_account_id = 1,
1747
- .credit_account_id = 2,
1748
- .ledger = 1,
1749
- .code = 1,
1750
- .amount = 15,
1751
- }),
1752
- mem.zeroInit(Transfer, .{
1753
- .id = 2,
1754
- .debit_account_id = 1,
1755
- .credit_account_id = 2,
1756
- .timeout = 1000,
1757
- .ledger = 1,
1758
- .code = 1,
1759
- .flags = .{ .pending = true },
1760
- .amount = 15,
1761
- }),
1762
- mem.zeroInit(Transfer, .{
1763
- .id = 3,
1764
- .debit_account_id = 1,
1765
- .credit_account_id = 2,
1766
- .timeout = 5,
1767
- .ledger = 1,
1768
- .code = 1,
1769
- .flags = .{ .pending = true },
1770
- .amount = 15,
1771
- }),
1772
- mem.zeroInit(Transfer, .{
1773
- .id = 4,
1774
- .debit_account_id = 1,
1775
- .credit_account_id = 2,
1776
- .timeout = 1,
1777
- .ledger = 1,
1778
- .code = 1,
1779
- .flags = .{ .pending = true },
1780
- .amount = 15,
1781
- }),
1782
- mem.zeroInit(Transfer, .{
1783
- .id = 5,
1784
- .debit_account_id = 1,
1785
- .credit_account_id = 2,
1786
- .user_data = 73,
1787
- .timeout = 6,
1788
- .ledger = 1,
1789
- .code = 1,
1790
- .flags = .{ .pending = true },
1791
- .amount = 7,
1792
- }),
1793
- };
1794
-
1795
- var state_machine = try StateMachine.init(testing.allocator, accounts.len, 100, 1);
1796
- defer state_machine.deinit();
1797
-
1798
- // Create accounts:
1799
- const accounts_input = mem.asBytes(&accounts);
1800
-
1801
- const accounts_output = try testing.allocator.alloc(u8, 4096);
1802
- defer testing.allocator.free(accounts_output);
1803
-
1804
- const accounts_timestamp = state_machine.prepare(.create_accounts, accounts_input);
1805
- {
1806
- const size = state_machine.commit(0, .create_accounts, accounts_input, accounts_output);
1807
- const errors = mem.bytesAsSlice(CreateAccountsResult, accounts_output[0..size]);
1808
- try expectEqual(@as(usize, 0), errors.len);
1809
- }
1810
- for (accounts) |account| {
1811
- try expectEqual(account, state_machine.get_account(account.id).?.*);
1812
- }
1813
-
1814
- // Create pending transfers:
1815
- const transfers_input = mem.asBytes(&transfers);
1816
-
1817
- const transfers_output = try testing.allocator.alloc(u8, 4096);
1818
- defer testing.allocator.free(transfers_output);
1819
-
1820
- const transfers_timestamp = state_machine.prepare(.create_transfers, transfers_input);
1821
- try testing.expect(transfers_timestamp > accounts_timestamp);
1822
- {
1823
- const size = state_machine.commit(0, .create_transfers, transfers_input, transfers_output);
1824
- const errors = mem.bytesAsSlice(CreateTransfersResult, transfers_output[0..size]);
1825
- try expectEqual(@as(usize, 0), errors.len);
1826
- }
1827
- for (transfers) |transfer| {
1828
- try expectEqual(transfer, state_machine.get_transfer(transfer.id).?.*);
1829
- }
1830
-
1831
- // Test balances before posting:
1832
- try test_account_balances(&state_machine, 1, 52, 15, 0, 0);
1833
- try test_account_balances(&state_machine, 2, 0, 0, 52, 15);
1834
-
1835
- // Post pending transfers:
1836
- const Vector = struct { result: CreateTransferResult, object: Transfer };
1837
- const timestamp: u64 = (state_machine.commit_timestamp + 1);
1838
-
1839
- const vectors = [_]Vector{
1840
- .{
1841
- .result = .ok,
1842
- .object = mem.zeroInit(Transfer, .{
1843
- .id = 101,
1844
- .debit_account_id = 1,
1845
- .credit_account_id = 2,
1846
- .user_data = 1000,
1847
- .pending_id = 2,
1848
- .ledger = 1,
1849
- .code = 1,
1850
- .flags = .{ .post_pending_transfer = true },
1851
- .amount = 13,
1852
- .timestamp = timestamp,
1853
- }),
1854
- },
1855
- .{
1856
- .result = .cannot_post_and_void_pending_transfer,
1857
- .object = mem.zeroInit(Transfer, .{
1858
- .id = 101,
1859
- .debit_account_id = 10,
1860
- .credit_account_id = 20,
1861
- .user_data = 30,
1862
- .pending_id = 0,
1863
- .timeout = 50,
1864
- .ledger = 60,
1865
- .code = 70,
1866
- .flags = .{
1867
- .pending = true,
1868
- .post_pending_transfer = true,
1869
- .void_pending_transfer = true,
1870
- },
1871
- .amount = 80,
1872
- .timestamp = timestamp + 1,
1873
- }),
1874
- },
1875
- .{
1876
- .result = .pending_transfer_cannot_post_or_void_another,
1877
- .object = mem.zeroInit(Transfer, .{
1878
- .id = 101,
1879
- .debit_account_id = 10,
1880
- .credit_account_id = 20,
1881
- .user_data = 30,
1882
- .pending_id = 0,
1883
- .timeout = 50,
1884
- .ledger = 60,
1885
- .code = 70,
1886
- .flags = .{
1887
- .pending = true,
1888
- .void_pending_transfer = true,
1889
- },
1890
- .amount = 80,
1891
- .timestamp = timestamp + 1,
1892
- }),
1893
- },
1894
- .{
1895
- .result = .timeout_reserved_for_pending_transfer,
1896
- .object = mem.zeroInit(Transfer, .{
1897
- .id = 101,
1898
- .debit_account_id = 10,
1899
- .credit_account_id = 20,
1900
- .user_data = 30,
1901
- .timeout = 50,
1902
- .ledger = 60,
1903
- .code = 70,
1904
- .flags = .{
1905
- .void_pending_transfer = true,
1906
- },
1907
- .amount = 80,
1908
- .timestamp = timestamp + 1,
1909
- }),
1910
- },
1911
- .{
1912
- .result = .pending_id_must_not_be_zero,
1913
- .object = mem.zeroInit(Transfer, .{
1914
- .id = 101,
1915
- .debit_account_id = 10,
1916
- .credit_account_id = 20,
1917
- .user_data = 30,
1918
- .pending_id = 0,
1919
- .ledger = 60,
1920
- .code = 70,
1921
- .flags = .{
1922
- .void_pending_transfer = true,
1923
- },
1924
- .amount = 80,
1925
- .timestamp = timestamp + 1,
1926
- }),
1927
- },
1928
- .{
1929
- .result = .pending_id_must_be_different,
1930
- .object = mem.zeroInit(Transfer, .{
1931
- .id = 101,
1932
- .debit_account_id = 10,
1933
- .credit_account_id = 20,
1934
- .user_data = 30,
1935
- .pending_id = 101,
1936
- .ledger = 60,
1937
- .code = 70,
1938
- .flags = .{
1939
- .void_pending_transfer = true,
1940
- },
1941
- .amount = 80,
1942
- .timestamp = timestamp + 1,
1943
- }),
1944
- },
1945
- .{
1946
- .result = .pending_transfer_not_found,
1947
- .object = mem.zeroInit(Transfer, .{
1948
- .id = 101,
1949
- .debit_account_id = 10,
1950
- .credit_account_id = 20,
1951
- .user_data = 30,
1952
- .pending_id = 102,
1953
- .ledger = 60,
1954
- .code = 70,
1955
- .flags = .{
1956
- .void_pending_transfer = true,
1957
- },
1958
- .amount = 80,
1959
- .timestamp = timestamp + 1,
1960
- }),
1961
- },
1962
- .{
1963
- .result = .pending_transfer_not_pending,
1964
- .object = mem.zeroInit(Transfer, .{
1965
- .id = 101,
1966
- .debit_account_id = 10,
1967
- .credit_account_id = 20,
1968
- .user_data = 30,
1969
- .pending_id = 1,
1970
- .ledger = 60,
1971
- .code = 70,
1972
- .flags = .{
1973
- .void_pending_transfer = true,
1974
- },
1975
- .amount = 80,
1976
- .timestamp = timestamp + 1,
1977
- }),
1978
- },
1979
- .{
1980
- .result = .pending_transfer_has_different_debit_account_id,
1981
- .object = mem.zeroInit(Transfer, .{
1982
- .id = 101,
1983
- .debit_account_id = 10,
1984
- .credit_account_id = 20,
1985
- .user_data = 30,
1986
- .pending_id = 2,
1987
- .ledger = 60,
1988
- .code = 70,
1989
- .flags = .{
1990
- .void_pending_transfer = true,
1991
- },
1992
- .amount = 80,
1993
- .timestamp = timestamp + 1,
1994
- }),
1995
- },
1996
- .{
1997
- .result = .pending_transfer_has_different_credit_account_id,
1998
- .object = mem.zeroInit(Transfer, .{
1999
- .id = 101,
2000
- .debit_account_id = 1,
2001
- .credit_account_id = 20,
2002
- .user_data = 30,
2003
- .pending_id = 2,
2004
- .ledger = 60,
2005
- .code = 70,
2006
- .flags = .{
2007
- .void_pending_transfer = true,
2008
- },
2009
- .amount = 80,
2010
- .timestamp = timestamp + 1,
2011
- }),
2012
- },
2013
- .{
2014
- .result = .pending_transfer_has_different_ledger,
2015
- .object = mem.zeroInit(Transfer, .{
2016
- .id = 101,
2017
- .debit_account_id = 1,
2018
- .credit_account_id = 2,
2019
- .user_data = 30,
2020
- .pending_id = 2,
2021
- .ledger = 60,
2022
- .code = 70,
2023
- .flags = .{
2024
- .void_pending_transfer = true,
2025
- },
2026
- .amount = 80,
2027
- .timestamp = timestamp + 1,
2028
- }),
2029
- },
2030
- .{
2031
- .result = .pending_transfer_has_different_code,
2032
- .object = mem.zeroInit(Transfer, .{
2033
- .id = 101,
2034
- .debit_account_id = 1,
2035
- .credit_account_id = 2,
2036
- .user_data = 30,
2037
- .pending_id = 2,
2038
- .ledger = 1,
2039
- .code = 70,
2040
- .flags = .{
2041
- .void_pending_transfer = true,
2042
- },
2043
- .amount = 80,
2044
- .timestamp = timestamp + 1,
2045
- }),
2046
- },
2047
- .{
2048
- .result = .exceeds_pending_transfer_amount,
2049
- .object = mem.zeroInit(Transfer, .{
2050
- .id = 101,
2051
- .debit_account_id = 1,
2052
- .credit_account_id = 2,
2053
- .user_data = 7000,
2054
- .pending_id = 2,
2055
- .ledger = 1,
2056
- .code = 1,
2057
- .flags = .{
2058
- .void_pending_transfer = true,
2059
- },
2060
- .amount = 80,
2061
- .timestamp = timestamp + 1,
2062
- }),
2063
- },
2064
- .{
2065
- .result = .pending_transfer_has_different_amount,
2066
- .object = mem.zeroInit(Transfer, .{
2067
- .id = 101,
2068
- .debit_account_id = 1,
2069
- .credit_account_id = 2,
2070
- .user_data = 7000,
2071
- .pending_id = 2,
2072
- .ledger = 1,
2073
- .code = 1,
2074
- .flags = .{
2075
- .void_pending_transfer = true,
2076
- },
2077
- .amount = 1,
2078
- .timestamp = timestamp + 1,
2079
- }),
2080
- },
2081
- .{
2082
- .result = .exists_with_different_flags,
2083
- .object = mem.zeroInit(Transfer, .{
2084
- .id = 101,
2085
- .debit_account_id = 0,
2086
- .credit_account_id = 0,
2087
- .user_data = 7000,
2088
- .pending_id = 3,
2089
- .ledger = 0,
2090
- .code = 0,
2091
- .flags = .{
2092
- .void_pending_transfer = true,
2093
- },
2094
- .amount = 15,
2095
- .timestamp = timestamp + 1,
2096
- }),
2097
- },
2098
- .{
2099
- .result = .exists_with_different_pending_id,
2100
- .object = mem.zeroInit(Transfer, .{
2101
- .id = 101,
2102
- .debit_account_id = 1,
2103
- .credit_account_id = 2,
2104
- .user_data = 7000,
2105
- .pending_id = 3,
2106
- .ledger = 1,
2107
- .code = 1,
2108
- .flags = .{
2109
- .post_pending_transfer = true,
2110
- },
2111
- .amount = 14,
2112
- .timestamp = timestamp + 1,
2113
- }),
2114
- },
2115
- .{
2116
- .result = .exists_with_different_user_data,
2117
- .object = mem.zeroInit(Transfer, .{
2118
- .id = 101,
2119
- .debit_account_id = 1,
2120
- .credit_account_id = 2,
2121
- .user_data = 7000,
2122
- .pending_id = 2,
2123
- .ledger = 1,
2124
- .code = 1,
2125
- .flags = .{
2126
- .post_pending_transfer = true,
2127
- },
2128
- .amount = 14,
2129
- .timestamp = timestamp + 1,
2130
- }),
2131
- },
2132
- .{
2133
- .result = .exists_with_different_user_data,
2134
- .object = mem.zeroInit(Transfer, .{
2135
- .id = 101,
2136
- .debit_account_id = 1,
2137
- .credit_account_id = 2,
2138
- .user_data = 0,
2139
- .pending_id = 2,
2140
- .ledger = 1,
2141
- .code = 1,
2142
- .flags = .{
2143
- .post_pending_transfer = true,
2144
- },
2145
- .amount = 14,
2146
- .timestamp = timestamp + 1,
2147
- }),
2148
- },
2149
- .{
2150
- .result = .exists_with_different_amount,
2151
- .object = mem.zeroInit(Transfer, .{
2152
- .id = 101,
2153
- .debit_account_id = 1,
2154
- .credit_account_id = 2,
2155
- .user_data = 1000,
2156
- .pending_id = 2,
2157
- .ledger = 1,
2158
- .code = 1,
2159
- .flags = .{
2160
- .post_pending_transfer = true,
2161
- },
2162
- .amount = 14,
2163
- .timestamp = timestamp + 1,
2164
- }),
2165
- },
2166
- .{
2167
- .result = .exists_with_different_amount,
2168
- .object = mem.zeroInit(Transfer, .{
2169
- .id = 101,
2170
- .debit_account_id = 1,
2171
- .credit_account_id = 2,
2172
- .user_data = 1000,
2173
- .pending_id = 2,
2174
- .ledger = 1,
2175
- .code = 1,
2176
- .flags = .{
2177
- .post_pending_transfer = true,
2178
- },
2179
- .amount = 0,
2180
- .timestamp = timestamp + 1,
2181
- }),
2182
- },
2183
- .{
2184
- .result = .exists,
2185
- .object = mem.zeroInit(Transfer, .{
2186
- .id = 101,
2187
- .debit_account_id = 1,
2188
- .credit_account_id = 2,
2189
- .user_data = 1000,
2190
- .pending_id = 2,
2191
- .ledger = 1,
2192
- .code = 1,
2193
- .flags = .{
2194
- .post_pending_transfer = true,
2195
- },
2196
- .amount = 13,
2197
- .timestamp = timestamp + 1,
2198
- }),
2199
- },
2200
- .{
2201
- .result = .pending_transfer_already_posted,
2202
- .object = mem.zeroInit(Transfer, .{
2203
- .id = 102,
2204
- .debit_account_id = 1,
2205
- .credit_account_id = 2,
2206
- .user_data = 1000,
2207
- .pending_id = 2,
2208
- .ledger = 1,
2209
- .code = 1,
2210
- .flags = .{
2211
- .post_pending_transfer = true,
2212
- },
2213
- .amount = 13,
2214
- .timestamp = timestamp + 1,
2215
- }),
2216
- },
2217
- .{
2218
- .result = .ok,
2219
- .object = mem.zeroInit(Transfer, .{
2220
- .id = 103,
2221
- .debit_account_id = 0,
2222
- .credit_account_id = 0,
2223
- .user_data = 0,
2224
- .pending_id = 3,
2225
- .ledger = 0,
2226
- .code = 0,
2227
- .flags = .{ .void_pending_transfer = true },
2228
- .amount = 15,
2229
- .timestamp = timestamp + 1,
2230
- }),
2231
- },
2232
- .{
2233
- .result = .pending_transfer_already_voided,
2234
- .object = mem.zeroInit(Transfer, .{
2235
- .id = 102,
2236
- .debit_account_id = 1,
2237
- .credit_account_id = 2,
2238
- .user_data = 1000,
2239
- .pending_id = 3,
2240
- .ledger = 1,
2241
- .code = 1,
2242
- .flags = .{
2243
- .post_pending_transfer = true,
2244
- },
2245
- .amount = 13,
2246
- .timestamp = timestamp + 2,
2247
- }),
2248
- },
2249
- .{
2250
- .result = .pending_transfer_expired,
2251
- .object = mem.zeroInit(Transfer, .{
2252
- .id = 102,
2253
- .debit_account_id = 1,
2254
- .credit_account_id = 2,
2255
- .user_data = 4000,
2256
- .pending_id = 4,
2257
- .ledger = 1,
2258
- .code = 1,
2259
- .flags = .{
2260
- .void_pending_transfer = true,
2261
- },
2262
- .amount = 15,
2263
- .timestamp = timestamp + 2,
2264
- }),
2265
- },
2266
- .{
2267
- .result = .ok,
2268
- .object = mem.zeroInit(Transfer, .{
2269
- .id = 105,
2270
- .debit_account_id = 0,
2271
- .credit_account_id = 0,
2272
- .user_data = 0,
2273
- .pending_id = 5,
2274
- .ledger = 0,
2275
- .code = 0,
2276
- .flags = .{ .post_pending_transfer = true },
2277
- .amount = 0,
2278
- .timestamp = timestamp + 2,
2279
- }),
2280
- },
2281
- };
2282
-
2283
- for (vectors) |vector, i| {
2284
- const result = state_machine.create_transfer(&vector.object);
2285
- expectEqual(vector.result, result) catch |err| {
2286
- print_test_vector(i, vector.result, result, vector.object, err);
2287
- return err;
2288
- };
2289
-
2290
- if (vector.result == .ok) {
2291
- // Test that posted values inherit from the pending or posting transfer:
2292
- const pending = state_machine.get_transfer(vector.object.pending_id).?.*;
2293
- const posted = state_machine.get_transfer(vector.object.id).?.*;
2294
-
2295
- const user_data = if (vector.object.user_data == 0)
2296
- pending.user_data
2297
- else
2298
- vector.object.user_data;
2299
-
2300
- const amount = if (vector.object.amount == 0)
2301
- pending.amount
2302
- else
2303
- vector.object.amount;
2304
-
2305
- const expected = Transfer{
2306
- .id = vector.object.id,
2307
- .debit_account_id = pending.debit_account_id,
2308
- .credit_account_id = pending.credit_account_id,
2309
- .user_data = user_data,
2310
- .reserved = 0,
2311
- .pending_id = pending.id,
2312
- .timeout = 0,
2313
- .ledger = pending.ledger,
2314
- .code = pending.code,
2315
- .flags = vector.object.flags,
2316
- .amount = amount,
2317
- .timestamp = vector.object.timestamp,
2318
- };
2319
- expectEqual(expected, posted) catch |err| {
2320
- print_test_vector(
2321
- i,
2322
- expected,
2323
- posted,
2324
- vector.object,
2325
- err,
2326
- );
2327
- return err;
2328
- };
2329
- }
2330
- }
2331
-
2332
- // Balances after posting:
2333
- try test_account_balances(&state_machine, 1, 15, 35, 0, 0);
2334
- try test_account_balances(&state_machine, 2, 0, 0, 15, 35);
2335
-
2336
- // Rollback posting transfer (different amount):
2337
- assert(vectors[0].result == .ok);
2338
- try test_transfer_rollback(&state_machine, &vectors[0].object);
2339
- try test_account_balances(&state_machine, 1, 30, 22, 0, 0);
2340
- try test_account_balances(&state_machine, 2, 0, 0, 30, 22);
2341
-
2342
- // Rollback voiding transfer:
2343
- assert(vectors[22].result == .ok);
2344
- try test_transfer_rollback(&state_machine, &vectors[22].object);
2345
- try test_account_balances(&state_machine, 1, 45, 22, 0, 0);
2346
- try test_account_balances(&state_machine, 2, 0, 0, 45, 22);
2347
-
2348
- // Rollback posting transfer (zero amount):
2349
- assert(vectors[25].result == .ok);
2350
- try test_transfer_rollback(&state_machine, &vectors[25].object);
2351
- try test_account_balances(&state_machine, 1, 52, 15, 0, 0);
2352
- try test_account_balances(&state_machine, 2, 0, 0, 52, 15);
2353
-
2354
- // Rollback all pending transfers:
2355
- try test_transfer_rollback(&state_machine, &transfers[1]);
2356
- try test_account_balances(&state_machine, 1, 37, 15, 0, 0);
2357
- try test_account_balances(&state_machine, 2, 0, 0, 37, 15);
2358
-
2359
- try test_transfer_rollback(&state_machine, &transfers[2]);
2360
- try test_account_balances(&state_machine, 1, 22, 15, 0, 0);
2361
- try test_account_balances(&state_machine, 2, 0, 0, 22, 15);
2362
-
2363
- try test_transfer_rollback(&state_machine, &transfers[3]);
2364
- try test_account_balances(&state_machine, 1, 7, 15, 0, 0);
2365
- try test_account_balances(&state_machine, 2, 0, 0, 7, 15);
2366
-
2367
- try test_transfer_rollback(&state_machine, &transfers[4]);
2368
- try test_account_balances(&state_machine, 1, 0, 15, 0, 0);
2369
- try test_account_balances(&state_machine, 2, 0, 0, 0, 15);
2370
-
2371
- // Rollback transfer:
2372
- try test_transfer_rollback(&state_machine, &transfers[0]);
2373
- try test_account_balances(&state_machine, 1, 0, 0, 0, 0);
2374
- try test_account_balances(&state_machine, 2, 0, 0, 0, 0);
2375
- }
2376
-
2377
- fn print_test_vector(
2378
- i: usize,
2379
- result_expect: anytype,
2380
- result_actual: anytype,
2381
- vector_object: anytype,
2382
- err: anyerror,
2383
- ) void {
2384
- std.debug.print("\nindex={}\n\nexpect: {}\n\nactual: {}\n\nobject: {}\n\nerr={}", .{
2385
- i,
2386
- result_expect,
2387
- result_actual,
2388
- vector_object,
2389
- err,
2390
- });
2391
- }
2392
-
2393
- fn test_account_balances(
2394
- state_machine: *const StateMachine,
2395
- account_id: u128,
2396
- debits_pending: u64,
2397
- debits_posted: u64,
2398
- credits_pending: u64,
2399
- credits_posted: u64,
2400
- ) !void {
2401
- const account = state_machine.get_account(account_id).?.*;
2402
- try expectEqual(debits_pending, account.debits_pending);
2403
- try expectEqual(debits_posted, account.debits_posted);
2404
- try expectEqual(credits_pending, account.credits_pending);
2405
- try expectEqual(credits_posted, account.credits_posted);
2406
- }
2407
-
2408
- fn test_transfer_rollback(state_machine: *StateMachine, transfer: *const Transfer) !void {
2409
- assert(state_machine.get_transfer(transfer.id) != null);
2410
-
2411
- state_machine.create_transfer_rollback(transfer);
2412
-
2413
- try expect(state_machine.get_transfer(transfer.id) == null);
2414
- if (transfer.flags.post_pending_transfer or transfer.flags.void_pending_transfer) {
2415
- try expect(state_machine.get_posted(transfer.pending_id) == null);
2416
- }
2417
- }
2418
-
2419
- test "zeroed_32_bytes" {
2420
- try test_zeroed_n_bytes(32);
2421
- }
2422
-
2423
- test "zeroed_48_bytes" {
2424
- try test_zeroed_n_bytes(48);
2425
- }
2426
-
2427
- fn test_zeroed_n_bytes(comptime n: usize) !void {
2428
- const routine = switch (n) {
2429
- 32 => zeroed_32_bytes,
2430
- 48 => zeroed_48_bytes,
2431
- else => unreachable,
2432
- };
2433
- var a = [_]u8{0} ** n;
2434
- var i: usize = 0;
2435
- while (i < a.len) : (i += 1) {
2436
- a[i] = 1;
2437
- try expectEqual(false, routine(a));
2438
- a[i] = 0;
2439
- }
2440
- try expectEqual(true, routine(a));
2441
- }
2442
-
2443
- test "equal_32_bytes" {
2444
- try test_equal_n_bytes(32);
2445
- }
2446
-
2447
- test "equal_48_bytes" {
2448
- try test_equal_n_bytes(48);
2449
- }
2450
-
2451
- fn test_equal_n_bytes(comptime n: usize) !void {
2452
- const routine = switch (n) {
2453
- 32 => equal_32_bytes,
2454
- 48 => equal_48_bytes,
2455
- else => unreachable,
2456
- };
2457
- var a = [_]u8{0} ** n;
2458
- var b = [_]u8{0} ** n;
2459
- var i: usize = 0;
2460
- while (i < a.len) : (i += 1) {
2461
- a[i] = 1;
2462
- try expectEqual(false, routine(a, b));
2463
- a[i] = 0;
2464
-
2465
- b[i] = 1;
2466
- try expectEqual(false, routine(a, b));
2467
- b[i] = 0;
2468
- }
2469
- try expectEqual(true, routine(a, b));
2470
- }