-
Notifications
You must be signed in to change notification settings - Fork 38
/
Copy pathfuzz_table.zig
273 lines (228 loc) · 10.4 KB
/
fuzz_table.zig
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
const std = @import("std");
const sig = @import("../sig.zig");
const network = @import("zig-network");
const AtomicBool = std.atomic.Value(bool);
const KeyPair = std.crypto.sign.Ed25519.KeyPair;
const ContactInfo = sig.gossip.data.ContactInfo;
const Pubkey = sig.core.pubkey.Pubkey;
const GossipTable = sig.gossip.GossipTable;
const SignedGossipData = sig.gossip.data.SignedGossipData;
const GossipData = sig.gossip.data.GossipData;
const GossipKey = sig.gossip.data.GossipKey;
const Signature = sig.core.Signature;
const Duration = sig.time.Duration;
const getVariant = sig.utils.types.getVariant;
const TRIM_INTERVAL = Duration.fromSecs(2);
pub fn run(seed: u64, args: *std.process.ArgIterator) !void {
const maybe_max_actions_string = args.next();
const maybe_max_actions = blk: {
if (maybe_max_actions_string) |max_actions_str| {
break :blk try std.fmt.parseInt(usize, max_actions_str, 10);
} else {
break :blk null;
}
};
// setup
var gpa = std.heap.GeneralPurposeAllocator(.{
.safety = true,
}){};
const allocator = gpa.allocator();
defer _ = gpa.deinit();
// NOTE: change to trace for full logs
var std_logger = sig.trace.DirectPrintLogger.init(
allocator,
.debug,
);
const logger = std_logger.logger();
var prng = std.rand.DefaultPrng.init(seed);
const random = prng.random();
// init gossip table
var gossip_table = try GossipTable.init(allocator);
defer gossip_table.deinit();
var put_count: u64 = 0;
var get_count: u64 = 0;
var total_action_count: u64 = 0;
var now: u64 = 100;
var insertion_times = try std.ArrayList(u64).initCapacity(allocator, 100);
defer insertion_times.deinit();
var pubkeys = try std.ArrayList(Pubkey).initCapacity(allocator, 100);
defer pubkeys.deinit();
var keypairs = try std.ArrayList(KeyPair).initCapacity(allocator, 100);
defer keypairs.deinit();
var signatures = std.AutoArrayHashMap(Pubkey, Signature).init(allocator);
defer signatures.deinit();
var keys = try std.ArrayList(GossipKey).initCapacity(allocator, 100);
defer keys.deinit();
var timer = try sig.time.Timer.start();
// get/put a bunch of accounts
while (true) {
if (maybe_max_actions) |max_actions| {
if (total_action_count >= max_actions) {
logger.info().logf("reached max actions: {}", .{max_actions});
break;
}
}
defer {
now += 1;
total_action_count += 1;
}
const action = random.enumValue(enum { put, get });
switch (action) {
.put => {
defer put_count += 1;
const new_keypair = random.boolean();
var data = GossipData{
.ContactInfo = try ContactInfo
.initRandom(allocator, random, Pubkey.initRandom(random), 0, 0, 0),
};
if (new_keypair) {
var seed_buf: [32]u8 = undefined;
random.bytes(&seed_buf);
const keypair = try KeyPair.create(seed_buf);
const pubkey = Pubkey.fromPublicKey(&keypair.public_key);
data.setId(pubkey);
data.wallclockPtr().* = now;
const signed_data = SignedGossipData.initSigned(&keypair, data);
// !
logger.trace().logf("putting pubkey: {}", .{pubkey});
const result = try gossip_table.insert(signed_data, now);
std.debug.assert(result.wasInserted());
try keys.append(GossipKey{ .ContactInfo = pubkey });
try keypairs.append(keypair);
try pubkeys.append(pubkey);
try signatures.put(pubkey, signed_data.signature);
try insertion_times.append(now);
} else {
if (pubkeys.items.len == 0) {
data.deinit(allocator);
continue;
}
const index = random.intRangeAtMost(usize, 0, pubkeys.items.len - 1);
const keypair = keypairs.items[index];
const pubkey = pubkeys.items[index];
data.setId(pubkey);
data.wallclockPtr().* = now;
const should_overwrite = random.boolean();
if (should_overwrite) {
logger.trace().logf("overwriting pubkey: {}", .{pubkey});
data.wallclockPtr().* = now;
} else {
logger.trace().logf("writing old pubkey: {}", .{pubkey});
const old_value = random.boolean();
const other_insertion_time = insertion_times.items[index];
if (old_value) {
// ignored old value
data.wallclockPtr().* = other_insertion_time -| random.intRangeAtMost(u64, 10, 100);
} else {
// will be a duplicate value
data.wallclockPtr().* = other_insertion_time;
}
}
const signed_data = SignedGossipData.initSigned(&keypair, data);
// !
const result = try gossip_table.insert(signed_data, now);
defer {
if (getVariant(result, .OverwroteExistingEntry)) |entry| {
entry.deinit(allocator);
} else if (!result.wasInserted()) {
data.deinit(allocator);
}
}
if (result == .IgnoredOldValue) {
logger.trace().logf("ignored old value: {}", .{pubkey});
std.debug.assert(!should_overwrite);
}
if (result == .IgnoredDuplicateValue) {
logger.trace().logf("duplicate value: {}", .{pubkey});
}
if (result.wasInserted()) {
keys.items[index] = GossipKey{ .ContactInfo = pubkey };
// should over-write the old value
insertion_times.items[index] = now;
try signatures.put(pubkey, signed_data.signature);
}
}
},
.get => {
if (pubkeys.items.len == 0) {
continue;
}
const index = random.intRangeAtMost(usize, 0, pubkeys.items.len - 1);
const pubkey = pubkeys.items[index];
const search_key = keys.items[index];
errdefer {
logger.err().logf("pubkey failed: {} with key: {}", .{ pubkey, search_key });
}
const versioned_data = gossip_table.get(search_key) orelse {
logger.err().logf("failed to get pubkey: {}", .{search_key});
return error.PubkeyNotFound;
};
if (!versioned_data.value.signature.eql(&signatures.get(pubkey).?)) {
logger.err().logf("signature mismatch: {}", .{pubkey});
return error.SignatureMismatch;
}
// via direct method
if (gossip_table.getThreadSafeContactInfo(pubkey) == null) {
logger.err().logf("failed to get contact info: {}", .{pubkey});
return error.ContactInfoNotFound;
}
// via iter
var iter = gossip_table.contactInfoIterator(0);
const found = while (iter.next()) |contact_info| {
if (contact_info.pubkey.equals(&pubkey)) {
break true;
}
} else false;
if (!found) {
logger.err().logf("failed to find pubkey: {}", .{pubkey});
return error.ContactInfoNotFound;
}
get_count += 1;
},
}
if (timer.read().gt(TRIM_INTERVAL)) {
defer timer.reset();
const size = gossip_table.len();
if (random.boolean()) {
// trim the table in half
const max_pubkey_capacity = size / 2;
const pubkeys_droppped_count = try gossip_table.attemptTrim(now, max_pubkey_capacity);
if (pubkeys_droppped_count == 0) continue;
logger.info().logf("op(trim): table size: {} -> {}", .{ size, gossip_table.len() });
} else {
// NOTE: not completely accurate, but good enough
const middle_index = insertion_times.items.len / 2;
const middle_insert_time = insertion_times.items[middle_index];
_ = try gossip_table.removeOldLabels(middle_insert_time, 0);
logger.info().logf("op(remove-old-labels): table size: {} -> {}", .{ size, gossip_table.len() });
}
// reset the pubkey list
const available_keys = gossip_table.pubkey_to_values.keys();
var index: u64 = 0;
while (index < pubkeys.items.len) {
const pubkey = pubkeys.items[index];
const still_exists = for (available_keys) |*key| {
if (key.equals(&pubkey)) {
break true;
}
} else false;
if (!still_exists) {
const pk_removed = pubkeys.swapRemove(index);
_ = insertion_times.swapRemove(index);
_ = signatures.swapRemove(pubkey);
_ = keys.swapRemove(index);
std.debug.assert(pk_removed.equals(&pubkey));
} else {
index += 1;
}
}
logger.info().logf("put: {}, get: {}", .{ put_count, get_count });
put_count = 0;
get_count = 0;
if (maybe_max_actions) |max_actions| {
const percent_int = (total_action_count * 100) / max_actions;
logger.info().logf("total actions: {} / {} ({}%)", .{ total_action_count, max_actions, percent_int });
}
}
}
}