You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
DrainWeights::dust_threshold is below Bitcoin Core's dust limit for P2TR change #81
DrainWeights::dust_threshold computes the threshold from the drain's actual output_weight + spend_weight. For DrainWeights::TR_KEYSPEND that is 172 + 230 = 402 WU, which at the default 3 sat/vB dust relay feerate gives 302 sats. Bitcoin Core's dust rule for segwit outputs instead assumes a fixed 67 vB spend size, which is why a P2TR output is dust below 330 sats (matching the crate's own TR_DUST_RELAY_MIN_VALUE).
Since LowestFee::drain_value relies on dust_threshold, it can return a P2TR change output valued between 302 and 329 sats, which default Bitcoin Core relay policy treats as dust.
This issue was found by AI.
To Reproduce
use bdk_coin_select::{
metrics::LowestFee,BnbMetric,Candidate,CoinSelector,DrainWeights,FeeRate,Target,TargetFee,TargetOutputs,TR_DUST_RELAY_MIN_VALUE,};#[test]fndoes_not_create_p2tr_change_below_core_dust_threshold(){let candidates = [Candidate{value:1_000,weight:1,input_count:1,is_segwit:true,}];letmut cs = CoinSelector::new(&candidates);assert!(cs.select(0));let target = Target{fee:TargetFee::ZERO,outputs:TargetOutputs{value_sum:1_000 - (TR_DUST_RELAY_MIN_VALUE - 10),weight_sum:1,n_outputs:1,},max_weight:None,};letmut metric = LowestFee{long_term_feerate:FeeRate::from_sat_per_vb(1.0),dust_relay_feerate:FeeRate::from_sat_per_vb(3.0),drain_weights:DrainWeights::TR_KEYSPEND,};// Fails: returns a drain with value 320.assert!(metric.drain(&cs, target).is_none());}
Expected behavior
The dust threshold used by LowestFee should not produce change outputs that Bitcoin Core's default dust relay policy rejects (i.e. below 330 sats for P2TR at 3 sat/vB).
I can pick this up. The test in the report doesn't compile anymore after #59 and #63, but I updated it and it still fails on dcb1ce9. dust_threshold comes out at 302 sats for TR_KEYSPEND at 3 sat/vB, and LowestFee hands back 320 sats of P2TR change. Core's dust limit for P2TR is 330.
Looks like this started with 28878e6. Before that, the caller passed the limit in through ChangePolicy. The catch is that Core doesn't look at the real spend weight at all. It just assumes 67 vB for witness programs and 148 vB for everything else, so output_weight + spend_weight can't line up with it unless we know the script type.
I see two ways to go. The small fix is to never let the spend side drop below Core's 67 vB. That keeps P2WPKH, P2WSH and P2TR change at or above Core's limit, but P2SH-wrapped change would still come in under it. The other way is to have the caller pass the dust limit in (e.g. from rust-bitcoin's Script::minimal_non_dust_custom), like ChangePolicy::min_value used to. That's exact for every script type, but it means changing LowestFee again before 0.5.0.
Activity
Ansh-699 commentedon Oct 21, 2026
I can pick this up. The test in the report doesn't compile anymore after #59 and #63, but I updated it and it still fails on dcb1ce9.
dust_thresholdcomes out at 302 sats forTR_KEYSPENDat 3 sat/vB, andLowestFeehands back 320 sats of P2TR change. Core's dust limit for P2TR is 330.Looks like this started with 28878e6. Before that, the caller passed the limit in through
ChangePolicy. The catch is that Core doesn't look at the real spend weight at all. It just assumes 67 vB for witness programs and 148 vB for everything else, sooutput_weight + spend_weightcan't line up with it unless we know the script type.I see two ways to go. The small fix is to never let the spend side drop below Core's 67 vB. That keeps P2WPKH, P2WSH and P2TR change at or above Core's limit, but P2SH-wrapped change would still come in under it. The other way is to have the caller pass the dust limit in (e.g. from rust-bitcoin's
Script::minimal_non_dust_custom), likeChangePolicy::min_valueused to. That's exact for every script type, but it means changingLowestFeeagain before 0.5.0.Which one would you rather have?