|
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