Skip to main content

bark/exit/
bdk.rs

1
2use anyhow::Context;
3use bitcoin::FeeRate;
4use log::warn;
5
6use crate::Wallet;
7use crate::exit::{Exit, ExitProgressStatus};
8use crate::onchain::{CpfpError, MakeCpfpFees};
9
10/// Exit transactions should only be RBF'ed if the new requested fee rate
11/// is strictly greater than the current package rate.
12pub (crate) fn should_rbf(new_fee_rate: FeeRate, old_fee_rate: FeeRate) -> bool {
13	new_fee_rate > old_fee_rate
14}
15
16impl Exit {
17	/// Advance ongoing exits by one step, handling CPFP fee-bumping via the wallet's
18	/// internal onchain wallet.
19	///
20	/// This makes progress on each exit but does not run an exit to completion — exits
21	/// span many blocks (broadcasting, confirmations, CSV timelocks, claim spends), so
22	/// this must be called repeatedly (e.g. once per block) until all exits reach a
23	/// terminal state.
24	///
25	/// It calls [Exit::progress_exits], creates CPFP transactions for any exits in
26	/// [crate::exit::ExitTxStatus::AwaitingCpfpBroadcast], then calls [Exit::progress_exits] again
27	/// so those exits advance to [crate::exit::ExitTxStatus::AwaitingConfirmation].
28	///
29	/// Callers with external or hardware wallets should use [Exit::exits_needing_cpfp]
30	/// and [Exit::provide_cpfp_tx] directly instead.
31	///
32	/// Returns an error if the wallet has no onchain wallet configured.
33	pub async fn progress_exits_with_cpfp(
34		&self,
35		wallet: &Wallet,
36		fee_rate_override: Option<FeeRate>,
37	) -> anyhow::Result<Option<Vec<ExitProgressStatus>>> {
38		let onchain_arc = wallet.inner.onchain.as_ref()
39			.context("no onchain wallet configured; cannot progress exits")?;
40
41		self.progress_exits(wallet).await?;
42
43		let fee_rate = match fee_rate_override {
44			Some(fr) => fr,
45			None => wallet.exit_mgr().default_exit_fee_rate().await,
46		};
47
48		for req in self.exits_needing_cpfp().await {
49			let fees = match req.rbf_requirement {
50				None => MakeCpfpFees::Effective(fee_rate),
51				Some(rbf) => {
52					// Only RBF if we can improve the fee rate; equal or lower rates are rejected
53					// by Bitcoin Core's RBF policy ("new feerate must be strictly greater").
54					if !should_rbf(fee_rate, rbf.min_fee_rate) {
55						warn!(
56							"Skipping exit CPFP RBF: requested fee rate {} is not above current package rate {}",
57							fee_rate, rbf.min_fee_rate,
58						);
59						continue;
60					}
61					MakeCpfpFees::Rbf {
62						min_effective_fee_rate: fee_rate,
63						current_package_fee: rbf.current_package_fee,
64					}
65				},
66			};
67			let child_tx = {
68				let mut onchain = onchain_arc.write().await;
69				let tx = match onchain.make_signed_p2a_cpfp(&req.exit_tx, fees).await {
70					Ok(tx) => tx,
71					Err(CpfpError::InsufficientConfirmedFunds { needed, available }) => {
72						warn!("Insufficient funds for exit CPFP: needed {} available {}", needed, available);
73						continue;
74					},
75					Err(e) => return Err(e.into()),
76				};
77				onchain.store_signed_p2a_cpfp(&tx).await?;
78				tx
79			};
80			let exit_txid = req.exit_tx.compute_txid();
81			self.provide_cpfp_tx(wallet, exit_txid, child_tx).await?;
82		}
83
84		self.progress_exits(wallet).await
85	}
86}