bitcoin_ext/
bdk.rs

1
2use std::borrow::BorrowMut;
3use std::sync::Arc;
4
5use bdk_wallet::{AddressInfo, SignOptions, TxBuilder, Wallet};
6use bdk_wallet::chain::BlockId;
7use bdk_wallet::coin_selection::InsufficientFunds;
8use bdk_wallet::error::CreateTxError;
9use bdk_wallet::signer::SignerError;
10use bitcoin::consensus::encode::serialize_hex;
11use bitcoin::{BlockHash, FeeRate, OutPoint, Transaction, TxOut, Txid, Weight, Witness};
12use bitcoin::psbt::{ExtractTxError, Input};
13use log::{debug, trace};
14
15use crate::{BlockHeight, TransactionExt};
16use crate::cpfp::MakeCpfpFees;
17use crate::fee::FEE_ANCHOR_SPEND_WEIGHT;
18
19/// The [bdk_wallet::KeychainKind] that is always used, because we only use a single keychain.
20pub const KEYCHAIN: bdk_wallet::KeychainKind = bdk_wallet::KeychainKind::External;
21
22
23/// An extension trait for [TxBuilder].
24pub trait TxBuilderExt<'a, A>: BorrowMut<TxBuilder<'a, A>> {
25	/// Add an input to the tx that spends a fee anchor.
26	fn add_fee_anchor_spend(&mut self, anchor: OutPoint, output: &TxOut)
27	where
28		A: bdk_wallet::coin_selection::CoinSelectionAlgorithm,
29	{
30		let psbt_in = Input {
31			witness_utxo: Some(output.clone()),
32			final_script_witness: Some(Witness::new()),
33			..Default::default()
34		};
35		self.borrow_mut().add_foreign_utxo(anchor, psbt_in, FEE_ANCHOR_SPEND_WEIGHT)
36			.expect("adding foreign utxo");
37	}
38}
39impl<'a, A> TxBuilderExt<'a, A> for TxBuilder<'a, A> {}
40
41#[derive(Debug, thiserror::Error)]
42pub enum CpfpInternalError {
43	#[error("{0}")]
44	General(String),
45	#[error("Unable to construct transaction: {0}")]
46	Create(CreateTxError),
47	#[error("Unable to extract the final transaction after signing the PSBT: {0}")]
48	Extract(ExtractTxError),
49	#[error("Failed to determine the weight/fee when creating a P2A CPFP")]
50	Fee(),
51	#[error("Unable to finalize CPFP transaction: {0}")]
52	FinalizeError(String),
53	#[error("You need more confirmations on your on-chain funds: {0}")]
54	InsufficientConfirmedFunds(InsufficientFunds),
55	#[error("Transaction has no fee anchor: {0}")]
56	NoFeeAnchor(Txid),
57	#[error("Unable to sign transaction: {0}")]
58	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			let opts = SignOptions {
171				trust_witness_utxo: true,
172				..Default::default()
173			};
174			let finalized = wallet.sign(&mut psbt, opts)
175				.map_err(|e| CpfpInternalError::Signer(e))?;
176			if !finalized {
177				return Err(CpfpInternalError::FinalizeError("finalization failed".into()));
178			}
179			let tx = psbt.extract_tx()
180				.map_err(|e| CpfpInternalError::Extract(e))?;
181			let anchor_weight = FEE_ANCHOR_SPEND_WEIGHT.to_wu();
182			assert!(tx.input.iter().any(|i| i.witness.size() as u64 == anchor_weight),
183				"Missing anchor spend, tx is {}", serialize_hex(&tx),
184			);
185
186			// We can finally check the fees and weight
187			let tx_weight = tx.weight();
188			let total_weight = tx_weight + p2a_weight;
189			if tx_weight != spend_weight {
190				// Since the weight changed, we can drop the transaction and recalculate the
191				// required fee amount.
192				wallet.cancel_tx(&tx);
193				spend_weight = tx_weight;
194				fee_needed = match fees {
195					MakeCpfpFees::Effective(fr) => total_weight * fr,
196					MakeCpfpFees::Rbf { min_effective_fee_rate, current_package_fee } => {
197						// RBF requires that you spend at least the total fee of every
198						// unconfirmed ancestor and the transaction you want to replace,
199						// then you must add mintxrelayfee * package_vbytes on top.
200						let min_tx_relay_fee = FeeRate::from_sat_per_vb(1).unwrap();
201						let min_package_fee = current_package_fee +
202							p2a_weight * min_tx_relay_fee +
203							tx_weight * min_tx_relay_fee;
204
205						// This is the fee we want to pay based on the given minimum effective fee
206						// rate. It's possible that the desired fee is lower than the minimum
207						// package fee if the currently broadcast child transaction is bigger than
208						// the transaction we just produced.
209						let desired_fee = total_weight * min_effective_fee_rate;
210						if desired_fee < min_package_fee {
211							debug!("Using a minimum fee of {} instead of the desired fee of {} for RBF",
212								min_package_fee, desired_fee,
213							);
214							min_package_fee
215						} else {
216							trace!("Attempting to use the desired fee of {} for CPFP RBF",
217								desired_fee,
218							);
219							desired_fee
220						}
221					}
222				}
223			} else {
224				debug!("Created P2A CPFP with weight {} and fee {} in {} iterations",
225					total_weight, fee_needed, i,
226				);
227				return Ok(tx);
228			}
229		}
230		Err(CpfpInternalError::General("Reached max iterations".into()))
231	}
232}
233
234impl WalletExt for Wallet {}