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.
|