Skip to content

Commit 02fc17e

Browse files
committed
Hoist responsibilities to the fuzzer entrypoint
1 parent 4bef284 commit 02fc17e

File tree

11 files changed

+170
-129
lines changed

11 files changed

+170
-129
lines changed

.circleci/config.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ jobs:
221221
at: workspace
222222
- run:
223223
name: Run Gossip Service Fuzzer
224-
command: workspace/zig-out-release/bin/fuzz gossip_service 19 10000
224+
command: workspace/zig-out-release/bin/fuzz --seed 19 gossip_service 10000
225225

226226
gossip_table_fuzz:
227227
executor: linux-executor
@@ -231,7 +231,7 @@ jobs:
231231
at: workspace
232232
- run:
233233
name: Run Gossip Table Fuzzer
234-
command: workspace/zig-out-release/bin/fuzz gossip_table 19 100000
234+
command: workspace/zig-out-release/bin/fuzz --seed 19 gossip_table 100000
235235

236236
allocators_fuzz:
237237
executor: linux-executor
@@ -241,7 +241,7 @@ jobs:
241241
at: workspace
242242
- run:
243243
name: Run Allocators Fuzzer
244-
command: workspace/zig-out-release/bin/fuzz allocators 19 10000
244+
command: workspace/zig-out-release/bin/fuzz --seed 19 allocators 10000
245245

246246
ledger_fuzz:
247247
executor: linux-executor
@@ -251,7 +251,7 @@ jobs:
251251
at: workspace
252252
- run:
253253
name: Run Ledger Fuzzer
254-
command: workspace/zig-out-release/bin/fuzz ledger 19 10000
254+
command: workspace/zig-out-release/bin/fuzz --seed 19 ledger 10000
255255

256256
solana_conformance:
257257
executor: linux-executor

scripts/kcov_fuzz_allocators.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ kcov \
2323
--include-pattern=src/utils/allocators.zig \
2424
--exclude-pattern=$HOME/.cache \
2525
kcov-output/ \
26-
./zig-out/bin/fuzz allocators 19 50_000
26+
./zig-out/bin/fuzz --seed 19 allocators 50_000
2727

2828
# open report
2929
echo "=> Opening kcov-output/index.html"

scripts/kcov_fuzz_gossip.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ kcov \
2323
--include-pattern=src/gossip/ \
2424
--exclude-pattern=$HOME/.cache \
2525
kcov-output/ \
26-
./zig-out/bin/fuzz gossip_service 19 50_000
26+
./zig-out/bin/fuzz --seed 19 gossip_service 50_000
2727

2828
# open report
2929
echo "=> Opening kcov-output/index.html"

scripts/kcov_fuzz_gossip_table.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ kcov \
2323
--include-pattern=src/gossip/ \
2424
--exclude-pattern=$HOME/.cache \
2525
kcov-output/ \
26-
./zig-out/bin/fuzz gossip_table 19 50_000
26+
./zig-out/bin/fuzz --seed 19 gossip_table 50_000
2727

2828
# open report
2929
echo "=> Opening kcov-output/index.html"

src/accountsdb/fuzz.zig

Lines changed: 9 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ const sig = @import("../sig.zig");
33
const cli = @import("cli");
44

55
const Account = sig.runtime.AccountSharedData;
6-
const ChannelPrintLogger = sig.trace.ChannelPrintLogger;
76
const Pubkey = sig.core.pubkey.Pubkey;
87
const Slot = sig.core.time.Slot;
98

@@ -44,7 +43,7 @@ pub const TrackedAccount = struct {
4443
pub const RunCmd = struct {
4544
max_slots: ?Slot,
4645

47-
pub const parser = cli.Parser(RunCmd, .{
46+
pub const cmd_info: cli.CommandInfo(RunCmd) = .{
4847
.help = .{
4948
.short = "Fuzz accountsdb.",
5049
.long = null,
@@ -59,10 +58,16 @@ pub const RunCmd = struct {
5958
.help = "The number of slots number which, when surpassed, will exit the fuzzer.",
6059
},
6160
},
62-
});
61+
};
6362
};
6463

65-
pub fn run(seed: u64, args: *std.process.ArgIterator) !void {
64+
pub fn run(
65+
allocator: std.mem.Allocator,
66+
logger: sig.trace.Logger("accountsdb.fuzz"),
67+
seed: u64,
68+
fuzz_data_dir: std.fs.Dir,
69+
run_cmd: RunCmd,
70+
) !void {
6671
var prng_state: std.Random.DefaultPrng = .init(seed);
6772
const random = prng_state.random();
6873

@@ -71,41 +76,8 @@ pub fn run(seed: u64, args: *std.process.ArgIterator) !void {
7176
const N_ACCOUNTS_MAX: u64 = 100_000;
7277
const N_ACCOUNTS_PER_SLOT = 10;
7378

74-
var gpa_state: std.heap.DebugAllocator(.{
75-
.safety = true,
76-
}) = .init;
77-
defer _ = gpa_state.deinit();
78-
const allocator = gpa_state.allocator();
79-
80-
const std_logger: *ChannelPrintLogger = try .init(.{
81-
.allocator = allocator,
82-
.max_level = .debug,
83-
.max_buffer = 1 << 20,
84-
}, null);
85-
defer std_logger.deinit();
86-
const logger = std_logger.logger("accountsdb.fuzz");
87-
88-
const run_cmd: RunCmd = cmd: {
89-
var argv_list: std.ArrayListUnmanaged([]const u8) = .empty;
90-
defer argv_list.deinit(allocator);
91-
while (args.next()) |arg| try argv_list.append(allocator, arg);
92-
93-
const stderr = std.io.getStdErr();
94-
const stderr_tty = std.io.tty.detectConfig(stderr);
95-
break :cmd try RunCmd.parser.parse(
96-
allocator,
97-
"fuzz accountsdb",
98-
stderr_tty,
99-
stderr.writer(),
100-
argv_list.items,
101-
) orelse return;
102-
};
103-
10479
const maybe_max_slots = run_cmd.max_slots;
10580

106-
var fuzz_data_dir = try std.fs.cwd().makeOpenPath(sig.FUZZ_DATA_DIR, .{});
107-
defer fuzz_data_dir.close();
108-
10981
const main_dir_name = "main";
11082
var main_accountsdb_dir = try fuzz_data_dir.makeOpenPath(main_dir_name, .{});
11183
defer main_accountsdb_dir.close();

src/accountsdb/fuzz_snapshot.zig

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@ const SnapshotManifest = sig.accounts_db.Manifest;
1515

1616
const MAX_FUZZ_TIME_NS = std.time.ns_per_s * 100_000;
1717

18-
pub fn run(args: *std.process.ArgIterator) !void {
19-
_ = args;
18+
pub fn run() !void {
2019
const seed = std.crypto.random.int(u64);
2120

2221
var gpa: std.heap.DebugAllocator(.{}) = .init;

src/fuzz.zig

Lines changed: 142 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,12 @@
11
const std = @import("std");
22
const sig = @import("sig.zig");
3+
const cli = @import("cli");
34

45
const accountsdb_fuzz = sig.accounts_db.fuzz;
5-
const gossip_fuzz_service = sig.gossip.fuzz_service;
6-
const gossip_fuzz_table = sig.gossip.fuzz_table;
7-
const accountsdb_snapshot_fuzz = sig.accounts_db.fuzz_snapshot;
6+
const gossip_service_fuzz = sig.gossip.fuzz_service;
7+
const gossip_table_fuzz = sig.gossip.fuzz_table;
8+
const snapshot_fuzz = sig.accounts_db.fuzz_snapshot;
89
const ledger_fuzz = sig.ledger.fuzz_ledger;
9-
const ChannelPrintLogger = sig.trace.ChannelPrintLogger;
10-
const Level = sig.trace.Level;
11-
12-
const servePrometheus = sig.prometheus.servePrometheus;
13-
const globalRegistry = sig.prometheus.globalRegistry;
14-
15-
// where seeds are saved (in case of too many logs)
16-
const SEED_FILE_PATH = sig.TEST_DATA_DIR ++ "fuzz_seeds.txt";
1710

1811
// Supported fuzz filters.
1912
// NOTE: changing these enum variants will require a change to the fuzz/kcov in `scripts/`
@@ -22,85 +15,160 @@ pub const FuzzFilter = enum {
2215
snapshot,
2316
gossip_service,
2417
gossip_table,
25-
allocators,
2618
ledger,
19+
allocators,
20+
};
21+
22+
const Cmd = struct {
23+
data_dir: ?[]const u8,
24+
seed: ?u64,
25+
fuzzer: ?union(FuzzFilter) {
26+
accountsdb: accountsdb_fuzz.RunCmd,
27+
snapshot: FuzzerTodo,
28+
gossip_service: FuzzerTodo,
29+
gossip_table: FuzzerTodo,
30+
ledger: FuzzerTodo,
31+
allocators: FuzzerTodo,
32+
},
33+
34+
const FuzzerTodo = struct {
35+
args: []const []const u8,
36+
37+
pub const cmd_info: cli.CommandInfo(FuzzerTodo) = .{
38+
.help = .{
39+
.short = "TODO: implement bespoke CLI integration for this fuzzer.",
40+
.long = null,
41+
},
42+
.sub = .{
43+
.args = .{
44+
.kind = .positional,
45+
.name_override = null,
46+
.alias = .none,
47+
.default_value = &.{},
48+
.config = .string,
49+
.help = "Args to pass to the specified fuzzer.",
50+
},
51+
},
52+
};
53+
};
54+
55+
const parser = cli.Parser(Cmd, .{
56+
.help = .{
57+
.short = "Fuzz a component of the validator.",
58+
.long = null,
59+
},
60+
.sub = .{
61+
.data_dir = .{
62+
.kind = .named,
63+
.name_override = null,
64+
.alias = .none,
65+
.default_value = null,
66+
.config = .string,
67+
.help = "Directory for all fuzzers to store their on-disk data relative to.",
68+
},
69+
.seed = .{
70+
.kind = .named,
71+
.name_override = null,
72+
.alias = .none,
73+
.default_value = null,
74+
.config = {},
75+
.help = "Seed for the PRNG for all random actions taken during fuzzing.",
76+
},
77+
.fuzzer = .{
78+
.accountsdb = accountsdb_fuzz.RunCmd.cmd_info,
79+
.snapshot = FuzzerTodo.cmd_info,
80+
.gossip_service = FuzzerTodo.cmd_info,
81+
.gossip_table = FuzzerTodo.cmd_info,
82+
.ledger = FuzzerTodo.cmd_info,
83+
.allocators = FuzzerTodo.cmd_info,
84+
},
85+
},
86+
});
2787
};
2888

2989
pub fn main() !void {
3090
var gpa_state: std.heap.DebugAllocator(.{}) = .init;
3191
defer _ = gpa_state.deinit();
32-
const allocator = gpa_state.allocator();
92+
const gpa = gpa_state.allocator();
93+
94+
const argv = try std.process.argsAlloc(gpa);
95+
defer std.process.argsFree(gpa, argv);
96+
const args = argv[1..];
97+
98+
const stderr = std.io.getStdErr();
99+
const stderr_tty = std.io.tty.detectConfig(stderr);
100+
const cmd: Cmd = cmd: {
101+
std.debug.lockStdErr();
102+
defer std.debug.unlockStdErr();
103+
break :cmd try Cmd.parser.parse(
104+
gpa,
105+
"fuzz",
106+
stderr_tty,
107+
stderr.writer(),
108+
args,
109+
) orelse return;
110+
};
111+
defer Cmd.parser.free(gpa, cmd);
33112

34-
var std_logger = try ChannelPrintLogger.init(.{
35-
.allocator = std.heap.c_allocator,
36-
.max_level = Level.debug,
113+
const std_logger: *sig.trace.ChannelPrintLogger = try .init(.{
114+
.allocator = gpa,
115+
.max_level = .debug,
37116
.max_buffer = 1 << 20,
38117
}, null);
39118
defer std_logger.deinit();
40-
41119
const logger = std_logger.logger("fuzz");
42120

43-
var cli_args = try std.process.argsWithAllocator(allocator);
44-
defer cli_args.deinit();
45-
46-
const metrics_port: u16 = 12345;
47-
48-
logger.info().logf("metrics port: {d}", .{metrics_port});
49-
const metrics_thread = try std.Thread
50-
// TODO: use the GPA here, the server is just leaking because we're losing the handle
51-
// to it and never deiniting.
52-
.spawn(.{}, servePrometheus, .{ std.heap.c_allocator, globalRegistry(), 12355 });
53-
metrics_thread.detach();
54-
55-
_ = cli_args.skip();
56-
const filter = blk: {
57-
const maybe_filter = cli_args.next();
58-
if (maybe_filter) |filter| {
59-
const parsed_filter = std.meta.stringToEnum(FuzzFilter, filter) orelse {
60-
std.debug.print(
61-
"Unknown filter. Supported values are: {s} ",
62-
.{std.meta.fieldNames(FuzzFilter)},
63-
);
64-
return error.UnknownFilter;
65-
};
66-
std.debug.print("filtering fuzz testing with prefix: {s}\n", .{filter});
67-
break :blk parsed_filter;
68-
} else {
69-
std.debug.print("fuzz filter required: usage: zig build fuzz -- <filter>\n", .{});
70-
return error.NoFilterProvided;
71-
}
72-
};
73-
74-
const seed = blk: {
75-
const maybe_seed = cli_args.next();
76-
if (maybe_seed) |seed_str| {
77-
break :blk try std.fmt.parseInt(u64, seed_str, 10);
78-
} else {
79-
break :blk std.crypto.random.int(u64);
80-
}
121+
const data_dir_name = cmd.data_dir orelse sig.FUZZ_DATA_DIR;
122+
const seed = cmd.seed orelse std.crypto.random.int(u64);
123+
const fuzzer = cmd.fuzzer orelse {
124+
std.debug.print("Missing filter.\n", .{});
125+
return;
81126
};
82127

83-
std.debug.print("using seed: {d}\n", .{seed});
84-
try writeSeedToFile(filter, seed);
85-
86-
switch (filter) {
87-
.accountsdb => try accountsdb_fuzz.run(seed, &cli_args),
88-
.snapshot => try accountsdb_snapshot_fuzz.run(&cli_args),
89-
.gossip_service => try gossip_fuzz_service.run(seed, &cli_args),
90-
.gossip_table => try gossip_fuzz_table.run(seed, &cli_args),
91-
.ledger => try ledger_fuzz.run(seed, &cli_args),
92-
.allocators => try sig.utils.allocators.runFuzzer(seed, &cli_args),
128+
var data_dir = try std.fs.cwd().makeOpenPath(data_dir_name, .{});
129+
defer data_dir.close();
130+
131+
{
132+
std.debug.print("using seed: {d}\n", .{seed});
133+
// where seeds are saved (in case of too many logs)
134+
const seed_file = try data_dir.createFile("fuzz_seeds.txt", .{ .truncate = false });
135+
defer seed_file.close();
136+
try seed_file.seekFromEnd(0);
137+
const now: u64 = @intCast(std.time.timestamp());
138+
try seed_file.writer().print(
139+
"{s}: time: {d}, seed: {d}\n",
140+
.{ @tagName(fuzzer), now, seed },
141+
);
93142
}
94-
}
95143

96-
/// writes the seed to the defined seed file (defined by SEED_FILE_PATH)
97-
pub fn writeSeedToFile(filter: FuzzFilter, seed: u64) !void {
98-
const seed_file = try std.fs.cwd().createFile(SEED_FILE_PATH, .{
99-
.truncate = false,
144+
const metrics_port: u16 = 12345;
145+
logger.info().logf("metrics port: {d}", .{metrics_port});
146+
// TODO: use the GPA here, the server is just leaking because we're losing the handle
147+
// to it and never deiniting.
148+
const metrics_thread: std.Thread = try .spawn(.{}, sig.prometheus.servePrometheus, .{
149+
std.heap.c_allocator,
150+
sig.prometheus.globalRegistry(),
151+
metrics_port,
100152
});
101-
defer seed_file.close();
102-
try seed_file.seekFromEnd(0);
153+
metrics_thread.detach();
103154

104-
const now: u64 = @intCast(std.time.timestamp());
105-
try seed_file.writer().print("{s}: time: {d}, seed: {d}\n", .{ @tagName(filter), now, seed });
155+
var sub_data_dir = try data_dir.makeOpenPath(@tagName(fuzzer), .{});
156+
defer sub_data_dir.close();
157+
158+
switch (fuzzer) {
159+
.accountsdb,
160+
=> |run_cmd| try accountsdb_fuzz.run(
161+
gpa,
162+
.from(logger),
163+
seed,
164+
sub_data_dir,
165+
run_cmd,
166+
),
167+
168+
.snapshot => try snapshot_fuzz.run(),
169+
.gossip_service => |run_cmd| try gossip_service_fuzz.run(seed, run_cmd.args),
170+
.gossip_table => |run_cmd| try gossip_table_fuzz.run(seed, run_cmd.args),
171+
.ledger => |run_cmd| try ledger_fuzz.run(seed, run_cmd.args),
172+
.allocators => |run_cmd| try sig.utils.allocators.runFuzzer(seed, run_cmd.args),
173+
}
106174
}

0 commit comments

Comments
 (0)