use crate::coins::Coin;
use crate::error::{Error, Result};
pub fn fee_bound(min_fee_rate: u64) -> u64 {
min_fee_rate.saturating_mul(200)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Selection {
pub picked: Vec<Coin>,
pub sum: u64,
}
pub fn select(coins: &[Coin], amount: u64, bound: u64) -> Result<Selection> {
let need = amount.saturating_add(bound);
let mut sorted: Vec<Coin> = coins.to_vec();
sorted.sort_by_key(|c| core::cmp::Reverse(c.value));
let mut picked = Vec::new();
let mut sum = 0u64;
for c in sorted {
sum = sum.saturating_add(c.value);
picked.push(c);
if sum >= need {
return Ok(Selection { picked, sum });
}
}
Err(Error::Insufficient { have: sum, need })
}
#[cfg(test)]
mod tests {
use super::*;
use bitcoin::hashes::Hash;
use bitcoin::{OutPoint, Txid};
fn coin(v: u64, n: u32) -> Coin {
Coin {
outpoint: OutPoint {
txid: Txid::from_raw_hash(Hash::all_zeros()),
vout: n,
},
value: v,
height: 0,
coinbase: false,
}
}
#[test]
fn largest_first_and_stable() {
let coins = [coin(5, 0), coin(5, 1), coin(9, 2)];
let s = select(&coins, 10, 0).unwrap();
assert_eq!(
s.picked.iter().map(|c| c.outpoint.vout).collect::<Vec<_>>(),
vec![2, 0]
);
assert_eq!(
select(&[], 1, 0).unwrap_err().to_string(),
"insufficient: 0 sats mature, 1 needed"
);
assert_eq!(fee_bound(3), 600);
}
}