Skip to content

LowestFee score loses precision for fees above 2^24 sats due to f32 conversion #82

Description

@noahjoeris

Describe the bug

LowestFee::fee_score converts the exact integer long-term fee to f32 before wrapping it in Ordf32:

Ordf32((fee_for_the_tx as u64 + fee_for_spending_drain) as f32)

f32 only represents integers exactly up to 2^24 (~16.7M sats). Above that, distinct fee totals round to the same f32 and compare as equal. Since branch-and-bound only replaces the incumbent on a strictly smaller score, it can keep a selection that pays more sats in fees than another available selection.

This issue was found by AI.

To Reproduce

use bdk_coin_select::{
    metrics::LowestFee, Candidate, CoinSelector, DrainWeights, FeeRate, Target, TargetFee,
    TargetOutputs,
};

#[test]
fn bnb_distinguishes_large_fee_scores() {
    let exact_fee = 1u64 << 25;
    let candidates = [
        Candidate { value: exact_fee + 2, weight: 100, input_count: 1, is_segwit: false },
        Candidate { value: exact_fee, weight: 100, input_count: 1, is_segwit: false },
    ];
    let target = Target {
        outputs: TargetOutputs { value_sum: 0, weight_sum: 0, n_outputs: 0 },
        fee: TargetFee { absolute: exact_fee, ..TargetFee::ZERO },
        max_weight: None,
    };
    let metric = LowestFee {
        long_term_feerate: FeeRate::from_sat_per_wu(1.0),
        dust_relay_feerate: FeeRate::ZERO,
        drain_weights: DrainWeights::TR_KEYSPEND,
    };

    let mut cs = CoinSelector::new(&candidates);
    cs.run_bnb(target, metric, usize::MAX).expect("a valid selection exists");

    // Fails: selects the first candidate and pays `exact_fee + 2`.
    assert_eq!(cs.fee(target.value(), 0), exact_fee as i64);
}

Expected behavior

Selections whose fees differ by a whole number of sats should not compare as equal, so branch-and-bound picks the one with the lower fee.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions