bitcoin_ext/
bdk.rs

1
2use std::borrow::BorrowMut;
3use std::sync::Arc;
4
5use bdk_wallet::{AddressInfo, TxBuilder, Wallet};
6use bdk_wallet::chain::BlockId;
7use bdk_wallet::coin_selection::InsufficientFunds;
8use bdk_wallet::error::CreateTxError;
9use bitcoin::consensus::encode::serialize_hex;
10use bitcoin::{BlockHash, FeeRate, OutPoint, Transaction, TxOut, Txid, Weight, Witness};
11use bitcoin::psbt::{ExtractTxError, Input};
12use log::{debug, trace};
13
14use crate::{BlockHeight, TransactionExt};
15use crate::cpfp::MakeCpfpFees;
16use crate::fee::FEE_ANCHOR_SPEND_WEIGHT;
17
18/// The [bdk_wallet::KeychainKind] that is always used, because we only use a single keychain.
19pub const KEYCHAIN: bdk_wallet::KeychainKind = bdk_wallet::KeychainKind::External;
20
21
22/// An extension trait for [TxBuilder].
23pub trait TxBuilderExt<'a, A>: BorrowMut<TxBuilder<'a, A>> {
24	/// Add an input to the tx that spends a fee anchor.
25	fn add_fee_anchor_spend(&mut self, anchor: OutPoint, output: &TxOut)
26	where
27		A: bdk_wallet::coin_selection::CoinSelectionAlgorithm,
28	{
29		let psbt_in = Input {
30			witness_utxo: Some(output.clone()),
31			final_script_witness: Some(Witness::new()),
32			..Default::default()
33		};
34		self.borrow_mut().add_foreign_utxo(anchor, psbt_in, FEE_ANCHOR_SPEND_WEIGHT)
35			.expect("adding foreign utxo");
36	}
37}
38impl<'a, A> TxBuilderExt<'a, A> for TxBuilder<'a, A> {}
39
40#[derive(Debug, thiserror::Error)]
41pub enum CpfpInternalError {
42	#[error("{0}")]
43	General(String),
44	#[error("Unable to construct transaction: {0}")]
45	Create(CreateTxError),
46	#[error("Unable to extract the final transaction after signing the PSBT: {0}")]
47	Extract(ExtractTxError),
48	#[error("Failed to determine the weight/fee when creating a P2A CPFP")]
49	Fee(),
50	#[error("Unable to finalize CPFP transaction: {0}")]
51	FinalizeError(String),
52	#[error("You need more confirmations on your on-chain funds: {0}")]
53	InsufficientConfirmedFunds(InsufficientFunds),
54	#[error("Transaction has no fee anchor: {0}")]
55	NoFeeAnchor(Txid),
56	#[allow(deprecated)]
57	#[error("Unable to sign transaction: {0}")]
58	Signer(bdk_wallet::signer::SignerError),
59}
60
61/// An extension trait for [Wallet].
62pub trait WalletExt: BorrowMut<Wallet> {
63	/// Peek into the next address.
64	fn peek_next_address(&self) -> AddressInfo {
65		self.borrow().peek_address(KEYCHAIN, self.borrow().next_derivation_index(KEYCHAIN))
66	}
67
68	/// Returns an iterator for each unconfirmed transaction in the wallet.
69	fn unconfirmed_txids(&self) -> impl Iterator<Item = Txid> {
70		self.borrow().transactions().filter_map(|tx| {
71			if tx.chain_position.is_unconfirmed() {
72				Some(tx.tx_node.txid)
73			} else {
74				None
75			}
76		})
77	}
78
79	/// Returns an iterator for each unconfirmed transaction in the wallet, useful for syncing
80	/// with bitcoin core.
81	fn unconfirmed_txs(&self) -> impl Iterator<Item = Arc<Transaction>> {
82		self.borrow().transactions().filter_map(|tx| {
83			if tx.chain_position.is_unconfirmed() {
84				Some(tx.tx_node.tx.clone())
85			} else {
86				None
87			}
88		})
89	}
90
91	/// Return all UTXOs that are untrusted: unconfirmed and not change.
92	fn untrusted_utxos(&self, confirmed_height: Option<BlockHeight>) -> Vec<OutPoint> {
93		let w = self.borrow();
94		let mut ret = Vec::new();
95		for utxo in w.list_unspent() {
96			// We trust confirmed utxos if they are confirmed enough.
97			if let Some(h) = utxo.chain_position.confirmation_height_upper_bound() {
98				if let Some(min) = confirmed_height {
99					if h <= min {
100						continue;
101					}
102				} else {
103					continue;
104				}
105			}
106
107			// For unconfirmed, we only trust txs from which all inputs are ours.
108			// NB this is still not 100% safe, because this can mark a tx that spends
109			// an untrusted tx as trusted. We don't create such txs in our codebase,
110			// but we should be careful not to start doing this.
111			let txid = utxo.outpoint.txid;
112			if let Some(tx) = w.get_tx(txid) {
113				if tx.tx_node.tx.input.iter().all(|i| w.get_tx(i.previous_output.txid).is_some()) {
114					continue;
115				}
116			}
117
118			ret.push(utxo.outpoint);
119		}
120		ret
121	}
122
123	/// Insert a checkpoint into the wallet.
124	///
125	/// It's advised to use this only when recovering a wallet with a birthday.
126	fn set_checkpoint(&mut self, height: u32, hash: BlockHash) {
127		let checkpoint = BlockId { height, hash };
128		let wallet = self.borrow_mut();
129		wallet.apply_update(bdk_wallet::Update {
130			chain: Some(wallet.latest_checkpoint().insert(checkpoint)),
131			..Default::default()
132		}).expect("should work, might fail if tip is genesis");
133	}
134
135	fn make_signed_p2a_cpfp(
136		&mut self,
137		tx: &Transaction,
138		fees: MakeCpfpFees,
139	) -> Result<Transaction, CpfpInternalError> {
140		let wallet = self.borrow_mut();
141		let (outpoint, txout) = tx.fee_anchor()
142			.ok_or_else(|| CpfpInternalError::NoFeeAnchor(tx.compute_txid()))?;
143
144		// Since BDK doesn't support adding extra weight for fees, we have to loop to achieve the
145		// effective fee rate and potential minimum fee we need.
146		let p2a_weight = tx.weight();
147		let extra_fee_needed = p2a_weight * fees.effective();
148
149		// Since BDK doesn't allow tx without recipients, we add a drain output.
150		let change_addr = wallet.reveal_next_address(KEYCHAIN);
151
152		// We will loop, constructing the transaction and signing it until we exceed the effective
153		// fee rate and meet any minimum fee requirements
154		let mut spend_weight = Weight::ZERO;
155		let mut fee_needed = extra_fee_needed;
156		for i in 0..100 {
157			let mut b = wallet.build_tx();
158			b.only_witness_utxo();
159			b.exclude_unconfirmed();
160			b.version(3); // for 1p1c package relay, all inputs must be confirmed
161			b.add_fee_anchor_spend(outpoint, txout);
162			b.drain_to(change_addr.address.script_pubkey());
163			b.fee_absolute(fee_needed);
164
165			// Attempt to create and sign the transaction
166			let mut psbt = b.finish().map_err(|e| match e {
167				CreateTxError::CoinSelection(e) => CpfpInternalError::InsufficientConfirmedFunds(e),
168				_ => CpfpInternalError::Create(e),
169			})?;
170			#[allow(deprecated)]
171			let opts = bdk_wallet::SignOptions {
172				trust_witness_utxo: true,
173				..Default::default()
174			};
175			let finalized = wallet.sign(&mut psbt, opts)
176				.map_err(|e| CpfpInternalError::Signer(e))?;
177			if !finalized {
178				return Err(CpfpInternalError::FinalizeError("finalization failed".into()));
179			}
180			let tx = psbt.extract_tx()
181				.map_err(|e| CpfpInternalError::Extract(e))?;
182			let anchor_weight = FEE_ANCHOR_SPEND_WEIGHT.to_wu();
183			assert!(tx.input.iter().any(|i| i.witness.size() as u64 == anchor_weight),
184				"Missing anchor spend, tx is {}", serialize_hex(&tx),
185			);
186
187			// We can finally check the fees and weight
188			let tx_weight = tx.weight();
189			let total_weight = tx_weight + p2a_weight;
190			if tx_weight != spend_weight {
191				// Since the weight changed, we can drop the transaction and recalculate the
192				// required fee amount.
193				wallet.cancel_tx(&tx);
194				spend_weight = tx_weight;
195				fee_needed = match fees {
196					MakeCpfpFees::Effective(fr) => total_weight * fr,
197					MakeCpfpFees::Rbf { min_effective_fee_rate, current_package_fee } => {
198						// RBF requires that you spend at least the total fee of every
199						// unconfirmed ancestor and the transaction you want to replace,
200						// then you must add mintxrelayfee * package_vbytes on top.
201						let min_tx_relay_fee = FeeRate::from_sat_per_vb(1).unwrap();
202						let min_package_fee = current_package_fee +
203							p2a_weight * min_tx_relay_fee +
204							tx_weight * min_tx_relay_fee;
205
206						// This is the fee we want to pay based on the given minimum effective fee
207						// rate. It's possible that the desired fee is lower than the minimum
208						// package fee if the currently broadcast child transaction is bigger than
209						// the transaction we just produced.
210						let desired_fee = total_weight * min_effective_fee_rate;
211						if desired_fee < min_package_fee {
212							debug!("Using a minimum fee of {} instead of the desired fee of {} for RBF",
213								min_package_fee, desired_fee,
214							);
215							min_package_fee
216						} else {
217							trace!("Attempting to use the desired fee of {} for CPFP RBF",
218								desired_fee,
219							);
220							desired_fee
221						}
222					}
223				}
224			} else {
225				debug!("Created P2A CPFP with weight {} and fee {} in {} iterations",
226					total_weight, fee_needed, i,
227				);
228				return Ok(tx);
229			}
230		}
231		Err(CpfpInternalError::General("Reached max iterations".into()))
232	}
233}
234
235impl WalletExt for Wallet {}