tigerbeetle-node 0.8.0 → 0.10.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.
- package/README.md +47 -47
- package/dist/benchmark.js +15 -15
- package/dist/benchmark.js.map +1 -1
- package/dist/index.d.ts +66 -61
- package/dist/index.js +66 -61
- package/dist/index.js.map +1 -1
- package/dist/test.js +1 -1
- package/dist/test.js.map +1 -1
- package/package.json +14 -16
- package/scripts/download_node_headers.sh +3 -1
- package/src/index.ts +5 -0
- package/src/node.zig +18 -19
- package/src/tigerbeetle/scripts/benchmark.bat +47 -46
- package/src/tigerbeetle/scripts/benchmark.sh +25 -10
- package/src/tigerbeetle/scripts/install.sh +2 -1
- package/src/tigerbeetle/scripts/install_zig.bat +109 -109
- package/src/tigerbeetle/scripts/install_zig.sh +18 -18
- package/src/tigerbeetle/scripts/upgrade_ubuntu_kernel.sh +12 -3
- package/src/tigerbeetle/scripts/vopr.bat +47 -47
- package/src/tigerbeetle/scripts/vopr.sh +5 -5
- package/src/tigerbeetle/src/benchmark.zig +17 -9
- package/src/tigerbeetle/src/benchmark_array_search.zig +317 -0
- package/src/tigerbeetle/src/benchmarks/perf.zig +299 -0
- package/src/tigerbeetle/src/c/tb_client/context.zig +103 -0
- package/src/tigerbeetle/src/c/tb_client/packet.zig +80 -0
- package/src/tigerbeetle/src/c/tb_client/signal.zig +288 -0
- package/src/tigerbeetle/src/c/tb_client/thread.zig +329 -0
- package/src/tigerbeetle/src/c/tb_client.h +201 -0
- package/src/tigerbeetle/src/c/tb_client.zig +101 -0
- package/src/tigerbeetle/src/c/test.zig +1 -0
- package/src/tigerbeetle/src/cli.zig +142 -83
- package/src/tigerbeetle/src/config.zig +136 -23
- package/src/tigerbeetle/src/demo.zig +12 -8
- package/src/tigerbeetle/src/demo_03_create_transfers.zig +3 -3
- package/src/tigerbeetle/src/demo_04_create_pending_transfers.zig +10 -10
- package/src/tigerbeetle/src/demo_05_post_pending_transfers.zig +7 -7
- package/src/tigerbeetle/src/demo_06_void_pending_transfers.zig +3 -3
- package/src/tigerbeetle/src/demo_07_lookup_transfers.zig +1 -1
- package/src/tigerbeetle/src/ewah.zig +318 -0
- package/src/tigerbeetle/src/ewah_benchmark.zig +121 -0
- package/src/tigerbeetle/src/eytzinger_benchmark.zig +317 -0
- package/src/tigerbeetle/src/fifo.zig +17 -1
- package/src/tigerbeetle/src/io/darwin.zig +12 -10
- package/src/tigerbeetle/src/io/linux.zig +25 -9
- package/src/tigerbeetle/src/io/windows.zig +13 -9
- package/src/tigerbeetle/src/iops.zig +101 -0
- package/src/tigerbeetle/src/lsm/binary_search.zig +214 -0
- package/src/tigerbeetle/src/lsm/bloom_filter.zig +82 -0
- package/src/tigerbeetle/src/lsm/compaction.zig +603 -0
- package/src/tigerbeetle/src/lsm/composite_key.zig +75 -0
- package/src/tigerbeetle/src/lsm/direction.zig +11 -0
- package/src/tigerbeetle/src/lsm/eytzinger.zig +587 -0
- package/src/tigerbeetle/src/lsm/forest.zig +630 -0
- package/src/tigerbeetle/src/lsm/grid.zig +473 -0
- package/src/tigerbeetle/src/lsm/groove.zig +939 -0
- package/src/tigerbeetle/src/lsm/k_way_merge.zig +452 -0
- package/src/tigerbeetle/src/lsm/level_iterator.zig +296 -0
- package/src/tigerbeetle/src/lsm/manifest.zig +680 -0
- package/src/tigerbeetle/src/lsm/manifest_level.zig +1169 -0
- package/src/tigerbeetle/src/lsm/manifest_log.zig +904 -0
- package/src/tigerbeetle/src/lsm/node_pool.zig +231 -0
- package/src/tigerbeetle/src/lsm/posted_groove.zig +399 -0
- package/src/tigerbeetle/src/lsm/segmented_array.zig +998 -0
- package/src/tigerbeetle/src/lsm/set_associative_cache.zig +844 -0
- package/src/tigerbeetle/src/lsm/table.zig +932 -0
- package/src/tigerbeetle/src/lsm/table_immutable.zig +196 -0
- package/src/tigerbeetle/src/lsm/table_iterator.zig +295 -0
- package/src/tigerbeetle/src/lsm/table_mutable.zig +123 -0
- package/src/tigerbeetle/src/lsm/test.zig +429 -0
- package/src/tigerbeetle/src/lsm/tree.zig +1085 -0
- package/src/tigerbeetle/src/main.zig +121 -95
- package/src/tigerbeetle/src/message_bus.zig +49 -48
- package/src/tigerbeetle/src/message_pool.zig +19 -3
- package/src/tigerbeetle/src/ring_buffer.zig +172 -31
- package/src/tigerbeetle/src/simulator.zig +171 -43
- package/src/tigerbeetle/src/state_machine.zig +1026 -599
- package/src/tigerbeetle/src/storage.zig +46 -16
- package/src/tigerbeetle/src/test/cluster.zig +257 -78
- package/src/tigerbeetle/src/test/message_bus.zig +15 -24
- package/src/tigerbeetle/src/test/network.zig +26 -17
- package/src/tigerbeetle/src/test/packet_simulator.zig +14 -1
- package/src/tigerbeetle/src/test/state_checker.zig +10 -6
- package/src/tigerbeetle/src/test/state_machine.zig +159 -68
- package/src/tigerbeetle/src/test/storage.zig +137 -49
- package/src/tigerbeetle/src/tigerbeetle.zig +5 -0
- package/src/tigerbeetle/src/unit_tests.zig +8 -0
- package/src/tigerbeetle/src/util.zig +51 -0
- package/src/tigerbeetle/src/vsr/client.zig +21 -7
- package/src/tigerbeetle/src/vsr/journal.zig +1429 -514
- package/src/tigerbeetle/src/vsr/replica.zig +1855 -550
- package/src/tigerbeetle/src/vsr/superblock.zig +1743 -0
- package/src/tigerbeetle/src/vsr/superblock_client_table.zig +258 -0
- package/src/tigerbeetle/src/vsr/superblock_free_set.zig +644 -0
- package/src/tigerbeetle/src/vsr/superblock_manifest.zig +546 -0
- package/src/tigerbeetle/src/vsr.zig +134 -52
- package/.yarn/releases/yarn-berry.cjs +0 -55
- package/.yarnrc.yml +0 -1
- package/scripts/postinstall.sh +0 -6
- package/yarn.lock +0 -42
|
@@ -17,634 +17,890 @@ const CreateTransfersResult = tb.CreateTransfersResult;
|
|
|
17
17
|
|
|
18
18
|
const CreateAccountResult = tb.CreateAccountResult;
|
|
19
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
|
-
init,
|
|
31
|
-
register,
|
|
32
|
-
|
|
33
|
-
/// Operations exported by TigerBeetle:
|
|
34
|
-
create_accounts,
|
|
35
|
-
create_transfers,
|
|
36
|
-
lookup_accounts,
|
|
37
|
-
lookup_transfers,
|
|
38
|
-
};
|
|
39
20
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
// TODO After recovery, set prepare_timestamp max(wall clock, op timestamp).
|
|
66
|
-
// TODO After recovery, set commit_timestamp max(wall clock, commit timestamp).
|
|
67
|
-
|
|
68
|
-
return StateMachine{
|
|
69
|
-
.allocator = allocator,
|
|
70
|
-
.prepare_timestamp = 0,
|
|
71
|
-
.commit_timestamp = 0,
|
|
72
|
-
.accounts = accounts,
|
|
73
|
-
.transfers = transfers,
|
|
74
|
-
.posted = posted,
|
|
75
|
-
};
|
|
76
|
-
}
|
|
21
|
+
pub fn StateMachineType(comptime Storage: type) type {
|
|
22
|
+
return struct {
|
|
23
|
+
const StateMachine = @This();
|
|
24
|
+
|
|
25
|
+
const Grid = @import("lsm/grid.zig").GridType(Storage);
|
|
26
|
+
const GrooveType = @import("lsm/groove.zig").GrooveType;
|
|
27
|
+
const ForestType = @import("lsm/forest.zig").ForestType;
|
|
28
|
+
|
|
29
|
+
const AccountsGroove = GrooveType(
|
|
30
|
+
Storage,
|
|
31
|
+
Account,
|
|
32
|
+
.{
|
|
33
|
+
.ignored = &[_][]const u8{ "reserved", "flags" },
|
|
34
|
+
.derived = .{},
|
|
35
|
+
},
|
|
36
|
+
);
|
|
37
|
+
const TransfersGroove = GrooveType(
|
|
38
|
+
Storage,
|
|
39
|
+
Transfer,
|
|
40
|
+
.{
|
|
41
|
+
.ignored = &[_][]const u8{ "reserved", "flags" },
|
|
42
|
+
.derived = .{},
|
|
43
|
+
},
|
|
44
|
+
);
|
|
45
|
+
const PostedGroove = @import("lsm/posted_groove.zig").PostedGrooveType(Storage);
|
|
77
46
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
47
|
+
pub const Forest = ForestType(Storage, .{
|
|
48
|
+
.accounts = AccountsGroove,
|
|
49
|
+
.transfers = TransfersGroove,
|
|
50
|
+
.posted = PostedGroove,
|
|
51
|
+
});
|
|
83
52
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
53
|
+
pub const Operation = enum(u8) {
|
|
54
|
+
/// Operations reserved by VR protocol (for all state machines):
|
|
55
|
+
reserved,
|
|
56
|
+
root,
|
|
57
|
+
register,
|
|
58
|
+
|
|
59
|
+
/// Operations exported by TigerBeetle:
|
|
60
|
+
create_accounts,
|
|
61
|
+
create_transfers,
|
|
62
|
+
lookup_accounts,
|
|
63
|
+
lookup_transfers,
|
|
91
64
|
};
|
|
92
|
-
}
|
|
93
65
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
.lookup_transfers => Transfer,
|
|
100
|
-
else => unreachable,
|
|
66
|
+
pub const Options = struct {
|
|
67
|
+
lsm_forest_node_count: u32,
|
|
68
|
+
cache_size_accounts: u32,
|
|
69
|
+
cache_size_transfers: u32,
|
|
70
|
+
cache_size_posted: u32,
|
|
101
71
|
};
|
|
102
|
-
}
|
|
103
72
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
73
|
+
prepare_timestamp: u64,
|
|
74
|
+
commit_timestamp: u64,
|
|
75
|
+
forest: Forest,
|
|
76
|
+
|
|
77
|
+
prefetch_input: ?[]align(16) const u8 = null,
|
|
78
|
+
prefetch_callback: ?fn (*StateMachine) void = null,
|
|
79
|
+
// TODO(ifreund): use a union for these to save memory, likely an extern union
|
|
80
|
+
// so that we can safetly @ptrCast() until @fieldParentPtr() is implemented
|
|
81
|
+
// for unions. See: https://github.com/ziglang/zig/issues/6611
|
|
82
|
+
prefetch_accounts_context: AccountsGroove.PrefetchContext = undefined,
|
|
83
|
+
prefetch_transfers_context: TransfersGroove.PrefetchContext = undefined,
|
|
84
|
+
prefetch_posted_context: PostedGroove.PrefetchContext = undefined,
|
|
85
|
+
|
|
86
|
+
open_callback: ?fn (*StateMachine) void = null,
|
|
87
|
+
compact_callback: ?fn (*StateMachine) void = null,
|
|
88
|
+
checkpoint_callback: ?fn (*StateMachine) void = null,
|
|
89
|
+
|
|
90
|
+
pub fn init(allocator: mem.Allocator, grid: *Grid, options: Options) !StateMachine {
|
|
91
|
+
var forest = try Forest.init(
|
|
92
|
+
allocator,
|
|
93
|
+
grid,
|
|
94
|
+
options.lsm_forest_node_count,
|
|
95
|
+
.{
|
|
96
|
+
.accounts = .{
|
|
97
|
+
.cache_size = options.cache_size_accounts,
|
|
98
|
+
.commit_count_max = 8191,
|
|
99
|
+
},
|
|
100
|
+
.transfers = .{
|
|
101
|
+
.cache_size = options.cache_size_transfers,
|
|
102
|
+
.commit_count_max = 8191 * 2,
|
|
103
|
+
},
|
|
104
|
+
.posted = .{
|
|
105
|
+
.cache_size = options.cache_size_posted,
|
|
106
|
+
.commit_count_max = 8191 * 2,
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
);
|
|
110
|
+
errdefer forest.deinit(allocator);
|
|
111
|
+
|
|
112
|
+
return StateMachine{
|
|
113
|
+
.prepare_timestamp = 0,
|
|
114
|
+
.commit_timestamp = 0,
|
|
115
|
+
.forest = forest,
|
|
116
|
+
};
|
|
113
117
|
}
|
|
114
|
-
}
|
|
115
118
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
realtime: i64,
|
|
119
|
-
comptime operation: Operation,
|
|
120
|
-
input: []u8,
|
|
121
|
-
) void {
|
|
122
|
-
// Guard against the wall clock going backwards by taking the max with timestamps issued:
|
|
123
|
-
self.prepare_timestamp = math.max(
|
|
124
|
-
// The cluster `commit_timestamp` may be ahead of our `prepare_timestamp` because this
|
|
125
|
-
// may be our first prepare as a recently elected leader:
|
|
126
|
-
math.max(self.prepare_timestamp, self.commit_timestamp) + 1,
|
|
127
|
-
@intCast(u64, realtime),
|
|
128
|
-
);
|
|
129
|
-
assert(self.prepare_timestamp > self.commit_timestamp);
|
|
130
|
-
var sum_reserved_timestamps: usize = 0;
|
|
131
|
-
var events = mem.bytesAsSlice(Event(operation), input);
|
|
132
|
-
for (events) |*event| {
|
|
133
|
-
sum_reserved_timestamps += event.timestamp;
|
|
134
|
-
self.prepare_timestamp += 1;
|
|
135
|
-
event.timestamp = self.prepare_timestamp;
|
|
119
|
+
pub fn deinit(self: *StateMachine, allocator: mem.Allocator) void {
|
|
120
|
+
self.forest.deinit(allocator);
|
|
136
121
|
}
|
|
137
|
-
// The client is responsible for ensuring that timestamps are reserved:
|
|
138
|
-
// Use a single branch condition to detect non-zero reserved timestamps.
|
|
139
|
-
// Summing then branching once is faster than branching every iteration of the loop.
|
|
140
|
-
assert(sum_reserved_timestamps == 0);
|
|
141
|
-
}
|
|
142
122
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
return switch (operation) {
|
|
153
|
-
.init => unreachable,
|
|
154
|
-
.register => 0,
|
|
155
|
-
.create_accounts => self.execute(.create_accounts, input, output),
|
|
156
|
-
.create_transfers => self.execute(.create_transfers, input, output),
|
|
157
|
-
.lookup_accounts => self.execute_lookup_accounts(input, output),
|
|
158
|
-
.lookup_transfers => self.execute_lookup_transfers(input, output),
|
|
159
|
-
else => unreachable,
|
|
160
|
-
};
|
|
161
|
-
}
|
|
123
|
+
pub fn Event(comptime operation: Operation) type {
|
|
124
|
+
return switch (operation) {
|
|
125
|
+
.create_accounts => Account,
|
|
126
|
+
.create_transfers => Transfer,
|
|
127
|
+
.lookup_accounts => u128,
|
|
128
|
+
.lookup_transfers => u128,
|
|
129
|
+
else => unreachable,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
162
132
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
comptime assert(operation != .lookup_accounts and operation != .lookup_transfers);
|
|
170
|
-
|
|
171
|
-
const events = mem.bytesAsSlice(Event(operation), input);
|
|
172
|
-
var results = mem.bytesAsSlice(Result(operation), output);
|
|
173
|
-
var count: usize = 0;
|
|
174
|
-
|
|
175
|
-
var chain: ?usize = null;
|
|
176
|
-
var chain_broken = false;
|
|
177
|
-
|
|
178
|
-
for (events) |*event, index| {
|
|
179
|
-
if (event.flags.linked and chain == null) {
|
|
180
|
-
chain = index;
|
|
181
|
-
assert(chain_broken == false);
|
|
182
|
-
}
|
|
183
|
-
const result = if (chain_broken) .linked_event_failed else switch (operation) {
|
|
184
|
-
.create_accounts => self.create_account(event),
|
|
185
|
-
.create_transfers => self.create_transfer(event),
|
|
133
|
+
pub fn Result(comptime operation: Operation) type {
|
|
134
|
+
return switch (operation) {
|
|
135
|
+
.create_accounts => CreateAccountsResult,
|
|
136
|
+
.create_transfers => CreateTransfersResult,
|
|
137
|
+
.lookup_accounts => Account,
|
|
138
|
+
.lookup_transfers => Transfer,
|
|
186
139
|
else => unreachable,
|
|
187
140
|
};
|
|
188
|
-
log.debug("{s} {}/{}: {}: {}", .{
|
|
189
|
-
@tagName(operation),
|
|
190
|
-
index + 1,
|
|
191
|
-
events.len,
|
|
192
|
-
result,
|
|
193
|
-
event,
|
|
194
|
-
});
|
|
195
|
-
if (result != .ok) {
|
|
196
|
-
if (chain) |chain_start_index| {
|
|
197
|
-
if (!chain_broken) {
|
|
198
|
-
chain_broken = true;
|
|
199
|
-
// Rollback events in LIFO order, excluding this event that broke the chain:
|
|
200
|
-
self.rollback(operation, input, chain_start_index, index);
|
|
201
|
-
// Add errors for rolled back events in FIFO order:
|
|
202
|
-
var chain_index = chain_start_index;
|
|
203
|
-
while (chain_index < index) : (chain_index += 1) {
|
|
204
|
-
results[count] = .{
|
|
205
|
-
.index = @intCast(u32, chain_index),
|
|
206
|
-
.result = .linked_event_failed,
|
|
207
|
-
};
|
|
208
|
-
count += 1;
|
|
209
|
-
}
|
|
210
|
-
} else {
|
|
211
|
-
assert(result == .linked_event_failed);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
results[count] = .{ .index = @intCast(u32, index), .result = result };
|
|
215
|
-
count += 1;
|
|
216
|
-
}
|
|
217
|
-
if (!event.flags.linked and chain != null) {
|
|
218
|
-
chain = null;
|
|
219
|
-
chain_broken = false;
|
|
220
|
-
}
|
|
221
141
|
}
|
|
222
|
-
// TODO client.zig: Validate that batch chains are always well-formed and closed.
|
|
223
|
-
// This is programming error and we should raise an exception for this in the client ASAP.
|
|
224
|
-
assert(chain == null);
|
|
225
|
-
assert(chain_broken == false);
|
|
226
142
|
|
|
227
|
-
|
|
228
|
-
|
|
143
|
+
pub fn open(self: *StateMachine, callback: fn (*StateMachine) void) void {
|
|
144
|
+
assert(self.open_callback == null);
|
|
145
|
+
self.open_callback = callback;
|
|
146
|
+
|
|
147
|
+
self.forest.open(forest_open_callback);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
fn forest_open_callback(forest: *Forest) void {
|
|
151
|
+
const self = @fieldParentPtr(StateMachine, "forest", forest);
|
|
152
|
+
assert(self.open_callback != null);
|
|
229
153
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
chain_start_index: usize,
|
|
235
|
-
chain_error_index: usize,
|
|
236
|
-
) void {
|
|
237
|
-
const events = mem.bytesAsSlice(Event(operation), input);
|
|
238
|
-
|
|
239
|
-
// We commit events in FIFO order.
|
|
240
|
-
// We must therefore rollback events in LIFO order with a reverse loop.
|
|
241
|
-
// We do not rollback `self.commit_timestamp` to ensure that subsequent events are
|
|
242
|
-
// timestamped correctly.
|
|
243
|
-
var index = chain_error_index;
|
|
244
|
-
while (index > chain_start_index) {
|
|
245
|
-
index -= 1;
|
|
246
|
-
|
|
247
|
-
assert(index >= chain_start_index);
|
|
248
|
-
assert(index < chain_error_index);
|
|
249
|
-
const event = events[index];
|
|
250
|
-
assert(event.timestamp <= self.commit_timestamp);
|
|
154
|
+
const callback = self.open_callback.?;
|
|
155
|
+
self.open_callback = null;
|
|
156
|
+
callback(self);
|
|
157
|
+
}
|
|
251
158
|
|
|
159
|
+
/// Returns the header's timestamp.
|
|
160
|
+
pub fn prepare(self: *StateMachine, operation: Operation, input: []u8) u64 {
|
|
252
161
|
switch (operation) {
|
|
253
|
-
.
|
|
254
|
-
.
|
|
162
|
+
.root => unreachable,
|
|
163
|
+
.register => {},
|
|
164
|
+
.create_accounts => self.prepare_timestamps(.create_accounts, input),
|
|
165
|
+
.create_transfers => self.prepare_timestamps(.create_transfers, input),
|
|
166
|
+
.lookup_accounts => {},
|
|
167
|
+
.lookup_transfers => {},
|
|
255
168
|
else => unreachable,
|
|
256
169
|
}
|
|
257
|
-
|
|
258
|
-
@tagName(operation),
|
|
259
|
-
index + 1,
|
|
260
|
-
events.len,
|
|
261
|
-
event,
|
|
262
|
-
});
|
|
170
|
+
return self.prepare_timestamp;
|
|
263
171
|
}
|
|
264
|
-
assert(index == chain_start_index);
|
|
265
|
-
}
|
|
266
172
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
173
|
+
fn prepare_timestamps(
|
|
174
|
+
self: *StateMachine,
|
|
175
|
+
comptime operation: Operation,
|
|
176
|
+
input: []u8,
|
|
177
|
+
) void {
|
|
178
|
+
var sum_reserved_timestamps: usize = 0;
|
|
179
|
+
var events = mem.bytesAsSlice(Event(operation), input);
|
|
180
|
+
for (events) |*event| {
|
|
181
|
+
sum_reserved_timestamps += event.timestamp;
|
|
182
|
+
self.prepare_timestamp += 1;
|
|
183
|
+
event.timestamp = self.prepare_timestamp;
|
|
276
184
|
}
|
|
185
|
+
// The client is responsible for ensuring that timestamps are reserved:
|
|
186
|
+
// Use a single branch condition to detect non-zero reserved timestamps.
|
|
187
|
+
// Summing then branching once is faster than branching every iteration of the loop.
|
|
188
|
+
assert(sum_reserved_timestamps == 0);
|
|
277
189
|
}
|
|
278
|
-
return results_count * @sizeOf(Account);
|
|
279
|
-
}
|
|
280
190
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
191
|
+
pub fn prefetch(
|
|
192
|
+
self: *StateMachine,
|
|
193
|
+
callback: fn (*StateMachine) void,
|
|
194
|
+
op: u64,
|
|
195
|
+
operation: Operation,
|
|
196
|
+
input: []align(16) const u8,
|
|
197
|
+
) void {
|
|
198
|
+
_ = op;
|
|
199
|
+
assert(self.prefetch_input == null);
|
|
200
|
+
assert(self.prefetch_callback == null);
|
|
201
|
+
|
|
202
|
+
if (operation == .register) {
|
|
203
|
+
callback(self);
|
|
204
|
+
return;
|
|
290
205
|
}
|
|
291
|
-
}
|
|
292
|
-
return results_count * @sizeOf(Transfer);
|
|
293
|
-
}
|
|
294
206
|
|
|
295
|
-
|
|
296
|
-
|
|
207
|
+
self.prefetch_input = input;
|
|
208
|
+
self.prefetch_callback = callback;
|
|
297
209
|
|
|
298
|
-
|
|
299
|
-
|
|
210
|
+
// We do this here instead of at the end of commit() to avoid the need to call
|
|
211
|
+
// prefetch() in the StateMachine unit tests below.
|
|
212
|
+
self.forest.grooves.accounts.prefetch_clear();
|
|
213
|
+
self.forest.grooves.transfers.prefetch_clear();
|
|
214
|
+
self.forest.grooves.posted.prefetch_clear();
|
|
300
215
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
216
|
+
return switch (operation) {
|
|
217
|
+
.reserved, .root, .register => unreachable,
|
|
218
|
+
.create_accounts => {
|
|
219
|
+
self.prefetch_create_accounts(mem.bytesAsSlice(Account, input));
|
|
220
|
+
},
|
|
221
|
+
.create_transfers => {
|
|
222
|
+
self.prefetch_create_transfers(mem.bytesAsSlice(Transfer, input));
|
|
223
|
+
},
|
|
224
|
+
.lookup_accounts => {
|
|
225
|
+
self.prefetch_lookup_accounts(mem.bytesAsSlice(u128, input));
|
|
226
|
+
},
|
|
227
|
+
.lookup_transfers => {
|
|
228
|
+
self.prefetch_lookup_transfers(mem.bytesAsSlice(u128, input));
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
}
|
|
304
232
|
|
|
305
|
-
|
|
306
|
-
|
|
233
|
+
fn prefetch_finish(self: *StateMachine) void {
|
|
234
|
+
assert(self.prefetch_input != null);
|
|
235
|
+
const callback = self.prefetch_callback.?;
|
|
236
|
+
self.prefetch_input = null;
|
|
237
|
+
self.prefetch_callback = null;
|
|
238
|
+
callback(self);
|
|
307
239
|
}
|
|
308
240
|
|
|
309
|
-
|
|
310
|
-
|
|
241
|
+
fn prefetch_create_accounts(self: *StateMachine, accounts: []const Account) void {
|
|
242
|
+
for (accounts) |*a| {
|
|
243
|
+
self.forest.grooves.accounts.prefetch_enqueue(a.id);
|
|
244
|
+
}
|
|
245
|
+
self.forest.grooves.accounts.prefetch(
|
|
246
|
+
prefetch_create_accounts_callback,
|
|
247
|
+
&self.prefetch_accounts_context,
|
|
248
|
+
);
|
|
249
|
+
}
|
|
311
250
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
if (a.credits_exceed_debits(0)) return .exceeds_debits;
|
|
251
|
+
fn prefetch_create_accounts_callback(completion: *AccountsGroove.PrefetchContext) void {
|
|
252
|
+
const self = @fieldParentPtr(StateMachine, "prefetch_accounts_context", completion);
|
|
315
253
|
|
|
316
|
-
|
|
254
|
+
self.prefetch_finish();
|
|
255
|
+
}
|
|
317
256
|
|
|
318
|
-
self
|
|
257
|
+
fn prefetch_create_transfers(self: *StateMachine, transfers: []const Transfer) void {
|
|
258
|
+
for (transfers) |*t| {
|
|
259
|
+
self.forest.grooves.transfers.prefetch_enqueue(t.id);
|
|
319
260
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
261
|
+
if (t.flags.post_pending_transfer or t.flags.void_pending_transfer) {
|
|
262
|
+
self.forest.grooves.transfers.prefetch_enqueue(t.pending_id);
|
|
263
|
+
// This prefetch isn't run yet, but enqueue it here as well to save an extra
|
|
264
|
+
// iteration over transfers.
|
|
265
|
+
self.forest.grooves.posted.prefetch_enqueue(t.pending_id);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
323
268
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
269
|
+
self.forest.grooves.transfers.prefetch(
|
|
270
|
+
prefetch_create_transfers_callback_transfers,
|
|
271
|
+
&self.prefetch_transfers_context,
|
|
272
|
+
);
|
|
273
|
+
}
|
|
327
274
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
if (@bitCast(u16, a.flags) != @bitCast(u16, e.flags)) return .exists_with_different_flags;
|
|
331
|
-
if (a.user_data != e.user_data) return .exists_with_different_user_data;
|
|
332
|
-
assert(zeroed_48_bytes(a.reserved) and zeroed_48_bytes(e.reserved));
|
|
333
|
-
if (a.ledger != e.ledger) return .exists_with_different_ledger;
|
|
334
|
-
if (a.code != e.code) return .exists_with_different_code;
|
|
335
|
-
if (a.debits_pending != e.debits_pending) return .exists_with_different_debits_pending;
|
|
336
|
-
if (a.debits_posted != e.debits_posted) return .exists_with_different_debits_posted;
|
|
337
|
-
if (a.credits_pending != e.credits_pending) return .exists_with_different_credits_pending;
|
|
338
|
-
if (a.credits_posted != e.credits_posted) return .exists_with_different_credits_posted;
|
|
339
|
-
return .exists;
|
|
340
|
-
}
|
|
275
|
+
fn prefetch_create_transfers_callback_transfers(completion: *TransfersGroove.PrefetchContext) void {
|
|
276
|
+
const self = @fieldParentPtr(StateMachine, "prefetch_transfers_context", completion);
|
|
341
277
|
|
|
342
|
-
|
|
343
|
-
|
|
278
|
+
const transfers = mem.bytesAsSlice(Event(.create_transfers), self.prefetch_input.?);
|
|
279
|
+
for (transfers) |*t| {
|
|
280
|
+
if (t.flags.post_pending_transfer or t.flags.void_pending_transfer) {
|
|
281
|
+
if (self.forest.grooves.transfers.get(t.pending_id)) |p| {
|
|
282
|
+
self.forest.grooves.accounts.prefetch_enqueue(p.debit_account_id);
|
|
283
|
+
self.forest.grooves.accounts.prefetch_enqueue(p.credit_account_id);
|
|
284
|
+
}
|
|
285
|
+
} else {
|
|
286
|
+
self.forest.grooves.accounts.prefetch_enqueue(t.debit_account_id);
|
|
287
|
+
self.forest.grooves.accounts.prefetch_enqueue(t.credit_account_id);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
344
290
|
|
|
345
|
-
|
|
346
|
-
|
|
291
|
+
self.forest.grooves.accounts.prefetch(
|
|
292
|
+
prefetch_create_transfers_callback_accounts,
|
|
293
|
+
&self.prefetch_accounts_context,
|
|
294
|
+
);
|
|
295
|
+
}
|
|
347
296
|
|
|
348
|
-
|
|
297
|
+
fn prefetch_create_transfers_callback_accounts(completion: *AccountsGroove.PrefetchContext) void {
|
|
298
|
+
const self = @fieldParentPtr(StateMachine, "prefetch_accounts_context", completion);
|
|
349
299
|
|
|
350
|
-
|
|
351
|
-
|
|
300
|
+
self.forest.grooves.posted.prefetch(
|
|
301
|
+
prefetch_create_transfers_callback_posted,
|
|
302
|
+
&self.prefetch_posted_context,
|
|
303
|
+
);
|
|
352
304
|
}
|
|
353
305
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
if (t.credit_account_id == t.debit_account_id) return .accounts_must_be_different;
|
|
306
|
+
fn prefetch_create_transfers_callback_posted(completion: *PostedGroove.PrefetchContext) void {
|
|
307
|
+
const self = @fieldParentPtr(StateMachine, "prefetch_posted_context", completion);
|
|
357
308
|
|
|
358
|
-
|
|
359
|
-
if (t.flags.pending) {
|
|
360
|
-
// Otherwise, reserved amounts may never be released.
|
|
361
|
-
if (t.timeout == 0) return .pending_transfer_must_timeout;
|
|
362
|
-
} else {
|
|
363
|
-
if (t.timeout != 0) return .timeout_reserved_for_pending_transfer;
|
|
309
|
+
self.prefetch_finish();
|
|
364
310
|
}
|
|
365
311
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
const dr = self.get_account(t.debit_account_id) orelse return .debit_account_not_found;
|
|
376
|
-
const cr = self.get_account(t.credit_account_id) orelse return .credit_account_not_found;
|
|
377
|
-
assert(dr.id == t.debit_account_id);
|
|
378
|
-
assert(cr.id == t.credit_account_id);
|
|
379
|
-
assert(t.timestamp > dr.timestamp);
|
|
380
|
-
assert(t.timestamp > cr.timestamp);
|
|
381
|
-
|
|
382
|
-
if (dr.ledger != cr.ledger) return .accounts_must_have_the_same_ledger;
|
|
383
|
-
if (t.ledger != dr.ledger) return .transfer_must_have_the_same_ledger_as_accounts;
|
|
384
|
-
|
|
385
|
-
// If the transfer already exists, then it must not influence the overflow or limit checks.
|
|
386
|
-
if (self.get_transfer(t.id)) |e| return create_transfer_exists(t, e);
|
|
387
|
-
|
|
388
|
-
if (t.flags.pending) {
|
|
389
|
-
if (sum_overflows(t.amount, dr.debits_pending)) return .overflows_debits_pending;
|
|
390
|
-
if (sum_overflows(t.amount, cr.credits_pending)) return .overflows_credits_pending;
|
|
312
|
+
fn prefetch_lookup_accounts(self: *StateMachine, ids: []const u128) void {
|
|
313
|
+
for (ids) |id| {
|
|
314
|
+
self.forest.grooves.accounts.prefetch_enqueue(id);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
self.forest.grooves.accounts.prefetch(
|
|
318
|
+
prefetch_lookup_accounts_callback,
|
|
319
|
+
&self.prefetch_accounts_context,
|
|
320
|
+
);
|
|
391
321
|
}
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
322
|
+
|
|
323
|
+
fn prefetch_lookup_accounts_callback(completion: *AccountsGroove.PrefetchContext) void {
|
|
324
|
+
const self = @fieldParentPtr(StateMachine, "prefetch_accounts_context", completion);
|
|
325
|
+
|
|
326
|
+
self.prefetch_finish();
|
|
397
327
|
}
|
|
398
|
-
|
|
399
|
-
|
|
328
|
+
|
|
329
|
+
fn prefetch_lookup_transfers(self: *StateMachine, ids: []const u128) void {
|
|
330
|
+
for (ids) |id| {
|
|
331
|
+
self.forest.grooves.transfers.prefetch_enqueue(id);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
self.forest.grooves.transfers.prefetch(
|
|
335
|
+
prefetch_lookup_transfers_callback,
|
|
336
|
+
&self.prefetch_transfers_context,
|
|
337
|
+
);
|
|
400
338
|
}
|
|
401
339
|
|
|
402
|
-
|
|
403
|
-
|
|
340
|
+
fn prefetch_lookup_transfers_callback(completion: *TransfersGroove.PrefetchContext) void {
|
|
341
|
+
const self = @fieldParentPtr(StateMachine, "prefetch_transfers_context", completion);
|
|
404
342
|
|
|
405
|
-
|
|
343
|
+
self.prefetch_finish();
|
|
344
|
+
}
|
|
406
345
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
346
|
+
pub fn commit(
|
|
347
|
+
self: *StateMachine,
|
|
348
|
+
client: u128,
|
|
349
|
+
op: u64,
|
|
350
|
+
operation: Operation,
|
|
351
|
+
input: []const u8,
|
|
352
|
+
output: []u8,
|
|
353
|
+
) usize {
|
|
354
|
+
_ = client;
|
|
355
|
+
_ = op;
|
|
356
|
+
|
|
357
|
+
const result = switch (operation) {
|
|
358
|
+
.root => unreachable,
|
|
359
|
+
.register => 0,
|
|
360
|
+
.create_accounts => self.execute(.create_accounts, input, output),
|
|
361
|
+
.create_transfers => self.execute(.create_transfers, input, output),
|
|
362
|
+
.lookup_accounts => self.execute_lookup_accounts(input, output),
|
|
363
|
+
.lookup_transfers => self.execute_lookup_transfers(input, output),
|
|
364
|
+
else => unreachable,
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
return result;
|
|
413
368
|
}
|
|
414
369
|
|
|
415
|
-
self
|
|
416
|
-
|
|
417
|
-
|
|
370
|
+
pub fn tick(self: *StateMachine) void {
|
|
371
|
+
self.forest.tick();
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
pub fn compact(self: *StateMachine, callback: fn (*StateMachine) void, op: u64) void {
|
|
375
|
+
assert(self.compact_callback == null);
|
|
376
|
+
assert(self.checkpoint_callback == null);
|
|
418
377
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
return self.post_or_void_pending_transfer_rollback(t);
|
|
378
|
+
self.compact_callback = callback;
|
|
379
|
+
self.forest.compact(compact_finish, op);
|
|
422
380
|
}
|
|
423
381
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
if (t.flags.pending) {
|
|
430
|
-
dr.debits_pending -= t.amount;
|
|
431
|
-
cr.credits_pending -= t.amount;
|
|
432
|
-
} else {
|
|
433
|
-
dr.debits_posted -= t.amount;
|
|
434
|
-
cr.credits_posted -= t.amount;
|
|
382
|
+
fn compact_finish(forest: *Forest) void {
|
|
383
|
+
const self = @fieldParentPtr(StateMachine, "forest", forest);
|
|
384
|
+
const callback = self.compact_callback.?;
|
|
385
|
+
self.compact_callback = null;
|
|
386
|
+
callback(self);
|
|
435
387
|
}
|
|
436
|
-
assert(self.transfers.remove(t.id));
|
|
437
|
-
}
|
|
438
388
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
389
|
+
pub fn checkpoint(self: *StateMachine, callback: fn (*StateMachine) void) void {
|
|
390
|
+
assert(self.compact_callback == null);
|
|
391
|
+
assert(self.checkpoint_callback == null);
|
|
392
|
+
|
|
393
|
+
self.checkpoint_callback = callback;
|
|
394
|
+
self.forest.checkpoint(checkpoint_finish);
|
|
445
395
|
}
|
|
446
|
-
|
|
447
|
-
|
|
396
|
+
|
|
397
|
+
fn checkpoint_finish(forest: *Forest) void {
|
|
398
|
+
const self = @fieldParentPtr(StateMachine, "forest", forest);
|
|
399
|
+
const callback = self.checkpoint_callback.?;
|
|
400
|
+
self.checkpoint_callback = null;
|
|
401
|
+
callback(self);
|
|
448
402
|
}
|
|
449
|
-
if (t.user_data != e.user_data) return .exists_with_different_user_data;
|
|
450
|
-
assert(t.reserved == 0 and e.reserved == 0);
|
|
451
|
-
assert(t.pending_id == 0 and e.pending_id == 0); // We know that the flags are the same.
|
|
452
|
-
if (t.timeout != e.timeout) return .exists_with_different_timeout;
|
|
453
|
-
assert(t.ledger == e.ledger); // If the accounts are the same, the ledger must be the same.
|
|
454
|
-
if (t.code != e.code) return .exists_with_different_code;
|
|
455
|
-
if (t.amount != e.amount) return .exists_with_different_amount;
|
|
456
|
-
return .exists;
|
|
457
|
-
}
|
|
458
403
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
404
|
+
fn execute(
|
|
405
|
+
self: *StateMachine,
|
|
406
|
+
comptime operation: Operation,
|
|
407
|
+
input: []const u8,
|
|
408
|
+
output: []u8,
|
|
409
|
+
) usize {
|
|
410
|
+
comptime assert(operation != .lookup_accounts and operation != .lookup_transfers);
|
|
411
|
+
|
|
412
|
+
const events = mem.bytesAsSlice(Event(operation), input);
|
|
413
|
+
var results = mem.bytesAsSlice(Result(operation), output);
|
|
414
|
+
var count: usize = 0;
|
|
415
|
+
|
|
416
|
+
var chain: ?usize = null;
|
|
417
|
+
var chain_broken = false;
|
|
418
|
+
|
|
419
|
+
for (events) |*event, index| {
|
|
420
|
+
if (event.flags.linked and chain == null) {
|
|
421
|
+
chain = index;
|
|
422
|
+
assert(chain_broken == false);
|
|
423
|
+
}
|
|
424
|
+
const result = if (chain_broken) .linked_event_failed else switch (operation) {
|
|
425
|
+
.create_accounts => self.create_account(event),
|
|
426
|
+
.create_transfers => self.create_transfer(event),
|
|
427
|
+
else => unreachable,
|
|
428
|
+
};
|
|
429
|
+
log.debug("{s} {}/{}: {}: {}", .{
|
|
430
|
+
@tagName(operation),
|
|
431
|
+
index + 1,
|
|
432
|
+
events.len,
|
|
433
|
+
result,
|
|
434
|
+
event,
|
|
435
|
+
});
|
|
436
|
+
if (result != .ok) {
|
|
437
|
+
if (chain) |chain_start_index| {
|
|
438
|
+
if (!chain_broken) {
|
|
439
|
+
chain_broken = true;
|
|
440
|
+
// Rollback events in LIFO order, excluding this event that broke the chain:
|
|
441
|
+
self.rollback(operation, input, chain_start_index, index);
|
|
442
|
+
// Add errors for rolled back events in FIFO order:
|
|
443
|
+
var chain_index = chain_start_index;
|
|
444
|
+
while (chain_index < index) : (chain_index += 1) {
|
|
445
|
+
results[count] = .{
|
|
446
|
+
.index = @intCast(u32, chain_index),
|
|
447
|
+
.result = .linked_event_failed,
|
|
448
|
+
};
|
|
449
|
+
count += 1;
|
|
450
|
+
}
|
|
451
|
+
} else {
|
|
452
|
+
assert(result == .linked_event_failed);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
results[count] = .{ .index = @intCast(u32, index), .result = result };
|
|
456
|
+
count += 1;
|
|
457
|
+
}
|
|
458
|
+
if (!event.flags.linked and chain != null) {
|
|
459
|
+
chain = null;
|
|
460
|
+
chain_broken = false;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
// TODO client.zig: Validate that batch chains are always well-formed and closed.
|
|
464
|
+
// This is programming error and we should raise an exception for this in the client ASAP.
|
|
465
|
+
assert(chain == null);
|
|
466
|
+
assert(chain_broken == false);
|
|
465
467
|
|
|
466
|
-
|
|
467
|
-
return .cannot_post_and_void_pending_transfer;
|
|
468
|
+
return @sizeOf(Result(operation)) * count;
|
|
468
469
|
}
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
470
|
+
|
|
471
|
+
fn rollback(
|
|
472
|
+
self: *StateMachine,
|
|
473
|
+
comptime operation: Operation,
|
|
474
|
+
input: []const u8,
|
|
475
|
+
chain_start_index: usize,
|
|
476
|
+
chain_error_index: usize,
|
|
477
|
+
) void {
|
|
478
|
+
const events = mem.bytesAsSlice(Event(operation), input);
|
|
479
|
+
|
|
480
|
+
// We commit events in FIFO order.
|
|
481
|
+
// We must therefore rollback events in LIFO order with a reverse loop.
|
|
482
|
+
// We do not rollback `self.commit_timestamp` to ensure that subsequent events are
|
|
483
|
+
// timestamped correctly.
|
|
484
|
+
var index = chain_error_index;
|
|
485
|
+
while (index > chain_start_index) {
|
|
486
|
+
index -= 1;
|
|
487
|
+
|
|
488
|
+
assert(index >= chain_start_index);
|
|
489
|
+
assert(index < chain_error_index);
|
|
490
|
+
const event = events[index];
|
|
491
|
+
assert(event.timestamp <= self.commit_timestamp);
|
|
492
|
+
|
|
493
|
+
switch (operation) {
|
|
494
|
+
.create_accounts => self.create_account_rollback(&event),
|
|
495
|
+
.create_transfers => self.create_transfer_rollback(&event),
|
|
496
|
+
else => unreachable,
|
|
497
|
+
}
|
|
498
|
+
log.debug("{s} {}/{}: rollback(): {}", .{
|
|
499
|
+
@tagName(operation),
|
|
500
|
+
index + 1,
|
|
501
|
+
events.len,
|
|
502
|
+
event,
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
assert(index == chain_start_index);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
fn execute_lookup_accounts(self: *StateMachine, input: []const u8, output: []u8) usize {
|
|
509
|
+
const batch = mem.bytesAsSlice(u128, input);
|
|
510
|
+
const output_len = @divFloor(output.len, @sizeOf(Account)) * @sizeOf(Account);
|
|
511
|
+
const results = mem.bytesAsSlice(Account, output[0..output_len]);
|
|
512
|
+
var results_count: usize = 0;
|
|
513
|
+
for (batch) |id| {
|
|
514
|
+
if (self.get_account(id)) |result| {
|
|
515
|
+
results[results_count] = result.*;
|
|
516
|
+
results_count += 1;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
return results_count * @sizeOf(Account);
|
|
489
520
|
}
|
|
490
|
-
|
|
491
|
-
|
|
521
|
+
|
|
522
|
+
fn execute_lookup_transfers(self: *StateMachine, input: []const u8, output: []u8) usize {
|
|
523
|
+
const batch = mem.bytesAsSlice(u128, input);
|
|
524
|
+
const output_len = @divFloor(output.len, @sizeOf(Transfer)) * @sizeOf(Transfer);
|
|
525
|
+
const results = mem.bytesAsSlice(Transfer, output[0..output_len]);
|
|
526
|
+
var results_count: usize = 0;
|
|
527
|
+
for (batch) |id| {
|
|
528
|
+
if (self.get_transfer(id)) |result| {
|
|
529
|
+
results[results_count] = result.*;
|
|
530
|
+
results_count += 1;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
return results_count * @sizeOf(Transfer);
|
|
492
534
|
}
|
|
493
|
-
// The user_data field is allowed to differ across pending and posting/voiding transfers.
|
|
494
|
-
if (t.ledger > 0 and t.ledger != p.ledger) return .pending_transfer_has_different_ledger;
|
|
495
|
-
if (t.code > 0 and t.code != p.code) return .pending_transfer_has_different_code;
|
|
496
535
|
|
|
497
|
-
|
|
498
|
-
|
|
536
|
+
fn create_account(self: *StateMachine, a: *const Account) CreateAccountResult {
|
|
537
|
+
assert(a.timestamp > self.commit_timestamp);
|
|
538
|
+
|
|
539
|
+
if (a.flags.padding != 0) return .reserved_flag;
|
|
540
|
+
if (!zeroed_48_bytes(a.reserved)) return .reserved_field;
|
|
541
|
+
|
|
542
|
+
if (a.id == 0) return .id_must_not_be_zero;
|
|
543
|
+
if (a.id == math.maxInt(u128)) return .id_must_not_be_int_max;
|
|
544
|
+
if (a.ledger == 0) return .ledger_must_not_be_zero;
|
|
545
|
+
if (a.code == 0) return .code_must_not_be_zero;
|
|
546
|
+
|
|
547
|
+
if (a.flags.debits_must_not_exceed_credits and a.flags.credits_must_not_exceed_debits) {
|
|
548
|
+
return .mutually_exclusive_flags;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
if (sum_overflows(a.debits_pending, a.debits_posted)) return .overflows_debits;
|
|
552
|
+
if (sum_overflows(a.credits_pending, a.credits_posted)) return .overflows_credits;
|
|
553
|
+
|
|
554
|
+
// Opening balances may never exceed limits:
|
|
555
|
+
if (a.debits_exceed_credits(0)) return .exceeds_credits;
|
|
556
|
+
if (a.credits_exceed_debits(0)) return .exceeds_debits;
|
|
557
|
+
|
|
558
|
+
if (self.get_account(a.id)) |e| return create_account_exists(a, e);
|
|
559
|
+
|
|
560
|
+
self.forest.grooves.accounts.put(a);
|
|
499
561
|
|
|
500
|
-
|
|
501
|
-
return .
|
|
562
|
+
self.commit_timestamp = a.timestamp;
|
|
563
|
+
return .ok;
|
|
502
564
|
}
|
|
503
565
|
|
|
504
|
-
|
|
566
|
+
fn create_account_rollback(self: *StateMachine, a: *const Account) void {
|
|
567
|
+
self.forest.grooves.accounts.remove(a.id);
|
|
568
|
+
}
|
|
505
569
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
return .
|
|
570
|
+
fn create_account_exists(a: *const Account, e: *const Account) CreateAccountResult {
|
|
571
|
+
assert(a.id == e.id);
|
|
572
|
+
if (@bitCast(u16, a.flags) != @bitCast(u16, e.flags)) return .exists_with_different_flags;
|
|
573
|
+
if (a.user_data != e.user_data) return .exists_with_different_user_data;
|
|
574
|
+
assert(zeroed_48_bytes(a.reserved) and zeroed_48_bytes(e.reserved));
|
|
575
|
+
if (a.ledger != e.ledger) return .exists_with_different_ledger;
|
|
576
|
+
if (a.code != e.code) return .exists_with_different_code;
|
|
577
|
+
if (a.debits_pending != e.debits_pending) return .exists_with_different_debits_pending;
|
|
578
|
+
if (a.debits_posted != e.debits_posted) return .exists_with_different_debits_posted;
|
|
579
|
+
if (a.credits_pending != e.credits_pending) return .exists_with_different_credits_pending;
|
|
580
|
+
if (a.credits_posted != e.credits_posted) return .exists_with_different_credits_posted;
|
|
581
|
+
return .exists;
|
|
509
582
|
}
|
|
510
583
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
if (p.timestamp + p.timeout <= t.timestamp) return .pending_transfer_expired;
|
|
514
|
-
|
|
515
|
-
self.transfers.putAssumeCapacityNoClobber(t.id, .{
|
|
516
|
-
.id = t.id,
|
|
517
|
-
.debit_account_id = p.debit_account_id,
|
|
518
|
-
.credit_account_id = p.credit_account_id,
|
|
519
|
-
.user_data = if (t.user_data > 0) t.user_data else p.user_data,
|
|
520
|
-
.reserved = p.reserved,
|
|
521
|
-
.ledger = p.ledger,
|
|
522
|
-
.code = p.code,
|
|
523
|
-
.pending_id = t.pending_id,
|
|
524
|
-
.timeout = t.timeout,
|
|
525
|
-
.timestamp = t.timestamp,
|
|
526
|
-
.flags = t.flags,
|
|
527
|
-
.amount = amount,
|
|
528
|
-
});
|
|
584
|
+
fn create_transfer(self: *StateMachine, t: *const Transfer) CreateTransferResult {
|
|
585
|
+
assert(t.timestamp > self.commit_timestamp);
|
|
529
586
|
|
|
530
|
-
|
|
587
|
+
if (t.flags.padding != 0) return .reserved_flag;
|
|
588
|
+
if (t.reserved != 0) return .reserved_field;
|
|
531
589
|
|
|
532
|
-
|
|
533
|
-
|
|
590
|
+
if (t.id == 0) return .id_must_not_be_zero;
|
|
591
|
+
if (t.id == math.maxInt(u128)) return .id_must_not_be_int_max;
|
|
534
592
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
dr.debits_posted += amount;
|
|
539
|
-
cr.credits_posted += amount;
|
|
540
|
-
}
|
|
593
|
+
if (t.flags.post_pending_transfer or t.flags.void_pending_transfer) {
|
|
594
|
+
return self.post_or_void_pending_transfer(t);
|
|
595
|
+
}
|
|
541
596
|
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
597
|
+
if (t.debit_account_id == 0) return .debit_account_id_must_not_be_zero;
|
|
598
|
+
if (t.debit_account_id == math.maxInt(u128)) return .debit_account_id_must_not_be_int_max;
|
|
599
|
+
if (t.credit_account_id == 0) return .credit_account_id_must_not_be_zero;
|
|
600
|
+
if (t.credit_account_id == math.maxInt(u128)) return .credit_account_id_must_not_be_int_max;
|
|
601
|
+
if (t.credit_account_id == t.debit_account_id) return .accounts_must_be_different;
|
|
602
|
+
|
|
603
|
+
if (t.pending_id != 0) return .pending_id_must_be_zero;
|
|
604
|
+
if (t.flags.pending) {
|
|
605
|
+
// Otherwise, reserved amounts may never be released.
|
|
606
|
+
if (t.timeout == 0) return .pending_transfer_must_timeout;
|
|
607
|
+
} else {
|
|
608
|
+
if (t.timeout != 0) return .timeout_reserved_for_pending_transfer;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
if (t.ledger == 0) return .ledger_must_not_be_zero;
|
|
612
|
+
if (t.code == 0) return .code_must_not_be_zero;
|
|
613
|
+
if (t.amount == 0) return .amount_must_not_be_zero;
|
|
614
|
+
|
|
615
|
+
// The etymology of the DR and CR abbreviations for debit/credit is interesting, either:
|
|
616
|
+
// 1. derived from the Latin past participles of debitum/creditum, i.e. debere/credere,
|
|
617
|
+
// 2. standing for debit record and credit record, or
|
|
618
|
+
// 3. relating to debtor and creditor.
|
|
619
|
+
// We use them to distinguish between `cr` (credit account), and `c` (commit).
|
|
620
|
+
const dr = self.get_account(t.debit_account_id) orelse return .debit_account_not_found;
|
|
621
|
+
const cr = self.get_account(t.credit_account_id) orelse return .credit_account_not_found;
|
|
622
|
+
assert(dr.id == t.debit_account_id);
|
|
623
|
+
assert(cr.id == t.credit_account_id);
|
|
624
|
+
assert(t.timestamp > dr.timestamp);
|
|
625
|
+
assert(t.timestamp > cr.timestamp);
|
|
626
|
+
|
|
627
|
+
if (dr.ledger != cr.ledger) return .accounts_must_have_the_same_ledger;
|
|
628
|
+
if (t.ledger != dr.ledger) return .transfer_must_have_the_same_ledger_as_accounts;
|
|
629
|
+
|
|
630
|
+
// If the transfer already exists, then it must not influence the overflow or limit checks.
|
|
631
|
+
if (self.get_transfer(t.id)) |e| return create_transfer_exists(t, e);
|
|
632
|
+
|
|
633
|
+
if (t.flags.pending) {
|
|
634
|
+
if (sum_overflows(t.amount, dr.debits_pending)) return .overflows_debits_pending;
|
|
635
|
+
if (sum_overflows(t.amount, cr.credits_pending)) return .overflows_credits_pending;
|
|
636
|
+
}
|
|
637
|
+
if (sum_overflows(t.amount, dr.debits_posted)) return .overflows_debits_posted;
|
|
638
|
+
if (sum_overflows(t.amount, cr.credits_posted)) return .overflows_credits_posted;
|
|
639
|
+
// We assert that the sum of the pending and posted balances can never overflow:
|
|
640
|
+
if (sum_overflows(t.amount, dr.debits_pending + dr.debits_posted)) {
|
|
641
|
+
return .overflows_debits;
|
|
642
|
+
}
|
|
643
|
+
if (sum_overflows(t.amount, cr.credits_pending + cr.credits_posted)) {
|
|
644
|
+
return .overflows_credits;
|
|
645
|
+
}
|
|
545
646
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
assert(t.flags.post_pending_transfer or t.flags.void_pending_transfer);
|
|
647
|
+
if (dr.debits_exceed_credits(t.amount)) return .exceeds_credits;
|
|
648
|
+
if (cr.credits_exceed_debits(t.amount)) return .exceeds_debits;
|
|
549
649
|
|
|
550
|
-
|
|
551
|
-
const p = self.get_transfer(t.pending_id).?;
|
|
552
|
-
assert(p.id == t.pending_id);
|
|
553
|
-
assert(p.debit_account_id > 0);
|
|
554
|
-
assert(p.credit_account_id > 0);
|
|
650
|
+
self.forest.grooves.transfers.put_no_clobber(t);
|
|
555
651
|
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
652
|
+
var dr_new = dr.*;
|
|
653
|
+
var cr_new = cr.*;
|
|
654
|
+
if (t.flags.pending) {
|
|
655
|
+
dr_new.debits_pending += t.amount;
|
|
656
|
+
cr_new.credits_pending += t.amount;
|
|
657
|
+
} else {
|
|
658
|
+
dr_new.debits_posted += t.amount;
|
|
659
|
+
cr_new.credits_posted += t.amount;
|
|
660
|
+
}
|
|
661
|
+
self.forest.grooves.accounts.put(&dr_new);
|
|
662
|
+
self.forest.grooves.accounts.put(&cr_new);
|
|
560
663
|
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
assert(amount > 0);
|
|
564
|
-
assert(amount <= p.amount);
|
|
565
|
-
dr.debits_posted -= amount;
|
|
566
|
-
cr.credits_posted -= amount;
|
|
664
|
+
self.commit_timestamp = t.timestamp;
|
|
665
|
+
return .ok;
|
|
567
666
|
}
|
|
568
|
-
dr.debits_pending += p.amount;
|
|
569
|
-
cr.credits_pending += p.amount;
|
|
570
667
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
668
|
+
fn create_transfer_rollback(self: *StateMachine, t: *const Transfer) void {
|
|
669
|
+
if (t.flags.post_pending_transfer or t.flags.void_pending_transfer) {
|
|
670
|
+
return self.post_or_void_pending_transfer_rollback(t);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
var dr = self.get_account(t.debit_account_id).?.*;
|
|
674
|
+
var cr = self.get_account(t.credit_account_id).?.*;
|
|
675
|
+
assert(dr.id == t.debit_account_id);
|
|
676
|
+
assert(cr.id == t.credit_account_id);
|
|
677
|
+
|
|
678
|
+
if (t.flags.pending) {
|
|
679
|
+
dr.debits_pending -= t.amount;
|
|
680
|
+
cr.credits_pending -= t.amount;
|
|
681
|
+
} else {
|
|
682
|
+
dr.debits_posted -= t.amount;
|
|
683
|
+
cr.credits_posted -= t.amount;
|
|
684
|
+
}
|
|
685
|
+
self.forest.grooves.accounts.put(&dr);
|
|
686
|
+
self.forest.grooves.accounts.put(&cr);
|
|
574
687
|
|
|
575
|
-
|
|
576
|
-
t: *const Transfer,
|
|
577
|
-
e: *const Transfer,
|
|
578
|
-
p: *const Transfer,
|
|
579
|
-
) CreateTransferResult {
|
|
580
|
-
assert(p.flags.pending);
|
|
581
|
-
assert(t.pending_id == p.id);
|
|
582
|
-
assert(t.id != p.id);
|
|
583
|
-
assert(t.id == e.id);
|
|
584
|
-
|
|
585
|
-
// Do not assume that `e` is necessarily a posting or voiding transfer.
|
|
586
|
-
if (@bitCast(u16, t.flags) != @bitCast(u16, e.flags)) {
|
|
587
|
-
return .exists_with_different_flags;
|
|
688
|
+
self.forest.grooves.transfers.remove(t.id);
|
|
588
689
|
}
|
|
589
690
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
assert(e.code == p.code);
|
|
601
|
-
assert(e.timestamp > p.timestamp);
|
|
602
|
-
|
|
603
|
-
assert(t.flags.post_pending_transfer == e.flags.post_pending_transfer);
|
|
604
|
-
assert(t.flags.void_pending_transfer == e.flags.void_pending_transfer);
|
|
605
|
-
assert(t.debit_account_id == 0 or t.debit_account_id == e.debit_account_id);
|
|
606
|
-
assert(t.credit_account_id == 0 or t.credit_account_id == e.credit_account_id);
|
|
607
|
-
assert(t.reserved == 0);
|
|
608
|
-
assert(t.timeout == 0);
|
|
609
|
-
assert(t.ledger == 0 or t.ledger == e.ledger);
|
|
610
|
-
assert(t.code == 0 or t.code == e.code);
|
|
611
|
-
assert(t.timestamp > e.timestamp);
|
|
612
|
-
|
|
613
|
-
if (t.user_data == 0) {
|
|
614
|
-
if (e.user_data != p.user_data) return .exists_with_different_user_data;
|
|
615
|
-
} else {
|
|
691
|
+
fn create_transfer_exists(t: *const Transfer, e: *const Transfer) CreateTransferResult {
|
|
692
|
+
assert(t.id == e.id);
|
|
693
|
+
// The flags change the behavior of the remaining comparisons, so compare the flags first.
|
|
694
|
+
if (@bitCast(u16, t.flags) != @bitCast(u16, e.flags)) return .exists_with_different_flags;
|
|
695
|
+
if (t.debit_account_id != e.debit_account_id) {
|
|
696
|
+
return .exists_with_different_debit_account_id;
|
|
697
|
+
}
|
|
698
|
+
if (t.credit_account_id != e.credit_account_id) {
|
|
699
|
+
return .exists_with_different_credit_account_id;
|
|
700
|
+
}
|
|
616
701
|
if (t.user_data != e.user_data) return .exists_with_different_user_data;
|
|
702
|
+
assert(t.reserved == 0 and e.reserved == 0);
|
|
703
|
+
assert(t.pending_id == 0 and e.pending_id == 0); // We know that the flags are the same.
|
|
704
|
+
if (t.timeout != e.timeout) return .exists_with_different_timeout;
|
|
705
|
+
assert(t.ledger == e.ledger); // If the accounts are the same, the ledger must be the same.
|
|
706
|
+
if (t.code != e.code) return .exists_with_different_code;
|
|
707
|
+
if (t.amount != e.amount) return .exists_with_different_amount;
|
|
708
|
+
return .exists;
|
|
617
709
|
}
|
|
618
710
|
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
711
|
+
fn post_or_void_pending_transfer(self: *StateMachine, t: *const Transfer) CreateTransferResult {
|
|
712
|
+
assert(t.id != 0);
|
|
713
|
+
assert(t.flags.padding == 0);
|
|
714
|
+
assert(t.reserved == 0);
|
|
715
|
+
assert(t.timestamp > self.commit_timestamp);
|
|
716
|
+
assert(t.flags.post_pending_transfer or t.flags.void_pending_transfer);
|
|
717
|
+
|
|
718
|
+
if (t.flags.post_pending_transfer and t.flags.void_pending_transfer) {
|
|
719
|
+
return .cannot_post_and_void_pending_transfer;
|
|
720
|
+
}
|
|
721
|
+
if (t.flags.pending) return .pending_transfer_cannot_post_or_void_another;
|
|
722
|
+
if (t.timeout != 0) return .timeout_reserved_for_pending_transfer;
|
|
723
|
+
|
|
724
|
+
if (t.pending_id == 0) return .pending_id_must_not_be_zero;
|
|
725
|
+
if (t.pending_id == math.maxInt(u128)) return .pending_id_must_not_be_int_max;
|
|
726
|
+
if (t.pending_id == t.id) return .pending_id_must_be_different;
|
|
727
|
+
|
|
728
|
+
const p = self.get_transfer(t.pending_id) orelse return .pending_transfer_not_found;
|
|
729
|
+
assert(p.id == t.pending_id);
|
|
730
|
+
if (!p.flags.pending) return .pending_transfer_not_pending;
|
|
731
|
+
|
|
732
|
+
const dr = self.get_account(p.debit_account_id).?;
|
|
733
|
+
const cr = self.get_account(p.credit_account_id).?;
|
|
734
|
+
assert(dr.id == p.debit_account_id);
|
|
735
|
+
assert(cr.id == p.credit_account_id);
|
|
736
|
+
assert(p.timestamp > dr.timestamp);
|
|
737
|
+
assert(p.timestamp > cr.timestamp);
|
|
738
|
+
assert(p.amount > 0);
|
|
739
|
+
|
|
740
|
+
if (t.debit_account_id > 0 and t.debit_account_id != p.debit_account_id) {
|
|
741
|
+
return .pending_transfer_has_different_debit_account_id;
|
|
742
|
+
}
|
|
743
|
+
if (t.credit_account_id > 0 and t.credit_account_id != p.credit_account_id) {
|
|
744
|
+
return .pending_transfer_has_different_credit_account_id;
|
|
745
|
+
}
|
|
746
|
+
// The user_data field is allowed to differ across pending and posting/voiding transfers.
|
|
747
|
+
if (t.ledger > 0 and t.ledger != p.ledger) return .pending_transfer_has_different_ledger;
|
|
748
|
+
if (t.code > 0 and t.code != p.code) return .pending_transfer_has_different_code;
|
|
749
|
+
|
|
750
|
+
const amount = if (t.amount > 0) t.amount else p.amount;
|
|
751
|
+
if (amount > p.amount) return .exceeds_pending_transfer_amount;
|
|
752
|
+
|
|
753
|
+
if (t.flags.void_pending_transfer and amount < p.amount) {
|
|
754
|
+
return .pending_transfer_has_different_amount;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
if (self.get_transfer(t.id)) |e| return post_or_void_pending_transfer_exists(t, e, p);
|
|
758
|
+
|
|
759
|
+
if (self.get_posted(t.pending_id)) |posted| {
|
|
760
|
+
if (posted) return .pending_transfer_already_posted;
|
|
761
|
+
return .pending_transfer_already_voided;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
assert(p.timestamp < t.timestamp);
|
|
765
|
+
assert(p.timeout > 0);
|
|
766
|
+
if (p.timestamp + p.timeout <= t.timestamp) return .pending_transfer_expired;
|
|
767
|
+
|
|
768
|
+
self.forest.grooves.transfers.put_no_clobber(&Transfer{
|
|
769
|
+
.id = t.id,
|
|
770
|
+
.debit_account_id = p.debit_account_id,
|
|
771
|
+
.credit_account_id = p.credit_account_id,
|
|
772
|
+
.user_data = if (t.user_data > 0) t.user_data else p.user_data,
|
|
773
|
+
.reserved = p.reserved,
|
|
774
|
+
.ledger = p.ledger,
|
|
775
|
+
.code = p.code,
|
|
776
|
+
.pending_id = t.pending_id,
|
|
777
|
+
.timeout = t.timeout,
|
|
778
|
+
.timestamp = t.timestamp,
|
|
779
|
+
.flags = t.flags,
|
|
780
|
+
.amount = amount,
|
|
781
|
+
});
|
|
782
|
+
|
|
783
|
+
self.forest.grooves.posted.put_no_clobber(t.pending_id, t.flags.post_pending_transfer);
|
|
784
|
+
|
|
785
|
+
var dr_new = dr.*;
|
|
786
|
+
var cr_new = cr.*;
|
|
787
|
+
|
|
788
|
+
dr_new.debits_pending -= p.amount;
|
|
789
|
+
cr_new.credits_pending -= p.amount;
|
|
790
|
+
|
|
791
|
+
if (t.flags.post_pending_transfer) {
|
|
792
|
+
assert(amount > 0);
|
|
793
|
+
assert(amount <= p.amount);
|
|
794
|
+
dr_new.debits_posted += amount;
|
|
795
|
+
cr_new.credits_posted += amount;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
self.forest.grooves.accounts.put(&dr_new);
|
|
799
|
+
self.forest.grooves.accounts.put(&cr_new);
|
|
800
|
+
|
|
801
|
+
self.commit_timestamp = t.timestamp;
|
|
802
|
+
return .ok;
|
|
623
803
|
}
|
|
624
804
|
|
|
625
|
-
|
|
626
|
-
|
|
805
|
+
fn post_or_void_pending_transfer_rollback(self: *StateMachine, t: *const Transfer) void {
|
|
806
|
+
assert(t.id > 0);
|
|
807
|
+
assert(t.flags.post_pending_transfer or t.flags.void_pending_transfer);
|
|
808
|
+
|
|
809
|
+
assert(t.pending_id > 0);
|
|
810
|
+
const p = self.get_transfer(t.pending_id).?;
|
|
811
|
+
assert(p.id == t.pending_id);
|
|
812
|
+
assert(p.debit_account_id > 0);
|
|
813
|
+
assert(p.credit_account_id > 0);
|
|
814
|
+
|
|
815
|
+
var dr = self.get_account(p.debit_account_id).?.*;
|
|
816
|
+
var cr = self.get_account(p.credit_account_id).?.*;
|
|
817
|
+
assert(dr.id == p.debit_account_id);
|
|
818
|
+
assert(cr.id == p.credit_account_id);
|
|
819
|
+
|
|
820
|
+
if (t.flags.post_pending_transfer) {
|
|
821
|
+
const amount = if (t.amount > 0) t.amount else p.amount;
|
|
822
|
+
assert(amount > 0);
|
|
823
|
+
assert(amount <= p.amount);
|
|
824
|
+
dr.debits_posted -= amount;
|
|
825
|
+
cr.credits_posted -= amount;
|
|
826
|
+
}
|
|
827
|
+
dr.debits_pending += p.amount;
|
|
828
|
+
cr.credits_pending += p.amount;
|
|
627
829
|
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
/// This is intended to lookup an Account and modify balances directly by reference.
|
|
631
|
-
/// This pointer is invalidated if the hash map is resized by another insert, e.g. if we get a
|
|
632
|
-
/// pointer, insert another account without capacity, and then modify this pointer... BOOM!
|
|
633
|
-
/// This is a sharp tool but replaces a lookup, copy and update with a single lookup.
|
|
634
|
-
fn get_account(self: *const StateMachine, id: u128) ?*Account {
|
|
635
|
-
return self.accounts.getPtr(id);
|
|
636
|
-
}
|
|
830
|
+
self.forest.grooves.accounts.put(&dr);
|
|
831
|
+
self.forest.grooves.accounts.put(&cr);
|
|
637
832
|
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
}
|
|
833
|
+
self.forest.grooves.posted.remove(t.pending_id);
|
|
834
|
+
self.forest.grooves.transfers.remove(t.id);
|
|
835
|
+
}
|
|
642
836
|
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
837
|
+
fn post_or_void_pending_transfer_exists(
|
|
838
|
+
t: *const Transfer,
|
|
839
|
+
e: *const Transfer,
|
|
840
|
+
p: *const Transfer,
|
|
841
|
+
) CreateTransferResult {
|
|
842
|
+
assert(p.flags.pending);
|
|
843
|
+
assert(t.pending_id == p.id);
|
|
844
|
+
assert(t.id != p.id);
|
|
845
|
+
assert(t.id == e.id);
|
|
846
|
+
|
|
847
|
+
// Do not assume that `e` is necessarily a posting or voiding transfer.
|
|
848
|
+
if (@bitCast(u16, t.flags) != @bitCast(u16, e.flags)) {
|
|
849
|
+
return .exists_with_different_flags;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
// If `e` posted or voided a different pending transfer, then the accounts will differ.
|
|
853
|
+
if (t.pending_id != e.pending_id) return .exists_with_different_pending_id;
|
|
854
|
+
|
|
855
|
+
assert(e.flags.post_pending_transfer or e.flags.void_pending_transfer);
|
|
856
|
+
assert(e.debit_account_id == p.debit_account_id);
|
|
857
|
+
assert(e.credit_account_id == p.credit_account_id);
|
|
858
|
+
assert(e.reserved == 0);
|
|
859
|
+
assert(e.pending_id == p.id);
|
|
860
|
+
assert(e.timeout == 0);
|
|
861
|
+
assert(e.ledger == p.ledger);
|
|
862
|
+
assert(e.code == p.code);
|
|
863
|
+
assert(e.timestamp > p.timestamp);
|
|
864
|
+
|
|
865
|
+
assert(t.flags.post_pending_transfer == e.flags.post_pending_transfer);
|
|
866
|
+
assert(t.flags.void_pending_transfer == e.flags.void_pending_transfer);
|
|
867
|
+
assert(t.debit_account_id == 0 or t.debit_account_id == e.debit_account_id);
|
|
868
|
+
assert(t.credit_account_id == 0 or t.credit_account_id == e.credit_account_id);
|
|
869
|
+
assert(t.reserved == 0);
|
|
870
|
+
assert(t.timeout == 0);
|
|
871
|
+
assert(t.ledger == 0 or t.ledger == e.ledger);
|
|
872
|
+
assert(t.code == 0 or t.code == e.code);
|
|
873
|
+
assert(t.timestamp > e.timestamp);
|
|
874
|
+
|
|
875
|
+
if (t.user_data == 0) {
|
|
876
|
+
if (e.user_data != p.user_data) return .exists_with_different_user_data;
|
|
877
|
+
} else {
|
|
878
|
+
if (t.user_data != e.user_data) return .exists_with_different_user_data;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
if (t.amount == 0) {
|
|
882
|
+
if (e.amount != p.amount) return .exists_with_different_amount;
|
|
883
|
+
} else {
|
|
884
|
+
if (t.amount != e.amount) return .exists_with_different_amount;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
return .exists;
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
fn get_account(self: *const StateMachine, id: u128) ?*const Account {
|
|
891
|
+
return self.forest.grooves.accounts.get(id);
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
fn get_transfer(self: *const StateMachine, id: u128) ?*const Transfer {
|
|
895
|
+
return self.forest.grooves.transfers.get(id);
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
/// Returns whether a pending transfer, if it exists, has already been posted or voided.
|
|
899
|
+
fn get_posted(self: *const StateMachine, pending_id: u128) ?bool {
|
|
900
|
+
return self.forest.grooves.posted.get(pending_id);
|
|
901
|
+
}
|
|
902
|
+
};
|
|
903
|
+
}
|
|
648
904
|
|
|
649
905
|
fn sum_overflows(a: u64, b: u64) bool {
|
|
650
906
|
var c: u64 = undefined;
|
|
@@ -701,6 +957,62 @@ test "sum_overflows" {
|
|
|
701
957
|
try expectEqual(true, sum_overflows(math.maxInt(u64), math.maxInt(u64)));
|
|
702
958
|
}
|
|
703
959
|
|
|
960
|
+
const TestContext = struct {
|
|
961
|
+
const Storage = @import("test/storage.zig").Storage;
|
|
962
|
+
const MessagePool = @import("message_pool.zig").MessagePool;
|
|
963
|
+
const SuperBlock = @import("vsr/superblock.zig").SuperBlockType(Storage);
|
|
964
|
+
const Grid = @import("lsm/grid.zig").GridType(Storage);
|
|
965
|
+
const StateMachine = StateMachineType(Storage);
|
|
966
|
+
|
|
967
|
+
storage: Storage,
|
|
968
|
+
message_pool: MessagePool,
|
|
969
|
+
superblock: SuperBlock,
|
|
970
|
+
grid: Grid,
|
|
971
|
+
state_machine: StateMachine,
|
|
972
|
+
|
|
973
|
+
fn init(ctx: *TestContext, allocator: mem.Allocator, options: StateMachine.Options) !void {
|
|
974
|
+
ctx.storage = try Storage.init(
|
|
975
|
+
allocator,
|
|
976
|
+
4096,
|
|
977
|
+
.{
|
|
978
|
+
.seed = 0,
|
|
979
|
+
.read_latency_min = 0,
|
|
980
|
+
.read_latency_mean = 0,
|
|
981
|
+
.write_latency_min = 0,
|
|
982
|
+
.write_latency_mean = 0,
|
|
983
|
+
.read_fault_probability = 0,
|
|
984
|
+
.write_fault_probability = 0,
|
|
985
|
+
},
|
|
986
|
+
0,
|
|
987
|
+
.{
|
|
988
|
+
.first_offset = 0,
|
|
989
|
+
.period = 0,
|
|
990
|
+
},
|
|
991
|
+
);
|
|
992
|
+
errdefer ctx.storage.deinit(allocator);
|
|
993
|
+
|
|
994
|
+
ctx.message_pool = .{ .free_list = null };
|
|
995
|
+
|
|
996
|
+
ctx.superblock = try SuperBlock.init(allocator, &ctx.storage, &ctx.message_pool);
|
|
997
|
+
errdefer ctx.superblock.deinit(allocator);
|
|
998
|
+
|
|
999
|
+
ctx.grid = try Grid.init(allocator, &ctx.superblock);
|
|
1000
|
+
errdefer ctx.grid.deinit(allocator);
|
|
1001
|
+
|
|
1002
|
+
ctx.state_machine = try StateMachine.init(allocator, &ctx.grid, options);
|
|
1003
|
+
errdefer ctx.state_machine.deinit(allocator);
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
fn deinit(ctx: *TestContext, allocator: mem.Allocator) void {
|
|
1007
|
+
ctx.storage.deinit(allocator);
|
|
1008
|
+
ctx.superblock.deinit(allocator);
|
|
1009
|
+
ctx.grid.deinit(allocator);
|
|
1010
|
+
ctx.state_machine.deinit(allocator);
|
|
1011
|
+
ctx.message_pool.deinit(allocator);
|
|
1012
|
+
ctx.* = undefined;
|
|
1013
|
+
}
|
|
1014
|
+
};
|
|
1015
|
+
|
|
704
1016
|
test "create/lookup/rollback accounts" {
|
|
705
1017
|
const Vector = struct { result: CreateAccountResult, object: Account };
|
|
706
1018
|
|
|
@@ -778,6 +1090,25 @@ test "create/lookup/rollback accounts" {
|
|
|
778
1090
|
.timestamp = 2,
|
|
779
1091
|
}),
|
|
780
1092
|
},
|
|
1093
|
+
.{
|
|
1094
|
+
.result = .id_must_not_be_int_max,
|
|
1095
|
+
.object = mem.zeroInit(Account, .{
|
|
1096
|
+
.id = math.maxInt(u128),
|
|
1097
|
+
.user_data = 0,
|
|
1098
|
+
.ledger = 0,
|
|
1099
|
+
.code = 0,
|
|
1100
|
+
.flags = .{
|
|
1101
|
+
.padding = 0,
|
|
1102
|
+
.debits_must_not_exceed_credits = true,
|
|
1103
|
+
.credits_must_not_exceed_debits = true,
|
|
1104
|
+
},
|
|
1105
|
+
.debits_pending = math.maxInt(u64),
|
|
1106
|
+
.debits_posted = math.maxInt(u64),
|
|
1107
|
+
.credits_pending = math.maxInt(u64),
|
|
1108
|
+
.credits_posted = math.maxInt(u64),
|
|
1109
|
+
.timestamp = 2,
|
|
1110
|
+
}),
|
|
1111
|
+
},
|
|
781
1112
|
.{
|
|
782
1113
|
.result = .ledger_must_not_be_zero,
|
|
783
1114
|
.object = mem.zeroInit(Account, .{
|
|
@@ -1032,10 +1363,18 @@ test "create/lookup/rollback accounts" {
|
|
|
1032
1363
|
},
|
|
1033
1364
|
};
|
|
1034
1365
|
|
|
1035
|
-
var
|
|
1036
|
-
|
|
1366
|
+
var context: TestContext = undefined;
|
|
1367
|
+
try context.init(testing.allocator, .{
|
|
1368
|
+
.lsm_forest_node_count = 1,
|
|
1369
|
+
.cache_size_accounts = vectors.len,
|
|
1370
|
+
.cache_size_transfers = 0,
|
|
1371
|
+
.cache_size_posted = 0,
|
|
1372
|
+
});
|
|
1373
|
+
defer context.deinit(testing.allocator);
|
|
1037
1374
|
|
|
1038
|
-
|
|
1375
|
+
const state_machine = &context.state_machine;
|
|
1376
|
+
|
|
1377
|
+
for (vectors) |*vector, i| {
|
|
1039
1378
|
const result = state_machine.create_account(&vector.object);
|
|
1040
1379
|
expectEqual(vector.result, result) catch |err| {
|
|
1041
1380
|
print_test_vector(i, vector.result, result, vector.object, err);
|
|
@@ -1090,21 +1429,24 @@ test "linked accounts" {
|
|
|
1090
1429
|
mem.zeroInit(Account, .{ .id = 4, .code = 1, .ledger = 1 }),
|
|
1091
1430
|
};
|
|
1092
1431
|
|
|
1093
|
-
var
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1432
|
+
var context: TestContext = undefined;
|
|
1433
|
+
try context.init(testing.allocator, .{
|
|
1434
|
+
.lsm_forest_node_count = 1,
|
|
1435
|
+
.cache_size_accounts = accounts_max,
|
|
1436
|
+
.cache_size_transfers = transfers_max,
|
|
1437
|
+
.cache_size_posted = transfers_pending_max,
|
|
1438
|
+
});
|
|
1439
|
+
defer context.deinit(testing.allocator);
|
|
1440
|
+
|
|
1441
|
+
const state_machine = &context.state_machine;
|
|
1100
1442
|
|
|
1101
1443
|
const input = mem.asBytes(&accounts);
|
|
1102
1444
|
|
|
1103
1445
|
const output = try testing.allocator.alloc(u8, 4096);
|
|
1104
1446
|
defer testing.allocator.free(output);
|
|
1105
1447
|
|
|
1106
|
-
state_machine.prepare(
|
|
1107
|
-
const size = state_machine.commit(0, .create_accounts, input, output);
|
|
1448
|
+
_ = state_machine.prepare(.create_accounts, input);
|
|
1449
|
+
const size = state_machine.commit(0, 0, .create_accounts, input, output);
|
|
1108
1450
|
const results = mem.bytesAsSlice(CreateAccountsResult, output[0..size]);
|
|
1109
1451
|
|
|
1110
1452
|
try expectEqualSlices(
|
|
@@ -1127,7 +1469,6 @@ test "linked accounts" {
|
|
|
1127
1469
|
try expectEqual(accounts[8], state_machine.get_account(accounts[8].id).?.*);
|
|
1128
1470
|
try expectEqual(accounts[11], state_machine.get_account(accounts[11].id).?.*);
|
|
1129
1471
|
try expectEqual(accounts[12], state_machine.get_account(accounts[12].id).?.*);
|
|
1130
|
-
try expectEqual(@as(u32, 5), state_machine.accounts.count());
|
|
1131
1472
|
|
|
1132
1473
|
// TODO How can we test that events were in fact rolled back in LIFO order?
|
|
1133
1474
|
// All our rollback handlers appear to be commutative.
|
|
@@ -1176,16 +1517,24 @@ test "create/lookup/rollback transfers" {
|
|
|
1176
1517
|
}),
|
|
1177
1518
|
};
|
|
1178
1519
|
|
|
1179
|
-
var
|
|
1180
|
-
|
|
1520
|
+
var context: TestContext = undefined;
|
|
1521
|
+
try context.init(testing.allocator, .{
|
|
1522
|
+
.lsm_forest_node_count = 1,
|
|
1523
|
+
.cache_size_accounts = accounts.len,
|
|
1524
|
+
.cache_size_transfers = 1,
|
|
1525
|
+
.cache_size_posted = 0,
|
|
1526
|
+
});
|
|
1527
|
+
defer context.deinit(testing.allocator);
|
|
1528
|
+
|
|
1529
|
+
const state_machine = &context.state_machine;
|
|
1181
1530
|
|
|
1182
1531
|
const input = mem.asBytes(&accounts);
|
|
1183
1532
|
|
|
1184
1533
|
const output = try testing.allocator.alloc(u8, 4096);
|
|
1185
1534
|
defer testing.allocator.free(output);
|
|
1186
1535
|
|
|
1187
|
-
state_machine.prepare(
|
|
1188
|
-
const size = state_machine.commit(0, .create_accounts, input, output);
|
|
1536
|
+
_ = state_machine.prepare(.create_accounts, input);
|
|
1537
|
+
const size = state_machine.commit(0, 0, .create_accounts, input, output);
|
|
1189
1538
|
|
|
1190
1539
|
const errors = mem.bytesAsSlice(CreateAccountsResult, output[0..size]);
|
|
1191
1540
|
try expect(errors.len == 0);
|
|
@@ -1245,6 +1594,21 @@ test "create/lookup/rollback transfers" {
|
|
|
1245
1594
|
.timestamp = timestamp,
|
|
1246
1595
|
}),
|
|
1247
1596
|
},
|
|
1597
|
+
.{
|
|
1598
|
+
.result = .id_must_not_be_int_max,
|
|
1599
|
+
.object = mem.zeroInit(Transfer, .{
|
|
1600
|
+
.id = math.maxInt(u128),
|
|
1601
|
+
.debit_account_id = 0,
|
|
1602
|
+
.credit_account_id = 0,
|
|
1603
|
+
.pending_id = 1,
|
|
1604
|
+
.timeout = 0,
|
|
1605
|
+
.ledger = 0,
|
|
1606
|
+
.code = 0,
|
|
1607
|
+
.flags = .{ .pending = true },
|
|
1608
|
+
.amount = 0,
|
|
1609
|
+
.timestamp = timestamp,
|
|
1610
|
+
}),
|
|
1611
|
+
},
|
|
1248
1612
|
.{
|
|
1249
1613
|
.result = .debit_account_id_must_not_be_zero,
|
|
1250
1614
|
.object = mem.zeroInit(Transfer, .{
|
|
@@ -1260,6 +1624,21 @@ test "create/lookup/rollback transfers" {
|
|
|
1260
1624
|
.timestamp = timestamp,
|
|
1261
1625
|
}),
|
|
1262
1626
|
},
|
|
1627
|
+
.{
|
|
1628
|
+
.result = .debit_account_id_must_not_be_int_max,
|
|
1629
|
+
.object = mem.zeroInit(Transfer, .{
|
|
1630
|
+
.id = 1,
|
|
1631
|
+
.debit_account_id = math.maxInt(u128),
|
|
1632
|
+
.credit_account_id = 0,
|
|
1633
|
+
.pending_id = 1,
|
|
1634
|
+
.timeout = 0,
|
|
1635
|
+
.ledger = 0,
|
|
1636
|
+
.code = 0,
|
|
1637
|
+
.flags = .{ .pending = true },
|
|
1638
|
+
.amount = 0,
|
|
1639
|
+
.timestamp = timestamp,
|
|
1640
|
+
}),
|
|
1641
|
+
},
|
|
1263
1642
|
.{
|
|
1264
1643
|
.result = .credit_account_id_must_not_be_zero,
|
|
1265
1644
|
.object = mem.zeroInit(Transfer, .{
|
|
@@ -1275,6 +1654,21 @@ test "create/lookup/rollback transfers" {
|
|
|
1275
1654
|
.timestamp = timestamp,
|
|
1276
1655
|
}),
|
|
1277
1656
|
},
|
|
1657
|
+
.{
|
|
1658
|
+
.result = .credit_account_id_must_not_be_int_max,
|
|
1659
|
+
.object = mem.zeroInit(Transfer, .{
|
|
1660
|
+
.id = 1,
|
|
1661
|
+
.debit_account_id = 100,
|
|
1662
|
+
.credit_account_id = math.maxInt(u128),
|
|
1663
|
+
.pending_id = 1,
|
|
1664
|
+
.timeout = 0,
|
|
1665
|
+
.ledger = 0,
|
|
1666
|
+
.code = 0,
|
|
1667
|
+
.flags = .{ .pending = true },
|
|
1668
|
+
.amount = 0,
|
|
1669
|
+
.timestamp = timestamp,
|
|
1670
|
+
}),
|
|
1671
|
+
},
|
|
1278
1672
|
.{
|
|
1279
1673
|
.result = .accounts_must_be_different,
|
|
1280
1674
|
.object = mem.zeroInit(Transfer, .{
|
|
@@ -1716,27 +2110,27 @@ test "create/lookup/rollback transfers" {
|
|
|
1716
2110
|
}
|
|
1717
2111
|
|
|
1718
2112
|
// Transfer 3:
|
|
1719
|
-
try test_account_balances(
|
|
1720
|
-
try test_account_balances(
|
|
2113
|
+
try test_account_balances(state_machine, 1, 100 + 123, 200 + 3, 0, 7);
|
|
2114
|
+
try test_account_balances(state_machine, 3, 0, 7, 110 + 123, 210 + 3);
|
|
1721
2115
|
state_machine.create_transfer_rollback(state_machine.get_transfer(3).?);
|
|
1722
|
-
try test_account_balances(
|
|
1723
|
-
try test_account_balances(
|
|
2116
|
+
try test_account_balances(state_machine, 1, 100 + 123, 200, 0, 7);
|
|
2117
|
+
try test_account_balances(state_machine, 3, 0, 7, 110 + 123, 210);
|
|
1724
2118
|
try expect(state_machine.get_transfer(3) == null);
|
|
1725
2119
|
|
|
1726
2120
|
// Transfer 2:
|
|
1727
|
-
try test_account_balances(
|
|
1728
|
-
try test_account_balances(
|
|
2121
|
+
try test_account_balances(state_machine, 1, 100 + 123, 200, 0, 7);
|
|
2122
|
+
try test_account_balances(state_machine, 3, 0, 7, 110 + 123, 210);
|
|
1729
2123
|
state_machine.create_transfer_rollback(state_machine.get_transfer(2).?);
|
|
1730
|
-
try test_account_balances(
|
|
1731
|
-
try test_account_balances(
|
|
2124
|
+
try test_account_balances(state_machine, 1, 100 + 123, 200, 0, 0);
|
|
2125
|
+
try test_account_balances(state_machine, 3, 0, 0, 110 + 123, 210);
|
|
1732
2126
|
try expect(state_machine.get_transfer(2) == null);
|
|
1733
2127
|
|
|
1734
2128
|
// Transfer 1:
|
|
1735
|
-
try test_account_balances(
|
|
1736
|
-
try test_account_balances(
|
|
2129
|
+
try test_account_balances(state_machine, 1, 100 + 123, 200, 0, 0);
|
|
2130
|
+
try test_account_balances(state_machine, 3, 0, 0, 110 + 123, 210);
|
|
1737
2131
|
state_machine.create_transfer_rollback(state_machine.get_transfer(1).?);
|
|
1738
|
-
try test_account_balances(
|
|
1739
|
-
try test_account_balances(
|
|
2132
|
+
try test_account_balances(state_machine, 1, 100, 200, 0, 0);
|
|
2133
|
+
try test_account_balances(state_machine, 3, 0, 0, 110, 210);
|
|
1740
2134
|
try expect(state_machine.get_transfer(1) == null);
|
|
1741
2135
|
|
|
1742
2136
|
for (accounts) |account| {
|
|
@@ -1803,8 +2197,16 @@ test "create/lookup/rollback 2-phase transfers" {
|
|
|
1803
2197
|
}),
|
|
1804
2198
|
};
|
|
1805
2199
|
|
|
1806
|
-
var
|
|
1807
|
-
|
|
2200
|
+
var context: TestContext = undefined;
|
|
2201
|
+
try context.init(testing.allocator, .{
|
|
2202
|
+
.lsm_forest_node_count = 1,
|
|
2203
|
+
.cache_size_accounts = accounts.len,
|
|
2204
|
+
.cache_size_transfers = 100,
|
|
2205
|
+
.cache_size_posted = 1,
|
|
2206
|
+
});
|
|
2207
|
+
defer context.deinit(testing.allocator);
|
|
2208
|
+
|
|
2209
|
+
const state_machine = &context.state_machine;
|
|
1808
2210
|
|
|
1809
2211
|
// Create accounts:
|
|
1810
2212
|
const accounts_input = mem.asBytes(&accounts);
|
|
@@ -1812,9 +2214,9 @@ test "create/lookup/rollback 2-phase transfers" {
|
|
|
1812
2214
|
const accounts_output = try testing.allocator.alloc(u8, 4096);
|
|
1813
2215
|
defer testing.allocator.free(accounts_output);
|
|
1814
2216
|
|
|
1815
|
-
state_machine.prepare(
|
|
2217
|
+
const accounts_timestamp = state_machine.prepare(.create_accounts, accounts_input);
|
|
1816
2218
|
{
|
|
1817
|
-
const size = state_machine.commit(0, .create_accounts, accounts_input, accounts_output);
|
|
2219
|
+
const size = state_machine.commit(0, 0, .create_accounts, accounts_input, accounts_output);
|
|
1818
2220
|
const errors = mem.bytesAsSlice(CreateAccountsResult, accounts_output[0..size]);
|
|
1819
2221
|
try expectEqual(@as(usize, 0), errors.len);
|
|
1820
2222
|
}
|
|
@@ -1828,9 +2230,10 @@ test "create/lookup/rollback 2-phase transfers" {
|
|
|
1828
2230
|
const transfers_output = try testing.allocator.alloc(u8, 4096);
|
|
1829
2231
|
defer testing.allocator.free(transfers_output);
|
|
1830
2232
|
|
|
1831
|
-
state_machine.prepare(
|
|
2233
|
+
const transfers_timestamp = state_machine.prepare(.create_transfers, transfers_input);
|
|
2234
|
+
try testing.expect(transfers_timestamp > accounts_timestamp);
|
|
1832
2235
|
{
|
|
1833
|
-
const size = state_machine.commit(0, .create_transfers, transfers_input, transfers_output);
|
|
2236
|
+
const size = state_machine.commit(0, 1, .create_transfers, transfers_input, transfers_output);
|
|
1834
2237
|
const errors = mem.bytesAsSlice(CreateTransfersResult, transfers_output[0..size]);
|
|
1835
2238
|
try expectEqual(@as(usize, 0), errors.len);
|
|
1836
2239
|
}
|
|
@@ -1839,8 +2242,8 @@ test "create/lookup/rollback 2-phase transfers" {
|
|
|
1839
2242
|
}
|
|
1840
2243
|
|
|
1841
2244
|
// Test balances before posting:
|
|
1842
|
-
try test_account_balances(
|
|
1843
|
-
try test_account_balances(
|
|
2245
|
+
try test_account_balances(state_machine, 1, 52, 15, 0, 0);
|
|
2246
|
+
try test_account_balances(state_machine, 2, 0, 0, 52, 15);
|
|
1844
2247
|
|
|
1845
2248
|
// Post pending transfers:
|
|
1846
2249
|
const Vector = struct { result: CreateTransferResult, object: Transfer };
|
|
@@ -1935,6 +2338,23 @@ test "create/lookup/rollback 2-phase transfers" {
|
|
|
1935
2338
|
.timestamp = timestamp + 1,
|
|
1936
2339
|
}),
|
|
1937
2340
|
},
|
|
2341
|
+
.{
|
|
2342
|
+
.result = .pending_id_must_not_be_int_max,
|
|
2343
|
+
.object = mem.zeroInit(Transfer, .{
|
|
2344
|
+
.id = 101,
|
|
2345
|
+
.debit_account_id = 10,
|
|
2346
|
+
.credit_account_id = 20,
|
|
2347
|
+
.user_data = 30,
|
|
2348
|
+
.pending_id = math.maxInt(u128),
|
|
2349
|
+
.ledger = 60,
|
|
2350
|
+
.code = 70,
|
|
2351
|
+
.flags = .{
|
|
2352
|
+
.void_pending_transfer = true,
|
|
2353
|
+
},
|
|
2354
|
+
.amount = 80,
|
|
2355
|
+
.timestamp = timestamp + 1,
|
|
2356
|
+
}),
|
|
2357
|
+
},
|
|
1938
2358
|
.{
|
|
1939
2359
|
.result = .pending_id_must_be_different,
|
|
1940
2360
|
.object = mem.zeroInit(Transfer, .{
|
|
@@ -2340,48 +2760,48 @@ test "create/lookup/rollback 2-phase transfers" {
|
|
|
2340
2760
|
}
|
|
2341
2761
|
|
|
2342
2762
|
// Balances after posting:
|
|
2343
|
-
try test_account_balances(
|
|
2344
|
-
try test_account_balances(
|
|
2763
|
+
try test_account_balances(state_machine, 1, 15, 35, 0, 0);
|
|
2764
|
+
try test_account_balances(state_machine, 2, 0, 0, 15, 35);
|
|
2345
2765
|
|
|
2346
2766
|
// Rollback posting transfer (different amount):
|
|
2347
2767
|
assert(vectors[0].result == .ok);
|
|
2348
|
-
try test_transfer_rollback(
|
|
2349
|
-
try test_account_balances(
|
|
2350
|
-
try test_account_balances(
|
|
2768
|
+
try test_transfer_rollback(state_machine, &vectors[0].object);
|
|
2769
|
+
try test_account_balances(state_machine, 1, 30, 22, 0, 0);
|
|
2770
|
+
try test_account_balances(state_machine, 2, 0, 0, 30, 22);
|
|
2351
2771
|
|
|
2352
2772
|
// Rollback voiding transfer:
|
|
2353
|
-
assert(vectors[
|
|
2354
|
-
try test_transfer_rollback(
|
|
2355
|
-
try test_account_balances(
|
|
2356
|
-
try test_account_balances(
|
|
2773
|
+
assert(vectors[23].result == .ok);
|
|
2774
|
+
try test_transfer_rollback(state_machine, &vectors[23].object);
|
|
2775
|
+
try test_account_balances(state_machine, 1, 45, 22, 0, 0);
|
|
2776
|
+
try test_account_balances(state_machine, 2, 0, 0, 45, 22);
|
|
2357
2777
|
|
|
2358
2778
|
// Rollback posting transfer (zero amount):
|
|
2359
|
-
assert(vectors[
|
|
2360
|
-
try test_transfer_rollback(
|
|
2361
|
-
try test_account_balances(
|
|
2362
|
-
try test_account_balances(
|
|
2779
|
+
assert(vectors[26].result == .ok);
|
|
2780
|
+
try test_transfer_rollback(state_machine, &vectors[26].object);
|
|
2781
|
+
try test_account_balances(state_machine, 1, 52, 15, 0, 0);
|
|
2782
|
+
try test_account_balances(state_machine, 2, 0, 0, 52, 15);
|
|
2363
2783
|
|
|
2364
2784
|
// Rollback all pending transfers:
|
|
2365
|
-
try test_transfer_rollback(
|
|
2366
|
-
try test_account_balances(
|
|
2367
|
-
try test_account_balances(
|
|
2785
|
+
try test_transfer_rollback(state_machine, &transfers[1]);
|
|
2786
|
+
try test_account_balances(state_machine, 1, 37, 15, 0, 0);
|
|
2787
|
+
try test_account_balances(state_machine, 2, 0, 0, 37, 15);
|
|
2368
2788
|
|
|
2369
|
-
try test_transfer_rollback(
|
|
2370
|
-
try test_account_balances(
|
|
2371
|
-
try test_account_balances(
|
|
2789
|
+
try test_transfer_rollback(state_machine, &transfers[2]);
|
|
2790
|
+
try test_account_balances(state_machine, 1, 22, 15, 0, 0);
|
|
2791
|
+
try test_account_balances(state_machine, 2, 0, 0, 22, 15);
|
|
2372
2792
|
|
|
2373
|
-
try test_transfer_rollback(
|
|
2374
|
-
try test_account_balances(
|
|
2375
|
-
try test_account_balances(
|
|
2793
|
+
try test_transfer_rollback(state_machine, &transfers[3]);
|
|
2794
|
+
try test_account_balances(state_machine, 1, 7, 15, 0, 0);
|
|
2795
|
+
try test_account_balances(state_machine, 2, 0, 0, 7, 15);
|
|
2376
2796
|
|
|
2377
|
-
try test_transfer_rollback(
|
|
2378
|
-
try test_account_balances(
|
|
2379
|
-
try test_account_balances(
|
|
2797
|
+
try test_transfer_rollback(state_machine, &transfers[4]);
|
|
2798
|
+
try test_account_balances(state_machine, 1, 0, 15, 0, 0);
|
|
2799
|
+
try test_account_balances(state_machine, 2, 0, 0, 0, 15);
|
|
2380
2800
|
|
|
2381
2801
|
// Rollback transfer:
|
|
2382
|
-
try test_transfer_rollback(
|
|
2383
|
-
try test_account_balances(
|
|
2384
|
-
try test_account_balances(
|
|
2802
|
+
try test_transfer_rollback(state_machine, &transfers[0]);
|
|
2803
|
+
try test_account_balances(state_machine, 1, 0, 0, 0, 0);
|
|
2804
|
+
try test_account_balances(state_machine, 2, 0, 0, 0, 0);
|
|
2385
2805
|
}
|
|
2386
2806
|
|
|
2387
2807
|
fn print_test_vector(
|
|
@@ -2401,7 +2821,7 @@ fn print_test_vector(
|
|
|
2401
2821
|
}
|
|
2402
2822
|
|
|
2403
2823
|
fn test_account_balances(
|
|
2404
|
-
state_machine: *
|
|
2824
|
+
state_machine: *TestContext.StateMachine,
|
|
2405
2825
|
account_id: u128,
|
|
2406
2826
|
debits_pending: u64,
|
|
2407
2827
|
debits_posted: u64,
|
|
@@ -2415,7 +2835,7 @@ fn test_account_balances(
|
|
|
2415
2835
|
try expectEqual(credits_posted, account.credits_posted);
|
|
2416
2836
|
}
|
|
2417
2837
|
|
|
2418
|
-
fn test_transfer_rollback(state_machine: *StateMachine, transfer: *const Transfer) !void {
|
|
2838
|
+
fn test_transfer_rollback(state_machine: *TestContext.StateMachine, transfer: *const Transfer) !void {
|
|
2419
2839
|
assert(state_machine.get_transfer(transfer.id) != null);
|
|
2420
2840
|
|
|
2421
2841
|
state_machine.create_transfer_rollback(transfer);
|
|
@@ -2478,3 +2898,10 @@ fn test_equal_n_bytes(comptime n: usize) !void {
|
|
|
2478
2898
|
}
|
|
2479
2899
|
try expectEqual(true, routine(a, b));
|
|
2480
2900
|
}
|
|
2901
|
+
|
|
2902
|
+
test "StateMachine: ref all decls" {
|
|
2903
|
+
const Storage = @import("storage.zig").Storage;
|
|
2904
|
+
const StateMachine = StateMachineType(Storage);
|
|
2905
|
+
|
|
2906
|
+
std.testing.refAllDecls(StateMachine);
|
|
2907
|
+
}
|