Skip to content
Bitcoin Roots

v31.1-roots.1 · Commit 1: [PATCH 01/17] build(roots): establish v31 portability foundation

doc/feature-contracts/subdust.md

Before: /dev/null · After: doc/feature-contracts/subdust.md

File metadata
new file mode 100644
index 0000000000000000000000000000000000000000..28a272bf59d96c985f801dd3d2bf764cbcfad0e5

Download original patch · Release notes

Diff rows 1–195 of 195. 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/subdust.md ADDED
@@ -0,0 +1,195 @@
1
+ # Sub-dust effective-fee contract
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
+ Stable ID: **policy-subdust**. Depends on **policy-dust** and the shared local
10
+ policy/configuration invariants in the [catalog](/doc/roots-features.md).
11
+ This worked section is split out for readability. It describes pinned Roots
12
+ `17e1d484aba2a881be03b8928fba3c0125e027a2`; implementation links are aids for
13
+ that version, not a requirement to keep a particular acceptance function or
14
+ check location.
15
+
16
+ ## Observable behavior and units
17
+
18
+ When enabled, subtract the sum of output dust-threshold deficits from a
19
+ transaction's modified policy fee. Actual input/output values, transaction ID
20
+ and actual fee do not change. Paying enough to cover the deficit may satisfy
21
+ fee checks; it never overrides another standardness, script, replacement,
22
+ package or consensus check.
23
+
24
+ For each output `i`, let `A_i` be its value in integer satoshis, `R` the current
25
+ effective dust relay rate in sat/kvB, and `S_i` its serialized output size in
26
+ bytes, including value and script-length encoding:
27
+
28
+ ```
29
+ T_i = 0 if scriptPubKey is unspendable
30
+ T_i = ceil(R * (S_i + 67) / 1000) for a witness program
31
+ T_i = ceil(R * (S_i + 148) / 1000) otherwise
32
+ D_i = max(0, T_i - A_i)
33
+ P = saturating_sum(D_i)
34
+ F = saturating_add(F_before_penalty, -P)
35
+ ```
36
+
37
+ Here `F_before_penalty` includes any `prioritisetransaction` fee delta; `F` is
38
+ the effective modified fee in satoshis. The 67-byte estimate uses integer
39
+ `107 / 4 = 26` in the discounted spend estimate; it is not 67.75 rounded later.
40
+ The witness estimate also applies to Taproot and future witness programs,
41
+ without assuming a smaller Taproot satisfaction. The ceiling is per output
42
+ threshold, before subtracting its value or accumulating deficits. At rate zero
43
+ all thresholds and the penalty are zero. For supported nonnegative rates there
44
+ is no floating-point arithmetic in this calculation.
45
+
46
+ For an ordinary individually accepted non-TRUC transaction with policy virtual
47
+ size `V`, the relay fee gate requires `F >= ceil(minrelay_sat_per_kvB * V / 1000)`.
48
+ `V` is the node's policy size, including sigop/data-cost adjustments, not
49
+ necessarily the size displayed by a generic raw decoder. Rolling mempool fees
50
+ can require more. Entry modified fees and ancestor/descendant accounting carry
51
+ the penalty into package/replacement/mining fee selection; this is not a
52
+ separate coin transfer or a charge added to a mined transaction's actual fee.
53
+
54
+ Version-specific sources: dust threshold (`src/policy/policy.cpp`, source reference)
55
+ `GetDustThreshold`, fee rounding (`src/policy/feerate.cpp`, source reference) and
56
+ integer evaluation (`src/util/feefrac.h`, source reference), penalty application (`src/validation.cpp`, source reference),
57
+ entry modified fees (`src/kernel/mempool_entry.h`, source reference),
58
+ saturation (`src/util/overflow.h`, source reference) and policy size (`src/txmempool.h`, source reference).
59
+
60
+ ## Worked boundaries
61
+
62
+ At the static default `R = 3000` sat/kvB:
63
+
64
+ | Output | Serialized size + spend estimate | Threshold | Value | Deficit |
65
+ | --- | --- | --- | --- | --- |
66
+ | P2PKH | 34 + 148 bytes | 546 sat | 545 sat | 1 sat |
67
+ | P2WPKH | 31 + 67 bytes | 294 sat | 294 sat | 0 sat |
68
+ | P2TR | 43 + 67 bytes | 330 sat | 0 sat | 330 sat |
69
+ | P2TR | 43 + 67 bytes | 330 sat | 100 sat | 230 sat |
70
+ | Two P2TR outputs | 110 bytes each | 330 sat each | 0 sat each | 660 sat total |
71
+ | OP_RETURN | Unspendable | 0 sat | 0 sat | 0 sat |
72
+
73
+ If the measured policy size is 200 vB and minimum relay rate is 1,000 sat/kvB,
74
+ one 100-sat P2TR output requires an actual fee of at least `200 + 230 = 430`
75
+ sat when no fee delta or other gate applies. A 429-sat fee fails this gate;
76
+ 430 sat reaches equality. This example assumes dust standardness is relaxed
77
+ on regtest, as in the owned test; the default standardness check may reject the
78
+ output first. The 200-vB size illustrates the formula, not the size of every
79
+ fixture. At `R = 1` sat/kvB a P2TR threshold is `ceil(110/1000) = 1` sat.
80
+
81
+ An output exactly at/above its threshold contributes nothing. Unspendable
82
+ outputs contribute nothing. A **zero-valued spendable** output contributes its
83
+ entire threshold; neither zero amount nor P2A classification is an automatic
84
+ penalty exemption. Anchor/ephemeral permission and penalty arithmetic are
85
+ separate checks in this source.
86
+
87
+ ## Bounds, defaults and interactions
88
+
89
+ - `subdustfeepenalty` is a boolean, defaults to true, and is disabled with
90
+ `-subdustfeepenalty=0` (or its equivalent boolean negation). It is not a fee
91
+ amount or multiplier. CLI values override config; ordinary repeated CLI
92
+ scalar values take the last value after negation processing, while config
93
+ takes the first. Full precedence is in **runtime-config** in the catalog.
94
+ The initialized mempool option is startup state, not a mutable RPC setting.
95
+ See option loading (`src/node/mempool_args.cpp`, source reference) and
96
+ boolean conversion (`src/common/args.cpp`, source reference). Accepted Boolean grammar and
97
+ malformed-input diagnostics are specified by [policy-parsing](policy-parsing.md).
98
+ - Disabling removes this deficit subtraction only. It does not disable dust
99
+ standardness or lower relay/mempool minimum fees. `acceptnonstdtxn=1` is
100
+ test-chain-only and does not itself disable the penalty. `dustrelayfee=0`
101
+ produces zero thresholds only if dynamic dust does not raise the effective
102
+ rate. Dynamic dust changes `R` and therefore future admission penalties;
103
+ current source does not reprice every existing entry when that rate changes.
104
+ - Deficits accumulate using signed 64-bit `CAmount` saturation: `P` stops at
105
+ `INT64_MAX`. Subtracting that nonnegative penalty uses saturation down to
106
+ `INT64_MIN` for modified-fee accounting; it must not wrap into a high fee.
107
+ Valid output amounts are nonnegative and checked before this calculation.
108
+ Saturating accumulation does **not** make arbitrarily large configured fee
109
+ rates safe: threshold evaluation requires its result to fit in signed
110
+ 64-bit, and dynamic rate multiplication has its own arithmetic limits.
111
+ Extreme configuration/threshold tests are an open gap.
112
+ - Standard dust/ephemeral checks can reject before or after penalty calculation.
113
+ The normal zero-fee ephemeral requirement remains; permission does not imply
114
+ automatic subtraction bypass. Enforced TRUC can bypass the individual relay
115
+ floor for a package-funded parent, but package/mempool/replacement gates still
116
+ use their own rules. Internal reorg rejection exceptions are not a public
117
+ option to bypass arbitrary validation. Fee-delta, CPFP, RBF/TRUC and priority
118
+ mining interactions require their own acceptance coverage.
119
+ - **Consensus invariant:** a consensus-valid block containing a transaction
120
+ rejected locally for this effective-fee deficit must still be accepted.
121
+ The penalty must not alter block fee/reward calculation or consensus script
122
+ flags. This is a required acceptance case even when local admission fails.
123
+
124
+ ## Acceptance evidence and gaps
125
+
126
+ mempool_subdust_fee_penalty.py (`test/functional/mempool_subdust_fee_penalty.py`, source reference)
127
+ uses regtest with `acceptnonstdtxn=1` to isolate the fee gate. Its exact cases are
128
+ `test_dust_output_increases_required_fee` (zero P2TR output, one below/equal fee),
129
+ `test_partial_dust_proportional_penalty` (100-sat output),
130
+ `test_multiple_dust_outputs_stack` (two zero outputs), `test_penalty_disabled`,
131
+ `test_op_return_not_penalized` and `test_above_dust_not_penalized`.
132
+ mempool_dust.py (`test/functional/mempool_dust.py`, source reference) `test_dust_output` separately
133
+ checks different script thresholds and exact threshold/one below;
134
+ mempool_tests.cpp (`src/test/mempool_tests.cpp`, source reference) `DynamicDustFeePublication`
135
+ checks changing published dust state.
136
+
137
+ The same sub-dust test also owns these interaction acceptance cases:
138
+
139
+ - `test_threshold_and_low_rate_boundaries`: exact threshold/one below, per-output
140
+ ceiling at one sat/kvB, and zero dust rate with the penalty still enabled.
141
+ - `test_anchor_and_standardness`: zero-valued P2A contributes 240 sat at the
142
+ default rate; paying more cannot bypass standard dust/ephemeral checks, and
143
+ disabling the penalty retains ordinary dust rejection.
144
+ - `test_carrier_cost_interaction`: carrier cost `1.01` rounds upward to five
145
+ weight units/byte; exact fee/one below, base versus modified fee and policy
146
+ size, zero cost, and full-count budget enforcement enabled/disabled.
147
+ - `test_fee_delta_and_replacement`: pre-admission and resident fee deltas,
148
+ an RBF rejection despite a higher actual fee, replacement with sufficient
149
+ modified fees, and unchanged actual fees.
150
+ - `test_package_and_truc`: ordinary v2 retains the individual relay floor;
151
+ enforced v3 sponsorship pays the parent's negative modified fee, including
152
+ the exact package minimum/one below and ancestor/descendant accounting.
153
+ `mempooltruc=accept` retains the individual floor. A consensus-valid block
154
+ containing the locally rejected v2 parent and its child is accepted.
155
+ - `test_mining_and_block_acceptance`: explicit valid block containing a locally
156
+ rejected sub-dust transaction; template exclusion/inclusion across its fee
157
+ floor via a one-sat delta; actual transaction fees and coinbase reward remain
158
+ unchanged. Penalty disabling and an enabled priority area have separate
159
+ template inclusion cases.
160
+
161
+ Repeated/config precedence is covered by `feature_policy_args.py` and
162
+ `rpc_effective_policy.py`, as specified in [policy-parsing](policy-parsing.md).
163
+ These cases isolate fees on regtest using nonstandard admission where needed;
164
+ they do not establish permission to bypass standard ephemeral spending rules.
165
+
166
+ The `subdust_fee_penalty` target in
167
+ tx_pool.cpp (`src/test/fuzz/tx_pool.cpp`, source reference) drives production admission with the
168
+ penalty enabled and disabled. An independent wide-integer oracle checks output
169
+ thresholds and expected acceptance, and admitted entry base/modified/ancestor/
170
+ descendant fees. Inputs include arbitrary nonnegative signed-64-bit dust rates,
171
+ up to sixteen small P2TR/P2WPKH/P2A/P2PKH/OP_RETURN outputs plus change, and
172
+ positive/negative fee deltas bounded by `MAX_MONEY`. Seeded cases exercise
173
+ accumulated deficits exceeding `INT64_MAX` and subtraction reaching `INT64_MIN`:
174
+ these must reject without wrapping into a high fee. Output sizes are deliberately
175
+ bounded so each individual threshold fits in signed 64-bit; the target does not
176
+ promise arbitrary configured rate/script combinations are safe. See the
177
+ [scoped fuzz procedure](policy-parsing.md#scoped-fuzz-evidence).
178
+
179
+ **Unresolved preservation gates:** extreme configured thresholds beyond the
180
+ target's representable size/rate domain, dynamic dust repricing/startup, interactions with
181
+ rolling mempool pressure, package replacement/sibling eviction with dust,
182
+ reorg accounting, and the complete ephemeral exception/fee-delta matrix.
183
+ The bounded arithmetic campaign does not establish full admission safety at
184
+ extreme rates or prove saturated ancestor accounting for arbitrary packages.
185
+ Dynamic modes retain the known scheduler startup failure recorded in
186
+ `policy-parsing`. Existing dust/TRUC/package/reorg suites cover their ordinary
187
+ cases, but cannot be presented as proof of these combined cases. A port must
188
+ retain these gates until focused coverage or an explicit compatibility decision
189
+ resolves them.
190
+
191
+ Retirement requires upstream equivalence of this formula, rounding, effective
192
+ fee accounting, defaults/disable behavior and configuration interactions,
193
+ with the owned tests and valid-block invariant passing. An upstream dust
194
+ standardness check alone is not this fee penalty. Apply the catalog's shared
195
+ retirement rule and retain the unresolved-gap record until tested.