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