| 
5 | 5 | // http://opensource.org/licenses/MIT>, at your option. You may not use this file except in  | 
6 | 6 | // accordance with one or both of these licenses.  | 
7 | 7 | 
 
  | 
 | 8 | +use bdk_wallet::error::{BuildFeeBumpError, CreateTxError};  | 
8 | 9 | use persist::KVStoreWalletPersister;  | 
9 | 10 | 
 
  | 
10 | 11 | use crate::config::{Config, RebroadcastPolicy};  | 
@@ -789,6 +790,167 @@ where  | 
789 | 790 | 		log_info!(self.logger, "No details found for payment {} in store", payment_id);  | 
790 | 791 | 		return Err(Error::InvalidPaymentId);  | 
791 | 792 | 	}  | 
 | 793 | + | 
 | 794 | +	pub(crate) fn bump_fee_rbf(&self, payment_id: PaymentId) -> Result<Txid, Error> {  | 
 | 795 | +		let old_payment =  | 
 | 796 | +			self.payment_store.get(&payment_id).ok_or(Error::InvalidPaymentId)?.clone();  | 
 | 797 | + | 
 | 798 | +		let mut locked_wallet = self.inner.lock().unwrap();  | 
 | 799 | + | 
 | 800 | +		let txid = Txid::from_slice(&payment_id.0).expect("32 bytes");  | 
 | 801 | + | 
 | 802 | +		let wallet_tx = locked_wallet.get_tx(txid).ok_or(Error::InvalidPaymentId)?;  | 
 | 803 | +		let (sent, received) = locked_wallet.sent_and_received(&wallet_tx.tx_node.tx);  | 
 | 804 | + | 
 | 805 | +		if sent <= received {  | 
 | 806 | +			log_error!(  | 
 | 807 | +				self.logger,  | 
 | 808 | +				"Transaction {} is not an outbound payment (sent: {}, received: {})",  | 
 | 809 | +				txid,  | 
 | 810 | +				sent,  | 
 | 811 | +				received  | 
 | 812 | +			);  | 
 | 813 | +			return Err(Error::InvalidPaymentId);  | 
 | 814 | +		}  | 
 | 815 | + | 
 | 816 | +		if old_payment.direction != PaymentDirection::Outbound {  | 
 | 817 | +			log_error!(self.logger, "Transaction {} is not an outbound payment", txid);  | 
 | 818 | +			return Err(Error::InvalidPaymentId);  | 
 | 819 | +		}  | 
 | 820 | + | 
 | 821 | +		if let PaymentKind::Onchain { status, .. } = &old_payment.kind {  | 
 | 822 | +			match status {  | 
 | 823 | +				ConfirmationStatus::Confirmed { .. } => {  | 
 | 824 | +					log_error!(  | 
 | 825 | +						self.logger,  | 
 | 826 | +						"Transaction {} is already confirmed and cannot be fee bumped",  | 
 | 827 | +						txid  | 
 | 828 | +					);  | 
 | 829 | +					return Err(Error::InvalidPaymentId);  | 
 | 830 | +				},  | 
 | 831 | +				ConfirmationStatus::Unconfirmed => {},  | 
 | 832 | +			}  | 
 | 833 | +		}  | 
 | 834 | + | 
 | 835 | +		let confirmation_target = ConfirmationTarget::OnchainPayment;  | 
 | 836 | +		let estimated_fee_rate = self.fee_estimator.estimate_fee_rate(confirmation_target);  | 
 | 837 | + | 
 | 838 | +		log_info!(self.logger, "Bumping fee to {}", estimated_fee_rate);  | 
 | 839 | + | 
 | 840 | +		let mut psbt = {  | 
 | 841 | +			let mut builder = locked_wallet.build_fee_bump(txid).map_err(|e| {  | 
 | 842 | +				log_error!(self.logger, "BDK fee bump failed for {}: {:?}", txid, e);  | 
 | 843 | +				match e {  | 
 | 844 | +					BuildFeeBumpError::TransactionNotFound(_) => Error::InvalidPaymentId,  | 
 | 845 | +					BuildFeeBumpError::TransactionConfirmed(_) => Error::InvalidPaymentId,  | 
 | 846 | +					BuildFeeBumpError::IrreplaceableTransaction(_) => Error::InvalidPaymentId,  | 
 | 847 | +					BuildFeeBumpError::FeeRateUnavailable => Error::InvalidPaymentId,  | 
 | 848 | +					_ => Error::InvalidFeeRate,  | 
 | 849 | +				}  | 
 | 850 | +			})?;  | 
 | 851 | + | 
 | 852 | +			builder.fee_rate(estimated_fee_rate);  | 
 | 853 | + | 
 | 854 | +			match builder.finish() {  | 
 | 855 | +				Ok(psbt) => Ok(psbt),  | 
 | 856 | +				Err(CreateTxError::FeeRateTooLow { required }) => {  | 
 | 857 | +					log_info!(self.logger, "BDK requires higher fee rate: {}", required);  | 
 | 858 | + | 
 | 859 | +					// Safety check  | 
 | 860 | +					const MAX_REASONABLE_FEE_RATE_SAT_VB: u64 = 1000;  | 
 | 861 | +					if required.to_sat_per_vb_ceil() > MAX_REASONABLE_FEE_RATE_SAT_VB {  | 
 | 862 | +						log_error!(  | 
 | 863 | +							self.logger,  | 
 | 864 | +							"BDK requires unreasonably high fee rate: {} sat/vB",  | 
 | 865 | +							required.to_sat_per_vb_ceil()  | 
 | 866 | +						);  | 
 | 867 | +						return Err(Error::InvalidFeeRate);  | 
 | 868 | +					}  | 
 | 869 | + | 
 | 870 | +					let mut builder = locked_wallet.build_fee_bump(txid).map_err(|e| {  | 
 | 871 | +						log_error!(self.logger, "BDK fee bump retry failed for {}: {:?}", txid, e);  | 
 | 872 | +						Error::InvalidFeeRate  | 
 | 873 | +					})?;  | 
 | 874 | + | 
 | 875 | +					builder.fee_rate(required);  | 
 | 876 | +					builder.finish().map_err(|e| {  | 
 | 877 | +						log_error!(  | 
 | 878 | +							self.logger,  | 
 | 879 | +							"Failed to finish PSBT with required fee rate: {:?}",  | 
 | 880 | +							e  | 
 | 881 | +						);  | 
 | 882 | +						Error::InvalidFeeRate  | 
 | 883 | +					})  | 
 | 884 | +				},  | 
 | 885 | +				Err(e) => {  | 
 | 886 | +					log_error!(self.logger, "Failed to create fee bump PSBT: {:?}", e);  | 
 | 887 | +					Err(Error::InvalidFeeRate)  | 
 | 888 | +				},  | 
 | 889 | +			}?  | 
 | 890 | +		};  | 
 | 891 | + | 
 | 892 | +		match locked_wallet.sign(&mut psbt, SignOptions::default()) {  | 
 | 893 | +			Ok(finalized) => {  | 
 | 894 | +				if !finalized {  | 
 | 895 | +					return Err(Error::OnchainTxCreationFailed);  | 
 | 896 | +				}  | 
 | 897 | +			},  | 
 | 898 | +			Err(err) => {  | 
 | 899 | +				log_error!(self.logger, "Failed to create transaction: {}", err);  | 
 | 900 | +				return Err(err.into());  | 
 | 901 | +			},  | 
 | 902 | +		}  | 
 | 903 | + | 
 | 904 | +		let mut locked_persister = self.persister.lock().unwrap();  | 
 | 905 | +		locked_wallet.persist(&mut locked_persister).map_err(|e| {  | 
 | 906 | +			log_error!(self.logger, "Failed to persist wallet: {}", e);  | 
 | 907 | +			Error::PersistenceFailed  | 
 | 908 | +		})?;  | 
 | 909 | + | 
 | 910 | +		let fee_bumped_tx = psbt.extract_tx().map_err(|e| {  | 
 | 911 | +			log_error!(self.logger, "Failed to extract transaction: {}", e);  | 
 | 912 | +			e  | 
 | 913 | +		})?;  | 
 | 914 | + | 
 | 915 | +		let new_txid = fee_bumped_tx.compute_txid();  | 
 | 916 | + | 
 | 917 | +		self.broadcaster.broadcast_transactions(&[&fee_bumped_tx]);  | 
 | 918 | + | 
 | 919 | +		let new_fee = locked_wallet.calculate_fee(&fee_bumped_tx).unwrap_or(Amount::ZERO);  | 
 | 920 | +		let new_fee_sats = new_fee.to_sat();  | 
 | 921 | + | 
 | 922 | +		let payment_details = PaymentDetails {  | 
 | 923 | +			id: PaymentId(new_txid.to_byte_array()),  | 
 | 924 | +			kind: PaymentKind::Onchain {  | 
 | 925 | +				txid: new_txid,  | 
 | 926 | +				status: ConfirmationStatus::Unconfirmed,  | 
 | 927 | +				raw_tx: Some(fee_bumped_tx),  | 
 | 928 | +				last_broadcast_time: Some(  | 
 | 929 | +					SystemTime::now()  | 
 | 930 | +						.duration_since(UNIX_EPOCH)  | 
 | 931 | +						.unwrap_or(Duration::from_secs(0))  | 
 | 932 | +						.as_secs(),  | 
 | 933 | +				),  | 
 | 934 | +				broadcast_attempts: Some(1),  | 
 | 935 | +			},  | 
 | 936 | +			amount_msat: old_payment.amount_msat,  | 
 | 937 | +			fee_paid_msat: Some(new_fee_sats * 1000),  | 
 | 938 | +			direction: old_payment.direction,  | 
 | 939 | +			status: PaymentStatus::Pending,  | 
 | 940 | +			latest_update_timestamp: SystemTime::now()  | 
 | 941 | +				.duration_since(UNIX_EPOCH)  | 
 | 942 | +				.unwrap_or(Duration::from_secs(0))  | 
 | 943 | +				.as_secs(),  | 
 | 944 | +		};  | 
 | 945 | + | 
 | 946 | +		self.payment_store.remove(&payment_id)?;  | 
 | 947 | + | 
 | 948 | +		self.payment_store.insert_or_update(payment_details)?;  | 
 | 949 | + | 
 | 950 | +		log_info!(self.logger, "RBF successful: replaced {} with {}", txid, new_txid);  | 
 | 951 | + | 
 | 952 | +		Ok(new_txid)  | 
 | 953 | +	}  | 
792 | 954 | }  | 
793 | 955 | 
 
  | 
794 | 956 | impl<B: Deref, E: Deref, L: Deref> Listen for Wallet<B, E, L>  | 
 | 
0 commit comments