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. Before is on the left; after is on the right. Unified view fits narrow screens more easily.
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.
|