Implementing review suggestions from afilini
This commit is contained in:
parent
759f6eac43
commit
49894ffa6d
@ -47,6 +47,9 @@ pub enum Error {
|
|||||||
FeeRateTooLow {
|
FeeRateTooLow {
|
||||||
required: crate::types::FeeRate,
|
required: crate::types::FeeRate,
|
||||||
},
|
},
|
||||||
|
FeeTooLow {
|
||||||
|
required: u64,
|
||||||
|
},
|
||||||
|
|
||||||
Key(crate::keys::KeyError),
|
Key(crate::keys::KeyError),
|
||||||
|
|
||||||
|
@ -299,13 +299,20 @@ where
|
|||||||
output: vec![],
|
output: vec![],
|
||||||
};
|
};
|
||||||
|
|
||||||
let fee_rate = get_fee_rate(&builder.fee_policy);
|
let (fee_rate, mut fee_amount) = match builder
|
||||||
|
.fee_policy
|
||||||
|
.as_ref()
|
||||||
|
.unwrap_or(&FeePolicy::FeeRate(FeeRate::default()))
|
||||||
|
{
|
||||||
|
FeePolicy::FeeAmount(amount) => (FeeRate::from_sat_per_vb(0.0), *amount as f32),
|
||||||
|
FeePolicy::FeeRate(rate) => (*rate, 0.0),
|
||||||
|
};
|
||||||
|
|
||||||
if builder.send_all && builder.recipients.len() != 1 {
|
if builder.send_all && builder.recipients.len() != 1 {
|
||||||
return Err(Error::SendAllMultipleOutputs);
|
return Err(Error::SendAllMultipleOutputs);
|
||||||
}
|
}
|
||||||
|
|
||||||
// we keep it as a float while we accumulate it, and only round it at the end
|
// we keep it as a float while we accumulate it, and only round it at the end
|
||||||
let mut fee_amount: f32 = 0.0;
|
|
||||||
let mut outgoing: u64 = 0;
|
let mut outgoing: u64 = 0;
|
||||||
let mut received: u64 = 0;
|
let mut received: u64 = 0;
|
||||||
|
|
||||||
@ -394,13 +401,6 @@ where
|
|||||||
|
|
||||||
let mut fee_amount = fee_amount.ceil() as u64;
|
let mut fee_amount = fee_amount.ceil() as u64;
|
||||||
|
|
||||||
if builder.has_absolute_fee() {
|
|
||||||
fee_amount = match builder.fee_policy.as_ref().unwrap() {
|
|
||||||
FeePolicy::FeeAmount(amount) => *amount,
|
|
||||||
_ => fee_amount,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let change_val = (selected_amount - outgoing).saturating_sub(fee_amount);
|
let change_val = (selected_amount - outgoing).saturating_sub(fee_amount);
|
||||||
if !builder.send_all && !change_val.is_dust() {
|
if !builder.send_all && !change_val.is_dust() {
|
||||||
let mut change_output = change_output.unwrap();
|
let mut change_output = change_output.unwrap();
|
||||||
@ -493,13 +493,28 @@ where
|
|||||||
// the new tx must "pay for its bandwidth"
|
// the new tx must "pay for its bandwidth"
|
||||||
let vbytes = tx.get_weight() as f32 / 4.0;
|
let vbytes = tx.get_weight() as f32 / 4.0;
|
||||||
let required_feerate = FeeRate::from_sat_per_vb(details.fees as f32 / vbytes + 1.0);
|
let required_feerate = FeeRate::from_sat_per_vb(details.fees as f32 / vbytes + 1.0);
|
||||||
let new_feerate = get_fee_rate(&builder.fee_policy);
|
let new_feerate = match builder
|
||||||
|
.fee_policy
|
||||||
if new_feerate < required_feerate && !builder.has_absolute_fee() {
|
.as_ref()
|
||||||
return Err(Error::FeeRateTooLow {
|
.unwrap_or(&FeePolicy::FeeRate(FeeRate::default()))
|
||||||
required: required_feerate,
|
{
|
||||||
});
|
FeePolicy::FeeAmount(amount) => {
|
||||||
}
|
if *amount < details.fees {
|
||||||
|
return Err(Error::FeeTooLow {
|
||||||
|
required: details.fees,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
FeeRate::from_sat_per_vb(0.0)
|
||||||
|
}
|
||||||
|
FeePolicy::FeeRate(rate) => {
|
||||||
|
if *rate < required_feerate {
|
||||||
|
return Err(Error::FeeRateTooLow {
|
||||||
|
required: required_feerate,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
*rate
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if builder.send_all && tx.output.len() > 1 {
|
if builder.send_all && tx.output.len() > 1 {
|
||||||
return Err(Error::SendAllMultipleOutputs);
|
return Err(Error::SendAllMultipleOutputs);
|
||||||
@ -630,14 +645,13 @@ where
|
|||||||
must_use_utxos.append(&mut original_utxos);
|
must_use_utxos.append(&mut original_utxos);
|
||||||
|
|
||||||
let amount_needed = tx.output.iter().fold(0, |acc, out| acc + out.value);
|
let amount_needed = tx.output.iter().fold(0, |acc, out| acc + out.value);
|
||||||
let initial_fee = tx.get_weight() as f32 / 4.0 * new_feerate.as_sat_vb();
|
let initial_fee = match builder
|
||||||
let initial_fee = if builder.has_absolute_fee() {
|
.fee_policy
|
||||||
match builder.fee_policy.as_ref().unwrap() {
|
.as_ref()
|
||||||
FeePolicy::FeeAmount(amount) => *amount as f32,
|
.unwrap_or(&FeePolicy::FeeRate(FeeRate::default()))
|
||||||
_ => initial_fee,
|
{
|
||||||
}
|
FeePolicy::FeeAmount(amount) => *amount as f32,
|
||||||
} else {
|
FeePolicy::FeeRate(_) => tx.get_weight() as f32 / 4.0 * new_feerate.as_sat_vb(),
|
||||||
initial_fee
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let coin_selection::CoinSelectionResult {
|
let coin_selection::CoinSelectionResult {
|
||||||
@ -669,12 +683,6 @@ where
|
|||||||
details.sent = selected_amount;
|
details.sent = selected_amount;
|
||||||
|
|
||||||
let mut fee_amount = fee_amount.ceil() as u64;
|
let mut fee_amount = fee_amount.ceil() as u64;
|
||||||
if builder.has_absolute_fee() {
|
|
||||||
fee_amount = match builder.fee_policy.as_ref().unwrap() {
|
|
||||||
FeePolicy::FeeAmount(amount) => *amount,
|
|
||||||
_ => fee_amount,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let removed_output_fee_cost = (serialize(&removed_updatable_output).len() as f32
|
let removed_output_fee_cost = (serialize(&removed_updatable_output).len() as f32
|
||||||
* new_feerate.as_sat_vb())
|
* new_feerate.as_sat_vb())
|
||||||
.ceil() as u64;
|
.ceil() as u64;
|
||||||
@ -682,23 +690,14 @@ where
|
|||||||
let change_val = selected_amount - amount_needed - fee_amount;
|
let change_val = selected_amount - amount_needed - fee_amount;
|
||||||
let change_val_after_add = change_val.saturating_sub(removed_output_fee_cost);
|
let change_val_after_add = change_val.saturating_sub(removed_output_fee_cost);
|
||||||
if !builder.send_all && !change_val_after_add.is_dust() {
|
if !builder.send_all && !change_val_after_add.is_dust() {
|
||||||
if builder.has_absolute_fee() {
|
removed_updatable_output.value = change_val_after_add;
|
||||||
removed_updatable_output.value = change_val_after_add + removed_output_fee_cost;
|
fee_amount += removed_output_fee_cost;
|
||||||
details.received += change_val_after_add + removed_output_fee_cost;
|
details.received += change_val_after_add;
|
||||||
} else {
|
|
||||||
removed_updatable_output.value = change_val_after_add;
|
|
||||||
fee_amount += removed_output_fee_cost;
|
|
||||||
details.received += change_val_after_add;
|
|
||||||
}
|
|
||||||
|
|
||||||
tx.output.push(removed_updatable_output);
|
tx.output.push(removed_updatable_output);
|
||||||
} else if builder.send_all && !change_val_after_add.is_dust() {
|
} else if builder.send_all && !change_val_after_add.is_dust() {
|
||||||
if builder.has_absolute_fee() {
|
removed_updatable_output.value = change_val_after_add;
|
||||||
removed_updatable_output.value = change_val_after_add + removed_output_fee_cost;
|
fee_amount += removed_output_fee_cost;
|
||||||
} else {
|
|
||||||
removed_updatable_output.value = change_val_after_add;
|
|
||||||
fee_amount += removed_output_fee_cost;
|
|
||||||
}
|
|
||||||
|
|
||||||
// send_all to our address
|
// send_all to our address
|
||||||
if self.is_mine(&removed_updatable_output.script_pubkey)? {
|
if self.is_mine(&removed_updatable_output.script_pubkey)? {
|
||||||
@ -1242,17 +1241,6 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// get the fee rate if specified or a default
|
|
||||||
fn get_fee_rate(fee_policy: &Option<FeePolicy>) -> FeeRate {
|
|
||||||
if fee_policy.is_none() {
|
|
||||||
return FeeRate::default();
|
|
||||||
}
|
|
||||||
match fee_policy.as_ref().unwrap() {
|
|
||||||
FeePolicy::FeeRate(fr) => *fr,
|
|
||||||
_ => FeeRate::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
@ -1716,6 +1704,52 @@ mod test {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(details.fees, 100);
|
assert_eq!(details.fees, 100);
|
||||||
|
assert_eq!(psbt.global.unsigned_tx.output.len(), 1);
|
||||||
|
assert_eq!(
|
||||||
|
psbt.global.unsigned_tx.output[0].value,
|
||||||
|
50_000 - details.fees
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_create_tx_absolute_zero_fee() {
|
||||||
|
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
|
||||||
|
let addr = wallet.get_new_address().unwrap();
|
||||||
|
let (psbt, details) = wallet
|
||||||
|
.create_tx(
|
||||||
|
TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)])
|
||||||
|
.fee_absolute(0)
|
||||||
|
.send_all(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(details.fees, 0);
|
||||||
|
assert_eq!(psbt.global.unsigned_tx.output.len(), 1);
|
||||||
|
assert_eq!(
|
||||||
|
psbt.global.unsigned_tx.output[0].value,
|
||||||
|
50_000 - details.fees
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic(expected = "InsufficientFunds")]
|
||||||
|
fn test_create_tx_absolute_high_fee() {
|
||||||
|
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
|
||||||
|
let addr = wallet.get_new_address().unwrap();
|
||||||
|
let (psbt, details) = wallet
|
||||||
|
.create_tx(
|
||||||
|
TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)])
|
||||||
|
.fee_absolute(60_000)
|
||||||
|
.send_all(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(details.fees, 0);
|
||||||
|
assert_eq!(psbt.global.unsigned_tx.output.len(), 1);
|
||||||
|
assert_eq!(
|
||||||
|
psbt.global.unsigned_tx.output[0].value,
|
||||||
|
50_000 - details.fees
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -2044,6 +2078,48 @@ mod test {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic(expected = "FeeTooLow")]
|
||||||
|
fn test_bump_fee_low_fee() {
|
||||||
|
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
|
||||||
|
let addr = wallet.get_new_address().unwrap();
|
||||||
|
let (psbt, mut details) = wallet
|
||||||
|
.create_tx(
|
||||||
|
TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)]).enable_rbf(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let tx = psbt.extract_tx();
|
||||||
|
let txid = tx.txid();
|
||||||
|
// skip saving the utxos, we know they can't be used anyways
|
||||||
|
details.transaction = Some(tx);
|
||||||
|
wallet.database.borrow_mut().set_tx(&details).unwrap();
|
||||||
|
|
||||||
|
wallet
|
||||||
|
.bump_fee(&txid, TxBuilder::new().fee_absolute(10))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic(expected = "FeeTooLow")]
|
||||||
|
fn test_bump_fee_zero_abs() {
|
||||||
|
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
|
||||||
|
let addr = wallet.get_new_address().unwrap();
|
||||||
|
let (psbt, mut details) = wallet
|
||||||
|
.create_tx(
|
||||||
|
TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)]).enable_rbf(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let tx = psbt.extract_tx();
|
||||||
|
let txid = tx.txid();
|
||||||
|
// skip saving the utxos, we know they can't be used anyways
|
||||||
|
details.transaction = Some(tx);
|
||||||
|
wallet.database.borrow_mut().set_tx(&details).unwrap();
|
||||||
|
|
||||||
|
wallet
|
||||||
|
.bump_fee(&txid, TxBuilder::new().fee_absolute(0))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_bump_fee_reduce_change() {
|
fn test_bump_fee_reduce_change() {
|
||||||
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
|
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
|
||||||
|
@ -82,6 +82,12 @@ pub enum FeePolicy {
|
|||||||
FeeAmount(u64),
|
FeeAmount(u64),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::default::Default for FeePolicy {
|
||||||
|
fn default() -> Self {
|
||||||
|
FeePolicy::FeeRate(FeeRate::default_min_relay_fee())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Unfortunately derive doesn't work with `PhantomData`: https://github.com/rust-lang/rust/issues/26925
|
// Unfortunately derive doesn't work with `PhantomData`: https://github.com/rust-lang/rust/issues/26925
|
||||||
impl<D: Database, Cs: CoinSelectionAlgorithm<D>> Default for TxBuilder<D, Cs>
|
impl<D: Database, Cs: CoinSelectionAlgorithm<D>> Default for TxBuilder<D, Cs>
|
||||||
where
|
where
|
||||||
@ -315,14 +321,6 @@ impl<D: Database, Cs: CoinSelectionAlgorithm<D>> TxBuilder<D, Cs> {
|
|||||||
phantom: PhantomData,
|
phantom: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if an absolute fee was specified
|
|
||||||
pub fn has_absolute_fee(&self) -> bool {
|
|
||||||
if self.fee_policy.is_none() {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
matches!(self.fee_policy.as_ref().unwrap(), FeePolicy::FeeAmount(_))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ordering of the transaction's inputs and outputs
|
/// Ordering of the transaction's inputs and outputs
|
||||||
|
Loading…
x
Reference in New Issue
Block a user