Skip to content

Commit c6c1c07

Browse files
committed
refactor: Move fee estimation to separate function
1 parent 40a3aeb commit c6c1c07

File tree

1 file changed

+116
-43
lines changed

1 file changed

+116
-43
lines changed

src/wallet/mod.rs

Lines changed: 116 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,9 @@ use bitcoin::{
5252

5353
use std::ops::Deref;
5454
use std::str::FromStr;
55-
use std::sync::{Arc, Mutex};
55+
use std::sync::{Arc, Mutex, MutexGuard};
5656

57+
#[derive(Debug, Copy, Clone)]
5758
pub(crate) enum OnchainSendAmount {
5859
ExactRetainingReserve { amount_sats: u64, cur_anchor_reserve_sats: u64 },
5960
AllRetainingReserve { cur_anchor_reserve_sats: u64 },
@@ -352,6 +353,114 @@ where
352353
.map_err(|_| Error::InvalidAddress)
353354
}
354355

356+
pub(crate) fn estimate_fee(
357+
&self, address: &Address, send_amount: OnchainSendAmount, fee_rate: Option<FeeRate>,
358+
) -> Result<Amount, Error> {
359+
let mut locked_wallet = self.inner.lock().unwrap();
360+
361+
// Use the set fee_rate or default to fee estimation.
362+
let confirmation_target = ConfirmationTarget::OnchainPayment;
363+
let fee_rate =
364+
fee_rate.unwrap_or_else(|| self.fee_estimator.estimate_fee_rate(confirmation_target));
365+
366+
self.estimate_fee_internal(&mut locked_wallet, address, send_amount, fee_rate).map_err(
367+
|e| {
368+
log_error!(self.logger, "Failed to estimate fee: {e}");
369+
e
370+
},
371+
)
372+
}
373+
374+
pub(crate) fn estimate_fee_internal(
375+
&self, locked_wallet: &mut MutexGuard<PersistedWallet<KVStoreWalletPersister>>,
376+
address: &Address, send_amount: OnchainSendAmount, fee_rate: FeeRate,
377+
) -> Result<Amount, Error> {
378+
const DUST_LIMIT_SATS: u64 = 546;
379+
match send_amount {
380+
OnchainSendAmount::ExactRetainingReserve { amount_sats, .. } => {
381+
let mut tx_builder = locked_wallet.build_tx();
382+
let amount = Amount::from_sat(amount_sats);
383+
tx_builder.add_recipient(address.script_pubkey(), amount).fee_rate(fee_rate);
384+
385+
let psbt = match tx_builder.finish() {
386+
Ok(psbt) => psbt,
387+
Err(err) => {
388+
log_error!(self.logger, "Failed to create temporary transaction: {}", err);
389+
return Err(err.into());
390+
},
391+
};
392+
393+
// 'cancel' the transaction to free up any used change addresses
394+
locked_wallet.cancel_tx(&psbt.unsigned_tx);
395+
psbt.fee().map_err(|_| Error::WalletOperationFailed)
396+
},
397+
OnchainSendAmount::AllRetainingReserve { cur_anchor_reserve_sats }
398+
if cur_anchor_reserve_sats > DUST_LIMIT_SATS =>
399+
{
400+
let change_address_info = locked_wallet.peek_address(KeychainKind::Internal, 0);
401+
let balance = locked_wallet.balance();
402+
let spendable_amount_sats = self
403+
.get_balances_inner(balance, cur_anchor_reserve_sats)
404+
.map(|(_, s)| s)
405+
.unwrap_or(0);
406+
let mut tx_builder = locked_wallet.build_tx();
407+
tx_builder
408+
.drain_wallet()
409+
.drain_to(address.script_pubkey())
410+
.add_recipient(
411+
change_address_info.address.script_pubkey(),
412+
Amount::from_sat(cur_anchor_reserve_sats),
413+
)
414+
.fee_rate(fee_rate);
415+
416+
let psbt = match tx_builder.finish() {
417+
Ok(psbt) => psbt,
418+
Err(err) => {
419+
log_error!(self.logger, "Failed to create temporary transaction: {}", err);
420+
return Err(err.into());
421+
},
422+
};
423+
424+
// 'cancel' the transaction to free up any used change addresses
425+
locked_wallet.cancel_tx(&psbt.unsigned_tx);
426+
427+
let estimated_tx_fee = psbt.fee().map_err(|_| Error::WalletOperationFailed)?;
428+
429+
// enforce the reserve requirements to make sure we can actually afford the tx + fee
430+
let estimated_spendable_amount = Amount::from_sat(
431+
spendable_amount_sats.saturating_sub(estimated_tx_fee.to_sat()),
432+
);
433+
434+
if estimated_spendable_amount == Amount::ZERO {
435+
log_error!(self.logger,
436+
"Unable to send payment without infringing on Anchor reserves. Available: {}sats, estimated fee required: {}sats.",
437+
spendable_amount_sats,
438+
estimated_tx_fee,
439+
);
440+
return Err(Error::InsufficientFunds);
441+
}
442+
443+
Ok(estimated_tx_fee)
444+
},
445+
OnchainSendAmount::AllDrainingReserve
446+
| OnchainSendAmount::AllRetainingReserve { cur_anchor_reserve_sats: _ } => {
447+
let mut tx_builder = locked_wallet.build_tx();
448+
tx_builder.drain_wallet().drain_to(address.script_pubkey()).fee_rate(fee_rate);
449+
let psbt = match tx_builder.finish() {
450+
Ok(psbt) => psbt,
451+
Err(err) => {
452+
log_error!(self.logger, "Failed to create temporary transaction: {}", err);
453+
return Err(err.into());
454+
},
455+
};
456+
457+
// 'cancel' the transaction to free up any used change addresses
458+
locked_wallet.cancel_tx(&psbt.unsigned_tx);
459+
psbt.fee().map_err(|_| Error::WalletOperationFailed)
460+
},
461+
}
462+
}
463+
355464
pub(crate) fn send_to_address(
356465
&self, address: &bitcoin::Address, send_amount: OnchainSendAmount,
357466
fee_rate: Option<FeeRate>,
@@ -378,60 +487,24 @@ where
378487
OnchainSendAmount::AllRetainingReserve { cur_anchor_reserve_sats }
379488
if cur_anchor_reserve_sats > DUST_LIMIT_SATS =>
380489
{
381-
let change_address_info = locked_wallet.peek_address(KeychainKind::Internal, 0);
382490
let balance = locked_wallet.balance();
383491
let spendable_amount_sats = self
384492
.get_balances_inner(balance, cur_anchor_reserve_sats)
385493
.map(|(_, s)| s)
386494
.unwrap_or(0);
387-
let tmp_tx = {
388-
let mut tmp_tx_builder = locked_wallet.build_tx();
389-
tmp_tx_builder
390-
.drain_wallet()
391-
.drain_to(address.script_pubkey())
392-
.add_recipient(
393-
change_address_info.address.script_pubkey(),
394-
Amount::from_sat(cur_anchor_reserve_sats),
395-
)
396-
.fee_rate(fee_rate);
397-
match tmp_tx_builder.finish() {
398-
Ok(psbt) => psbt.unsigned_tx,
399-
Err(err) => {
400-
log_error!(
401-
self.logger,
402-
"Failed to create temporary transaction: {}",
403-
err
404-
);
405-
return Err(err.into());
406-
},
407-
}
408-
};
409495

410-
let estimated_tx_fee = locked_wallet.calculate_fee(&tmp_tx).map_err(|e| {
411-
log_error!(
412-
self.logger,
413-
"Failed to calculate fee of temporary transaction: {}",
496+
// estimate_fee_internal will enforce that we are retaining the reserve limits
497+
let estimated_tx_fee = self
498+
.estimate_fee_internal(&mut locked_wallet, address, send_amount, fee_rate)
499+
.map_err(|e| {
500+
log_error!(self.logger, "Failed to estimate fee: {e}");
414501
e
415-
);
416-
e
417-
})?;
418-
419-
// 'cancel' the transaction to free up any used change addresses
420-
locked_wallet.cancel_tx(&tmp_tx);
502+
})?;
421503

422504
let estimated_spendable_amount = Amount::from_sat(
423505
spendable_amount_sats.saturating_sub(estimated_tx_fee.to_sat()),
424506
);
425507

426-
if estimated_spendable_amount == Amount::ZERO {
427-
log_error!(self.logger,
428-
"Unable to send payment without infringing on Anchor reserves. Available: {}sats, estimated fee required: {}sats.",
429-
spendable_amount_sats,
430-
estimated_tx_fee,
431-
);
432-
return Err(Error::InsufficientFunds);
433-
}
434-
435508
let mut tx_builder = locked_wallet.build_tx();
436509
tx_builder
437510
.add_recipient(address.script_pubkey(), estimated_spendable_amount)

0 commit comments

Comments
 (0)