Skip to content
Bitcoin Roots

v31.1-roots.1 · Commit 2: [PATCH 02/17] feat(policy): preserve Roots parsing and local transaction checks

src/test/fuzz/policy_args.cpp

Before: /dev/null · After: src/test/fuzz/policy_args.cpp

File metadata
new file mode 100644
index 0000000000000000000000000000000000000000..eaa8af6199499f1d66326b6f362d1ff8c711f525

Download original patch · Release notes

Diff rows 1–118 of 118. Line numbers refer to the original before/after files.

+ Added− RemovedUnmarked lines are context.

Scroll the code area horizontally to read long lines. The two line-number columns refer to before and after.

src/test/fuzz/policy_args.cpp ADDED
@@ -0,0 +1,118 @@
1
+ // Copyright (c) 2026 The Bitcoin Roots developers
2
+ // Distributed under the MIT software license, see the accompanying
3
+ // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
+
5
+ #include <common/args.h>
6
+ #include <common/settings.h>
7
+ #include <init.h>
8
+ #include <node/mempool_args.h>
9
+ #include <test/fuzz/FuzzedDataProvider.h>
10
+ #include <test/fuzz/fuzz.h>
11
+ #include <test/util/setup_common.h>
12
+ #include <univalue.h>
13
+ #include <util/check.h>
14
+
15
+ #include <array>
16
+ #include <cstdint>
17
+ #include <string>
18
+ #include <string_view>
19
+ #include <utility>
20
+
21
+ namespace {
22
+ void InitializePolicyArgs()
23
+ {
24
+ static const auto setup = MakeNoLogFileContext<const BasicTestingSetup>();
25
+ }
26
+
27
+ // Decimal accumulation with an explicit bound is independent of ToIntegral.
28
+ bool ValidInteger(std::string_view value, uint64_t maximum)
29
+ {
30
+ bool negative{false};
31
+ if (!value.empty() && (value.front() == '+' || value.front() == '-')) {
32
+ negative = value.front() == '-';
33
+ value.remove_prefix(1);
34
+ }
35
+ if (value.empty()) return false;
36
+ uint64_t number{0};
37
+ for (const unsigned char digit : value) {
38
+ if (digit < '0' || digit > '9') return false;
39
+ const auto numeric_digit{static_cast<uint64_t>(digit - '0')};
40
+ if (number > maximum / 10 || (number == maximum / 10 && numeric_digit > maximum % 10)) return false;
41
+ number = number * 10 + numeric_digit;
42
+ }
43
+ return !negative || number == 0;
44
+ }
45
+ } // namespace
46
+
47
+ FUZZ_TARGET(policy_args, .init = InitializePolicyArgs)
48
+ {
49
+ FuzzedDataProvider provider{buffer.data(), buffer.size()};
50
+ // The complete remaining input is the value: embedded NULs are not discarded.
51
+ const auto value = provider.ConsumeRemainingBytesAsString();
52
+ for (const char* option : {"-subdustfeepenalty", "-rejectparasites", "-acceptunknownwitness",
53
+ "-datacarrierfullcount", "-permitbareanchor", "-rejecttokens", "-permitbarepubkey",
54
+ "-permitbaredatacarrier", "-acceptnonstddatacarrier"}) {
55
+ ArgsManager args;
56
+ args.ForceSetArg(option, value);
57
+ Assert(bool(ValidateRootsPolicyOptions(args)) == (value.empty() || value == "0" || value == "1"));
58
+ }
59
+ for (const auto& [option, maximum] : {
60
+ std::pair<const char*, uint64_t>{"-maxscriptsize", UINT32_MAX},
61
+ {"-bytespersigopstrict", UINT32_MAX}, {"-maxtxlegacysigops", UINT32_MAX},
62
+ {"-minrelaycoinblocks", INT64_MAX}, {"-minrelaymaturity", INT32_MAX},
63
+ {"-blockmaxsize", 4'000'000}, {"-blockprioritysize", INT64_MAX}}) {
64
+ ArgsManager args;
65
+ args.ForceSetArg(option, value);
66
+ Assert(bool(ValidateRootsPolicyOptions(args)) == ValidInteger(value, maximum));
67
+ }
68
+ for (const char* option : {"-datacarriercost", "-mempoolreplacement", "-mempooltruc", "-permitephemeral", "-spkreuse"}) {
69
+ ArgsManager args;
70
+ args.ForceSetArg(option, value);
71
+ const bool valid{bool(ValidateRootsPolicyOptions(args))};
72
+ if (valid) {
73
+ // Neither a valid prefix nor a recognized list token permits trailing junk.
74
+ args.ForceSetArg(option, value + '\xff');
75
+ Assert(!ValidateRootsPolicyOptions(args));
76
+ }
77
+ if (std::string_view{option} == "-datacarriercost") {
78
+ const auto parsed{ParseDataCarrierCost(value)};
79
+ Assert(bool(parsed) == valid);
80
+ if (parsed) {
81
+ // The rounded weight has an exact, canonical quarter-vbyte spelling.
82
+ const auto weight{*parsed};
83
+ const auto canonical{std::to_string(weight / 4) + "." + std::to_string(100 + 25 * (weight % 4)).substr(1)};
84
+ const auto roundtrip{ParseDataCarrierCost(canonical)};
85
+ Assert(roundtrip && *roundtrip == weight);
86
+ }
87
+ }
88
+ }
89
+ const auto dynamic{ParseDustDynamicOpt(value, /*max_fee_estimate_blocks=*/1008)};
90
+ if (dynamic) {
91
+ const auto [target, multiplier]{*dynamic};
92
+ Assert(multiplier > 0);
93
+ Assert(target >= 1 || (target <= -2 && target >= -1008) || target == 0);
94
+ const auto mode{target == 0 ? "off" : std::to_string(multiplier / 1000) + "." +
95
+ std::to_string(1000 + multiplier % 1000).substr(1) + "*" +
96
+ (target < 0 ? "target:" : "mempool:") + std::to_string(target < 0 ? -target : target)};
97
+ const auto roundtrip{ParseDustDynamicOpt(mode, 1008)};
98
+ Assert(roundtrip && *roundtrip == *dynamic);
99
+ }
100
+ UniValue setting;
101
+ if (setting.read(value)) {
102
+ ArgsManager args;
103
+ args.LockSettings([&](common::Settings& settings) { settings.rw_settings["subdustfeepenalty"] = setting; });
104
+ const auto result{ValidateRootsPolicyOptions(args)};
105
+ if (setting.isArray() || setting.isObject()) Assert(!result);
106
+ if (setting.isNull() || setting.isBool()) Assert(result);
107
+ }
108
+ // Check raw negation before ArgsManager's Boolean coercion can hide junk.
109
+ // argv is NUL-terminated; embedded NULs above remain covered through ForceSetArg.
110
+ if (value.find('\0') == value.npos) {
111
+ ArgsManager args;
112
+ SetupServerArgs(args);
113
+ const std::string argument{"-nosubdustfeepenalty=" + value};
114
+ const std::array<const char*, 2> argv{"fuzz", argument.c_str()};
115
+ std::string error;
116
+ Assert(args.ParseParameters(argv.size(), argv.data(), error) == (value.empty() || value == "0" || value == "1"));
117
+ }
118
+ }