v31.1-roots.1 · Commit 1: [PATCH 01/17] build(roots): establish v31 portability foundation
doc/feature-contracts/policy-parsing.md
Before: /dev/null · After: doc/feature-contracts/policy-parsing.md
File metadata
new file mode 100644 index 0000000000000000000000000000000000000000..c646bfdb4e044ea02e58efdfbcd61a9eae1795dd
Download original patch · Release notes
Diff rows 1–249 of 249. 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.
doc/feature-contracts/policy-parsing.md
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
# Roots policy input compatibility
|
|
2
|
+
|
|
3
|
+
**v31 candidate status: deferred.** This preserved contract describes the
|
|
4
|
+
accepted 30.3 source, including its documented gaps; the foundation does not
|
|
5
|
+
implement its Roots controls or RPC fields. Source code/test paths below refer
|
|
6
|
+
to `10d772eb7f4b00b5193428a0dde4c06558d6c297`, not current candidate behavior.
|
|
7
|
+
[The port review](../roots-v31-port-review.md) records ownership and current gates.
|
|
8
|
+
|
|
9
|
+
**Contract ID: policy-parsing.** Scope: startup input for the Roots-added local
|
|
10
|
+
policy controls listed below. This contract supplements `runtime-config` and the
|
|
11
|
+
policy contracts in [the catalog](../roots-features.md). It does not change
|
|
12
|
+
admission rules, block validity, or defaults. Scoped startup validation enforces
|
|
13
|
+
the decided grammar; the baseline column records the former permissive behavior
|
|
14
|
+
and explains migration.
|
|
15
|
+
|
|
16
|
+
## Scope, defaults and ranges
|
|
17
|
+
|
|
18
|
+
| Options (without leading `-`) | Default | Accepted effective value |
|
|
19
|
+
| --- | --- | --- |
|
|
20
|
+
| `subdustfeepenalty`, `rejectparasites`, `acceptunknownwitness`, `datacarrierfullcount`, `permitbareanchor` | `1` | Boolean below |
|
|
21
|
+
| `rejecttokens`, `permitbarepubkey`, `permitbaredatacarrier`, `acceptnonstddatacarrier` | `0` | Boolean below |
|
|
22
|
+
| `maxscriptsize` | `1650` bytes | Integer, 0 through 4,294,967,295 |
|
|
23
|
+
| `bytespersigopstrict` | `20` bytes | Integer, 0 through 4,294,967,295 |
|
|
24
|
+
| `maxtxlegacysigops` | `2500` sigops | Integer, 0 through 4,294,967,295 |
|
|
25
|
+
| `minrelaycoinblocks` | `0` coin blocks | Integer, 0 through 9,223,372,036,854,775,807 |
|
|
26
|
+
| `minrelaymaturity` | `0` blocks | Integer, 0 through 2,147,483,647 |
|
|
27
|
+
| `blockmaxsize` | Unset: 4,000,000 serialized-byte cap, with 1,200,000 default weight cap | Integer, 0 through 4,000,000; retain existing template clamping |
|
|
28
|
+
| `blockprioritysize` | `100000` bytes | Integer, 0 through 9,223,372,036,854,775,807 |
|
|
29
|
+
| `datacarriercost` | `1` vbyte/data byte | Fixed point, 0 through 1,073,741,823.75; round up to quarters of a vbyte |
|
|
30
|
+
| `dustdynamic` | `off` | Existing structured grammar below |
|
|
31
|
+
| `mempoolreplacement` | `fee,-optin` (full RBF) | Mode/list below |
|
|
32
|
+
| `mempooltruc` | `enforce` | Mode/list below |
|
|
33
|
+
| `permitephemeral` | anchor allowed, dust/send disallowed | Mode/list below |
|
|
34
|
+
| `spkreuse` | `allow` | `allow`, `conflict`, or Boolean alias |
|
|
35
|
+
|
|
36
|
+
Inherited Core options, including `bytespersigop`, `datacarrier`,
|
|
37
|
+
`datacarriersize`, `permitbaremultisig`, `mempoolfullrbf`, `blockmaxweight`,
|
|
38
|
+
`blockreservedweight`, and fee amounts, keep their existing parsers. Changing
|
|
39
|
+
Roots defaults for an inherited option does not bring its parser into this scope.
|
|
40
|
+
Peer, wallet, RPC and runtime options are outside this contract.
|
|
41
|
+
|
|
42
|
+
**Baseline help conflict:** `blockmaxsize` help advertised `300000` as its
|
|
43
|
+
default, but initialized template options leave the serialized cap at 4,000,000
|
|
44
|
+
when it is unset. Omitting both size and weight options retains the conservative
|
|
45
|
+
1,200,000 weight cap. Explicitly setting only `blockmaxsize` makes weight
|
|
46
|
+
unrestricted up to the consensus maximum; setting only `blockmaxweight` leaves
|
|
47
|
+
the serialized cap at 4,000,000. Preserve these effective defaults and option
|
|
48
|
+
interactions. Updated help reports the actual unset serialized cap.
|
|
49
|
+
|
|
50
|
+
## Grammar and migration decisions
|
|
51
|
+
|
|
52
|
+
A Boolean accepts exactly `0`, `1`, or the empty string. The empty string,
|
|
53
|
+
including an elided CLI flag, means true. Keep `-noOPTION`, `-noOPTION=1`, and
|
|
54
|
+
`-noOPTION=0` (the last means true); explicit empty negation means false. A
|
|
55
|
+
negated value must itself be empty, `0`, or `1` before conversion, so malformed
|
|
56
|
+
negations cannot hide behind the inherited coercion. Other numbers, signs,
|
|
57
|
+
leading zeros, whitespace, textual `true`/`false`, fractions, and suffixes are
|
|
58
|
+
errors. JSON Boolean settings retain their corresponding Boolean meaning.
|
|
59
|
+
|
|
60
|
+
Integers consume the complete ASCII decimal string, with an optional `+` or `-`
|
|
61
|
+
sign and one or more digits. Leading zeros are allowed. The parsed value must
|
|
62
|
+
be nonnegative and fit the range above; `-0` remains zero. Empty/elided values,
|
|
63
|
+
whitespace, fractions, suffixes, and overflow are errors. The upper bounds
|
|
64
|
+
prevent narrowing/wrapping or saturating an out-of-range value; coin-block and
|
|
65
|
+
priority-size controls retain the nonnegative range of the former signed
|
|
66
|
+
64-bit getter rather than silently expanding it. Existing `-noOPTION` and
|
|
67
|
+
`-noOPTION=1` retain zero; `-noOPTION=0` retains one, except fixed-point cost,
|
|
68
|
+
where its existing string conversion also means a cost of one vbyte. Negated
|
|
69
|
+
values are checked as Booleans before conversion.
|
|
70
|
+
|
|
71
|
+
Carrier cost retains the existing whole-value `ParseFixedPoint` grammar:
|
|
72
|
+
optional minus sign, integer part `0` or a nonzero digit followed by digits,
|
|
73
|
+
optional decimal point followed by at least one digit, and optional `e`/`E`
|
|
74
|
+
exponent with optional sign and at least one digit. The result must be exactly
|
|
75
|
+
representable in hundredths and within the range above. Thus `1.2300`, `1e0`,
|
|
76
|
+
`1e-2` and `-0` remain valid; `+1`, `01`, `.5`, `1.`, `0.001`, whitespace,
|
|
77
|
+
trailing junk and arithmetic overflow remain invalid. There is no new ban on
|
|
78
|
+
exponents or redundant fractional zeros. The existing fixed-point parser's
|
|
79
|
+
bounded mantissa/exponent rules remain authoritative, including for zero.
|
|
80
|
+
|
|
81
|
+
`dustdynamic` retains `off`/`0` or
|
|
82
|
+
`[<multiplier>*]target:<blocks>` / `[<multiplier>*]mempool:<kvB>`.
|
|
83
|
+
The multiplier uses the same fixed-point grammar with thousandth precision,
|
|
84
|
+
scaled range 1 through 2,147,483,647; default multiplier is 3. Target is an
|
|
85
|
+
unsigned decimal integer from 2 through the existing estimator horizon.
|
|
86
|
+
Mempool position is a signed decimal integer from 1 through 2,147,483,647.
|
|
87
|
+
Their existing `ToIntegral` grammar rejects a leading `+` and accepts leading
|
|
88
|
+
zeros. Empty values, extra separators, suffixes and unknown modes are errors.
|
|
89
|
+
`-nodustdynamic` and `-nodustdynamic=1` retain `off`/`0`; the inherited
|
|
90
|
+
double-negative `-nodustdynamic=0` yields `1` and remains an invalid mode.
|
|
91
|
+
Validate the raw negated Boolean value before conversion.
|
|
92
|
+
|
|
93
|
+
Keep these mode/list forms and their existing precedence semantics:
|
|
94
|
+
|
|
95
|
+
- `mempoolreplacement`: Boolean alias, or `fee`, `optin`, `-optin` tokens
|
|
96
|
+
separated by `,` or `+`. `0` disables replacement; empty/`1` enables fee-based
|
|
97
|
+
replacement. Repeated recognized tokens retain their current meaning;
|
|
98
|
+
the last `optin`/`-optin` token wins. Preserve the existing interaction with
|
|
99
|
+
inherited `mempoolfullrbf`, including its contradiction warning.
|
|
100
|
+
- `mempooltruc`: Boolean alias, or `reject`, `accept`, `enforce`, `optin`,
|
|
101
|
+
`-enforce`, `-optin`, `0` tokens separated by `,` or `+`. Empty/`1` means
|
|
102
|
+
enforce. `0`/`reject` means reject; enforce takes priority over accept;
|
|
103
|
+
later assignments to the same flag win. Preserve all recognized aliases.
|
|
104
|
+
- `permitephemeral`: Boolean alias, or `anchor`, `dust`, `send`, `reject`, `0`
|
|
105
|
+
tokens with an optional `-` prefix, separated by `,` or `+`. Empty/`1` enables
|
|
106
|
+
all three exceptions, `0`/`reject` disables them. Preserve existing implied
|
|
107
|
+
flags and last assignment to each flag, including signed recognized tokens.
|
|
108
|
+
- `spkreuse`: `allow`/`1`/empty allows reuse, `conflict`/`0` rejects conflicts.
|
|
109
|
+
|
|
110
|
+
Lists must contain recognized nonempty tokens only; unknown tokens and leading,
|
|
111
|
+
trailing, or doubled separators are errors. Whitespace is not token syntax.
|
|
112
|
+
Booleans used as aliases must consume the entire string; `2` and `1junk` are
|
|
113
|
+
errors. Negation retains each mode's existing Boolean alias behavior.
|
|
114
|
+
|
|
115
|
+
| Input class | Baseline behavior | Decided behavior / migration |
|
|
116
|
+
| --- | --- | --- |
|
|
117
|
+
| Omitted option | Built-in default | Unchanged |
|
|
118
|
+
| Empty/elided Boolean or list | Enables Boolean behavior | Unchanged; use `=0` to disable explicitly |
|
|
119
|
+
| Empty/elided integer | Converts to zero | Error; write `=0` |
|
|
120
|
+
| `+1`, `01`, `-0` integer | Numeric conversion | Retain within range |
|
|
121
|
+
| `+1`, `01`, `-1`, `2` Boolean | Nonzero becomes true | Error; write `=1` or `=0` |
|
|
122
|
+
| CLI value containing outer whitespace | Numeric getters trim; carrier cost rejects | Error for scoped values; remove whitespace |
|
|
123
|
+
| Config whitespace around `=`/value | Config reader trims before parsing | Retain config formatting; parser sees trimmed value |
|
|
124
|
+
| `12junk`, `1.5` integer | Prefix becomes 12 or 1 | Error; write the intended whole integer |
|
|
125
|
+
| `junk`, `false`, `0.5` Boolean | Converts to false | Error; write `=0` |
|
|
126
|
+
| Signed overflow | Getter saturates, then may narrow/wrap | Error; choose an in-range value |
|
|
127
|
+
| Negative nonzero integer | May wrap in unsigned destinations | Error; use zero or a supported positive value |
|
|
128
|
+
| Unknown list token / empty token | Ignored, possibly changing the selected mode | Error; remove or correct token |
|
|
129
|
+
| Unknown `spkreuse` string | Selects conflict rejection | Error; write `conflict` |
|
|
130
|
+
| Exact fractional/exponent carrier cost | Whole-value parse | Unchanged; not a partial parse |
|
|
131
|
+
| Malformed negated value | Converts permissively before policy sees it | Error; use an explicit `0` or `1` |
|
|
132
|
+
|
|
133
|
+
These incompatibilities replace silent guesses with actionable startup errors.
|
|
134
|
+
They do not authorize narrowing valid documented policy semantics or changing
|
|
135
|
+
how accepted values influence transactions or templates.
|
|
136
|
+
|
|
137
|
+
## Sources, precedence and diagnostics
|
|
138
|
+
|
|
139
|
+
Preserve `runtime-config` precedence: forced settings, CLI, read/write config,
|
|
140
|
+
JSON settings, network config section, default config section, built-in default.
|
|
141
|
+
For scalar options the last CLI occurrence and first config occurrence win after
|
|
142
|
+
inherited negation processing. Validate the selected effective value; shadowed
|
|
143
|
+
ordinary values need not cause errors. Raw negations must be checked before the
|
|
144
|
+
argument manager converts them, even when subsequently shadowed. No change to
|
|
145
|
+
how config comments or outer whitespace are removed is intended. Config lines
|
|
146
|
+
without `=` remain syntax errors; CLI elision follows the option grammar above.
|
|
147
|
+
|
|
148
|
+
Invalid scoped effective values must produce a startup error naming the option,
|
|
149
|
+
the offending value, and its accepted grammar/range. Preserve the existing
|
|
150
|
+
`Invalid -datacarriercost value: '…'` diagnostic prefix and the specific
|
|
151
|
+
`dustdynamic` failure context, adding useful bounds without disguising the
|
|
152
|
+
reason. Errors must not report unrelated configuration values or secrets.
|
|
153
|
+
|
|
154
|
+
## Baseline evidence and remaining acceptance gates
|
|
155
|
+
|
|
156
|
+
Characterized at `f66eca4ae466e198fecd66cac22a0033918b12ec` (same production
|
|
157
|
+
parser as source `17e1d484aba2a881be03b8928fba3c0125e027a2`). Paths:
|
|
158
|
+
argument conversion (`src/common/args.cpp`, source reference),
|
|
159
|
+
config reading (`src/common/config.cpp`, source reference),
|
|
160
|
+
merging (`src/common/settings.cpp`, source reference),
|
|
161
|
+
mempool options (`src/node/mempool_args.cpp`, source reference),
|
|
162
|
+
startup options (`src/init.cpp`, source reference),
|
|
163
|
+
template options (`src/node/miner.cpp`, source reference), and
|
|
164
|
+
fixed-point conversion (`src/util/strencodings.cpp`, source reference).
|
|
165
|
+
|
|
166
|
+
Focused baseline evidence: `getarg_tests` (10 cases),
|
|
167
|
+
`feature_config_args.py`, and a 160-case isolated regtest startup matrix exercising
|
|
168
|
+
Boolean/integer/cost classes, list aliases/junk, CLI/config pairs, repeated
|
|
169
|
+
options, section precedence, negation and bounds. Configuration trims outer
|
|
170
|
+
whitespace; CLI does not. The cost cases verify last CLI / first config /
|
|
171
|
+
CLI-over-config / network-over-default selection with invalid shadowed values.
|
|
172
|
+
The matrix produced 127 clean startups/shutdowns, 30 expected startup diagnostics,
|
|
173
|
+
and the three existing runtime aborts described below. All 160 outcomes matched
|
|
174
|
+
the source-derived characterization; an expected rejection is not a test failure.
|
|
175
|
+
|
|
176
|
+
**Existing runtime gap:** non-off `dustdynamic` modes can parse successfully but
|
|
177
|
+
abort startup because the mempool is constructed without its required scheduler.
|
|
178
|
+
Parser acceptance does not prove these modes operate. This contract records that
|
|
179
|
+
baseline failure; repairing scheduler wiring is a separate runtime change.
|
|
180
|
+
|
|
181
|
+
Acceptance: policy_args_tests.cpp (`src/test/policy_args_tests.cpp`, source reference)
|
|
182
|
+
`boolean_values`, `integer_boundaries`, `carrier_cost_compatibility`, `mode_tokens`,
|
|
183
|
+
`raw_negation_is_scoped`, `defaults_and_mode_semantics`,
|
|
184
|
+
`settings_types_and_precedence`, and `dynamic_dust_grammar_without_runtime_claim`
|
|
185
|
+
cover parser boundaries, exact weight rounding, defaults, types and aliases.
|
|
186
|
+
feature_policy_args.py (`test/functional/feature_policy_args.py`, source reference) `run_test`
|
|
187
|
+
covers isolated CLI/config startup parity, shadowing, negation and unchanged
|
|
188
|
+
inherited Core parsing. Dynamic-dust non-off grammar is unit-tested without
|
|
189
|
+
claiming a working startup. Configuration and affected
|
|
190
|
+
carrier/sub-dust/dust/replacement/TRUC/template policy suites remain regression
|
|
191
|
+
gates.
|
|
192
|
+
Retirement requires an upstream parser with the same grammar, default values,
|
|
193
|
+
precedence, diagnostics and covered policy behavior. No PR number is known;
|
|
194
|
+
a release note must be added when one is assigned.
|
|
195
|
+
|
|
196
|
+
## Scoped fuzz evidence
|
|
197
|
+
|
|
198
|
+
`policy_args` in policy_args.cpp (`src/test/fuzz/policy_args.cpp`, source reference) exercises
|
|
199
|
+
the production option validator, carrier-cost and dynamic-dust parsers, and raw
|
|
200
|
+
Boolean negation. Its independent bounded decimal oracle checks complete integer
|
|
201
|
+
consumption and range limits; Boolean grammar, rejection of appended junk,
|
|
202
|
+
canonical cost/dynamic round trips and JSON setting types have explicit
|
|
203
|
+
invariants. The entire input is a value, retaining embedded NULs; only the argv
|
|
204
|
+
negation check skips NUL-containing values because argv strings cannot carry them.
|
|
205
|
+
Dynamic parsing does not construct a dynamic mempool or establish runtime support.
|
|
206
|
+
|
|
207
|
+
`subdust_fee_penalty` exercises actual admission and entry fee accounting against
|
|
208
|
+
a wide-integer oracle; its supported arithmetic domain and remaining gates are
|
|
209
|
+
in [the sub-dust contract](subdust.md). Both targets use the existing fuzz registry.
|
|
210
|
+
|
|
211
|
+
Use the existing `libfuzzer` preset when its compiler runtime is installed. A
|
|
212
|
+
sanitizer corpus replay can also use the supported standalone fuzz runner:
|
|
213
|
+
|
|
214
|
+
```sh
|
|
215
|
+
cmake -B build_fuzz_policy -G Ninja -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ \
|
|
216
|
+
-DCMAKE_BUILD_TYPE=Debug -DBUILD_FOR_FUZZING=ON -DSANITIZERS=address,undefined \
|
|
217
|
+
-DENABLE_WALLET=OFF -DBUILD_GUI=OFF -DENABLE_IPC=OFF
|
|
218
|
+
cmake --build build_fuzz_policy -j4
|
|
219
|
+
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=halt_on_error=1 FUZZ=policy_args \
|
|
220
|
+
timeout 120 build_fuzz_policy/bin/fuzz /path/to/local/policy_args
|
|
221
|
+
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=halt_on_error=1 FUZZ=subdust_fee_penalty \
|
|
222
|
+
timeout 120 build_fuzz_policy/bin/fuzz /path/to/local/subdust_fee_penalty
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Keep inputs and artifacts outside Git. Capture the exact source revision and dirty
|
|
226
|
+
diff, binary/configuration, generator, PRNG seed, input counts, wall time and exits
|
|
227
|
+
for every campaign. The preservation run at base `b60a6e04` plus the L5 test diff
|
|
228
|
+
used GCC 14 ASan/UBSan, Python PRNG seed `310503`, 52 parser boundary spellings
|
|
229
|
+
plus 5,000 mutations, and 225 arithmetic boundaries plus 1,000 generated cases.
|
|
230
|
+
Both 120-second replay budgets completed: 5,052 parser inputs in 34 seconds and
|
|
231
|
+
1,225 arithmetic inputs in 4 seconds, without sanitizer reports. These are bounded
|
|
232
|
+
smoke results, not proof of exhaustive coverage.
|
|
233
|
+
|
|
234
|
+
Parser seeds include empty/Boolean/signed/overflow integers, exact and rounded
|
|
235
|
+
carrier costs, exponents, embedded NUL and trailing junk, malformed lists, JSON
|
|
236
|
+
types, dynamic multiplier/target bounds and a 4,096-digit value. Arithmetic seeds
|
|
237
|
+
combine rates `0`, `1`, `3000`, `INT64_MAX/2` and `INT64_MAX`; actual fees `0`,
|
|
238
|
+
`1000`, `1000000`; deltas `-MAX_MONEY`, `0`, `MAX_MONEY`; zero-valued P2TR/P2A/
|
|
239
|
+
OP_RETURN, mixed sixteen-output fixtures and sixteen above-default-dust outputs.
|
|
240
|
+
Arithmetic inputs consume integers from the end using `FuzzedDataProvider`:
|
|
241
|
+
rate (8 bytes), fee (3), delta plus `MAX_MONEY` (7), output count minus one (1),
|
|
242
|
+
then each output value (2) and script index (1); integers are little endian and
|
|
243
|
+
the chunks are concatenated in reverse consumption order.
|
|
244
|
+
|
|
245
|
+
The installed Clang 19 lacked compiler-rt/libFuzzer/ASan archives; the compile
|
|
246
|
+
probe failed before testing. GCC sanitizer compilation and execution succeeded.
|
|
247
|
+
The executed campaigns therefore used corpus replay, including generated
|
|
248
|
+
mutations, with no coverage-guided engine. A coverage-guided libFuzzer campaign
|
|
249
|
+
remains an explicit evidence gap, and cannot be claimed from these replay counts.
|