Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: use u64 for weights instead of u32 #28

Merged
merged 2 commits into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@ std = []
[dev-dependencies]
rand = "0.8"
proptest = "1.4"
bitcoin = "0.30"
bitcoin = "0.32"
28 changes: 16 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,20 @@
```rust
use std::str::FromStr;
use bdk_coin_select::{ CoinSelector, Candidate, TR_KEYSPEND_TXIN_WEIGHT, Drain, FeeRate, Target, ChangePolicy, TargetOutputs, TargetFee, DrainWeights};
use bitcoin::{ Address, Network, Transaction, TxIn, TxOut };
use bitcoin::{ Amount, Address, Network, Transaction, TxIn, TxOut };

let recipient_addr =
Address::from_str("tb1pvjf9t34fznr53u5tqhejz4nr69luzkhlvsdsdfq9pglutrpve2xq7hps46").unwrap();
let recipient_addr: Address = "tb1pvjf9t34fznr53u5tqhejz4nr69luzkhlvsdsdfq9pglutrpve2xq7hps46"
.parse::<Address<_>>()
.unwrap()
.assume_checked();

let outputs = vec![TxOut {
value: 3_500_000,
script_pubkey: recipient_addr.payload.script_pubkey(),
value: Amount::from_sat(3_500_000),
script_pubkey: recipient_addr.script_pubkey(),
}];

let target = Target {
outputs: TargetOutputs::fund_outputs(outputs.iter().map(|output| (output.weight() as u32, output.value))),
outputs: TargetOutputs::fund_outputs(outputs.iter().map(|output| (output.weight().to_wu(), output.value.to_sat()))),
fee: TargetFee::from_feerate(FeeRate::from_sat_per_vb(42.0))
};

Expand Down Expand Up @@ -87,14 +89,16 @@ metric by implementing the [`BnbMetric`] yourself but we don't recommend this.
use std::str::FromStr;
use bdk_coin_select::{ Candidate, CoinSelector, FeeRate, Target, TargetFee, TargetOutputs, ChangePolicy, TR_KEYSPEND_TXIN_WEIGHT, TR_DUST_RELAY_MIN_VALUE};
use bdk_coin_select::metrics::LowestFee;
use bitcoin::{ Address, Network, Transaction, TxIn, TxOut };
use bitcoin::{ Address, Amount, Network, Transaction, TxIn, TxOut };

let recipient_addr =
Address::from_str("tb1pvjf9t34fznr53u5tqhejz4nr69luzkhlvsdsdfq9pglutrpve2xq7hps46").unwrap();
let recipient_addr: Address = "tb1pvjf9t34fznr53u5tqhejz4nr69luzkhlvsdsdfq9pglutrpve2xq7hps46"
.parse::<Address<_>>()
.unwrap()
.assume_checked();

let outputs = vec![TxOut {
value: 210_000,
script_pubkey: recipient_addr.payload.script_pubkey(),
value: Amount::from_sat(210_000),
script_pubkey: recipient_addr.script_pubkey(),
}];

let candidates = [
Expand Down Expand Up @@ -125,7 +129,7 @@ let mut coin_selector = CoinSelector::new(&candidates);

let target = Target {
fee: TargetFee::from_feerate(FeeRate::from_sat_per_vb(15.0)),
outputs: TargetOutputs::fund_outputs(outputs.iter().map(|output| (output.weight() as u32, output.value))),
outputs: TargetOutputs::fund_outputs(outputs.iter().map(|output| (output.weight().to_wu(), output.value.to_sat()))),
};

// The change output must be at least this size to be relayed.
Expand Down
14 changes: 7 additions & 7 deletions src/coin_selector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,14 +131,14 @@ impl<'a> CoinSelector<'a> {

/// The weight of the inputs including the witness header and the varint for the number of
/// inputs.
pub fn input_weight(&self) -> u32 {
pub fn input_weight(&self) -> u64 {
let is_segwit_tx = self.selected().any(|(_, wv)| wv.is_segwit);
let witness_header_extra_weight = is_segwit_tx as u32 * 2;
let witness_header_extra_weight = is_segwit_tx as u64 * 2;

let input_count = self.selected().map(|(_, wv)| wv.input_count).sum::<usize>();
let input_varint_weight = varint_size(input_count) * 4;

let selected_weight: u32 = self
let selected_weight: u64 = self
.selected()
.map(|(_, candidate)| {
let mut weight = candidate.weight;
Expand Down Expand Up @@ -166,7 +166,7 @@ impl<'a> CoinSelector<'a> {
///
/// If you don't have any drain outputs (only target outputs) just set drain_weights to
/// [`DrainWeights::NONE`].
pub fn weight(&self, target_ouputs: TargetOutputs, drain_weight: DrainWeights) -> u32 {
pub fn weight(&self, target_ouputs: TargetOutputs, drain_weight: DrainWeights) -> u64 {
TX_FIXED_FIELD_WEIGHT
+ self.input_weight()
+ target_ouputs.output_weight_with_drain(drain_weight)
Expand Down Expand Up @@ -331,7 +331,7 @@ impl<'a> CoinSelector<'a> {
let mut excess_waste = self.excess(target, drain).max(0) as f32;
// we allow caller to discount this waste depending on how wasteful excess actually is
// to them.
excess_waste *= excess_discount.max(0.0).min(1.0);
excess_waste *= excess_discount.clamp(0.0, 1.0);
waste += excess_waste;
} else {
waste +=
Expand Down Expand Up @@ -642,7 +642,7 @@ pub struct Candidate {
/// Total weight of including this/these UTXO(s).
/// `txin` fields: `prevout`, `nSequence`, `scriptSigLen`, `scriptSig`, `scriptWitnessLen`,
/// `scriptWitness` should all be included.
pub weight: u32,
pub weight: u64,
/// Total number of inputs; so we can calculate extra `varint` weight due to `vin` len changes.
pub input_count: usize,
/// Whether this [`Candidate`] contains at least one segwit spend.
Expand All @@ -660,7 +660,7 @@ impl Candidate {
///
/// `satisfaction_weight` is the weight of `scriptSigLen + scriptSig + scriptWitnessLen +
/// scriptWitness`.
pub fn new(value: u64, satisfaction_weight: u32, is_segwit: bool) -> Candidate {
pub fn new(value: u64, satisfaction_weight: u64, is_segwit: bool) -> Candidate {
let weight = TXIN_BASE_WEIGHT + satisfaction_weight;
Candidate {
value,
Expand Down
4 changes: 2 additions & 2 deletions src/drain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ pub struct DrainWeights {
/// The weight of including this drain output.
///
/// This must not take into account any weight change from varint output count.
pub output_weight: u32,
pub output_weight: u64,
/// The weight of spending this drain output (in the future).
pub spend_weight: u32,
pub spend_weight: u64,
/// The total number of outputs that the drain will use
pub n_outputs: usize,
}
Expand Down
20 changes: 10 additions & 10 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,35 +29,35 @@ pub use drain::*;

/// Txin "base" fields include `outpoint` (32+4) and `nSequence` (4) and 1 byte for the scriptSig
/// length.
pub const TXIN_BASE_WEIGHT: u32 = (32 + 4 + 4 + 1) * 4;
pub const TXIN_BASE_WEIGHT: u64 = (32 + 4 + 4 + 1) * 4;

/// The weight of a TXOUT with a zero length `scriptPubKey`
#[allow(clippy::identity_op)]
pub const TXOUT_BASE_WEIGHT: u32 =
pub const TXOUT_BASE_WEIGHT: u64 =
// The value
4 * core::mem::size_of::<u64>() as u32
4 * core::mem::size_of::<u64>() as u64
// The spk length
+ (4 * 1);

/// The weight of the `nVersion` and `nLockTime` transaction fields
pub const TX_FIXED_FIELD_WEIGHT: u32 = (4 /* nVersion */ + 4/* nLockTime */) * 4;
pub const TX_FIXED_FIELD_WEIGHT: u64 = (4 /* nVersion */ + 4/* nLockTime */) * 4;

/// The weight of a taproot keyspend witness
pub const TR_KEYSPEND_SATISFACTION_WEIGHT: u32 = 1 /*witness_len*/ + 1 /*item len*/ + 64 /*signature*/;
pub const TR_KEYSPEND_SATISFACTION_WEIGHT: u64 = 1 /*witness_len*/ + 1 /*item len*/ + 64 /*signature*/;

/// The weight of a segwit `v1` (taproot) script pubkey in an output. This does not include the weight of
/// the `TxOut` itself or the script pubkey length field.
pub const TR_SPK_WEIGHT: u32 = (1 + 1 + 32) * 4; // version + push + key
pub const TR_SPK_WEIGHT: u64 = (1 + 1 + 32) * 4; // version + push + key

/// The weight of a taproot TxIn with witness
pub const TR_KEYSPEND_TXIN_WEIGHT: u32 = TXIN_BASE_WEIGHT + TR_KEYSPEND_SATISFACTION_WEIGHT;
pub const TR_KEYSPEND_TXIN_WEIGHT: u64 = TXIN_BASE_WEIGHT + TR_KEYSPEND_SATISFACTION_WEIGHT;

/// The minimum value a taproot output can have to be relayed with Bitcoin core's default dust relay
/// fee
pub const TR_DUST_RELAY_MIN_VALUE: u64 = 330;

/// Helper to calculate varint size. `v` is the value the varint represents.
const fn varint_size(v: usize) -> u32 {
const fn varint_size(v: usize) -> u64 {
if v <= 0xfc {
return 1;
}
Expand All @@ -71,6 +71,6 @@ const fn varint_size(v: usize) -> u32 {
}

#[allow(unused)]
fn txout_weight_from_spk_len(spk_len: usize) -> u32 {
(TXOUT_BASE_WEIGHT + varint_size(spk_len) + (spk_len as u32)) * 4
fn txout_weight_from_spk_len(spk_len: usize) -> u64 {
(TXOUT_BASE_WEIGHT + varint_size(spk_len) + (spk_len as u64)) * 4
}
18 changes: 9 additions & 9 deletions src/target.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@ pub struct TargetOutputs {
/// The sum of the values of the individual `TxOuts`s.
pub value_sum: u64,
/// The sum of the weights of the individual `TxOut`s.
pub weight_sum: u32,
pub weight_sum: u64,
/// The total number of outputs
pub n_outputs: usize,
}

impl TargetOutputs {
/// The output weight of the outptus we're trying to fund
pub fn output_weight(&self) -> u32 {
pub fn output_weight(&self) -> u64 {
self.weight_sum + varint_size(self.n_outputs) * 4
}

Expand All @@ -42,13 +42,13 @@ impl TargetOutputs {
/// adding the drain weights might add an extra vbyte for the length of the varint.
///
/// [`output_weight`]: Self::output_weight
pub fn output_weight_with_drain(&self, drain_weight: DrainWeights) -> u32 {
pub fn output_weight_with_drain(&self, drain_weight: DrainWeights) -> u64 {
let n_outputs = drain_weight.n_outputs + self.n_outputs;
varint_size(n_outputs) * 4 + drain_weight.output_weight + self.weight_sum
}

/// Creates a `TargetOutputs` from a list of outputs represented as `(weight, value)` pairs.
pub fn fund_outputs(outputs: impl IntoIterator<Item = (u32, u64)>) -> Self {
pub fn fund_outputs(outputs: impl IntoIterator<Item = (u64, u64)>) -> Self {
let mut n_outputs = 0;
let mut weight_sum = 0;
let mut value_sum = 0;
Expand All @@ -72,10 +72,10 @@ impl TargetOutputs {
/// There are two orthogonal constraints:
///
/// - `rate`: The feerate of the transaction must at least be this high. You set this to control how
/// quickly your transaction is confirmed. Typically a coin selection will try and hit this target
/// exactly but it might go over if the `replace` constraint takes precedence or if the
/// [`ChangePolicy`] determines that the excess value should just be given to miners (rather than
/// create a change output).
/// quickly your transaction is confirmed. Typically a coin selection will try and hit this target
/// exactly but it might go over if the `replace` constraint takes precedence or if the
/// [`ChangePolicy`] determines that the excess value should just be given to miners (rather than
/// create a change output).
/// - `replace`: The selection must have a high enough fee to satisfy [RBF rule 4]
///
/// [RBF rule 4]: https://github.com/bitcoin/bitcoin/blob/master/doc/policy/mempool-replacements.md#current-replace-by-fee-policy
Expand Down Expand Up @@ -135,7 +135,7 @@ impl Replace {
/// This is defined by [RBF rule 4].
///
/// [RBF rule 4]: https://github.com/bitcoin/bitcoin/blob/master/doc/policy/mempool-replacements.md#current-replace-by-fee-policy
pub fn min_fee_to_do_replacement(&self, replacing_tx_weight: u32) -> u64 {
pub fn min_fee_to_do_replacement(&self, replacing_tx_weight: u64) -> u64 {
let min_fee_increment =
(replacing_tx_weight as f32 * self.incremental_relay_feerate.spwu()).ceil() as u64;
self.fee + min_fee_increment
Expand Down
6 changes: 3 additions & 3 deletions tests/changeless.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ proptest! {
let mut rng = TestRng::deterministic_rng(RngAlgorithm::ChaCha);
let feerate = FeeRate::from_sat_per_vb(feerate);
let drain_weights = DrainWeights {
output_weight: drain_weight,
spend_weight: drain_spend_weight,
output_weight: drain_weight as u64,
spend_weight: drain_spend_weight as u64,
n_outputs: n_drain_outputs,
};

Expand All @@ -61,7 +61,7 @@ proptest! {
outputs: TargetOutputs {
n_outputs: n_target_outputs,
value_sum: target_value,
weight_sum: target_weight,
weight_sum: target_weight as u64,
},
fee: TargetFee {
rate: feerate,
Expand Down
6 changes: 3 additions & 3 deletions tests/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ impl StrategyParams {
},
outputs: TargetOutputs {
value_sum: self.target_value,
weight_sum: self.target_weight,
weight_sum: self.target_weight as u64,
n_outputs: self.n_target_outputs,
},
}
Expand All @@ -233,8 +233,8 @@ impl StrategyParams {

pub fn drain_weights(&self) -> DrainWeights {
DrainWeights {
output_weight: self.drain_weight,
spend_weight: self.drain_spend_weight,
output_weight: self.drain_weight as u64,
spend_weight: self.drain_spend_weight as u64,
n_outputs: self.n_drain_outputs,
}
}
Expand Down
2 changes: 1 addition & 1 deletion tests/lowest_fee.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ fn combined_changeless_metric() {
let params = common::StrategyParams {
n_candidates: 100,
target_value: 100_000,
target_weight: 1000 - TX_FIXED_FIELD_WEIGHT - 1,
target_weight: 1000 - TX_FIXED_FIELD_WEIGHT as u32 - 1,
replace: None,
feerate: 5.0,
feerate_lt_diff: -4.0,
Expand Down
2 changes: 1 addition & 1 deletion tests/rbf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ fn run_bitcoin_core_rbf_tests() {
fn pays_for_rbf(
original_fees: u64,
replacement_fees: u64,
replacement_vsize: u32,
replacement_vsize: u64,
relay_fee: FeeRate,
) -> bool {
let min_fee = Replace {
Expand Down
Loading