@@ -12,6 +12,7 @@ use std::sync::{Arc, Mutex};
1212
1313use bdk_chain:: spk_client:: { FullScanRequest , SyncRequest } ;
1414use bdk_wallet:: descriptor:: ExtendedDescriptor ;
15+ use bdk_wallet:: error:: { BuildFeeBumpError , CreateTxError } ;
1516use bdk_wallet:: event:: WalletEvent ;
1617#[ allow( deprecated) ]
1718use bdk_wallet:: SignOptions ;
@@ -30,7 +31,9 @@ use bitcoin::{
3031 WitnessProgram , WitnessVersion ,
3132} ;
3233
33- use lightning:: chain:: chaininterface:: BroadcasterInterface ;
34+ use lightning:: chain:: chaininterface:: {
35+ BroadcasterInterface , INCREMENTAL_RELAY_FEE_SAT_PER_1000_WEIGHT ,
36+ } ;
3437use lightning:: chain:: channelmonitor:: ANTI_REORG_DELAY ;
3538use lightning:: chain:: { BestBlock , Listen } ;
3639use lightning:: events:: bump_transaction:: { Input , Utxo , WalletSource } ;
@@ -259,16 +262,23 @@ impl Wallet {
259262 confirmation_status,
260263 ) ;
261264
262- let pending_payment =
263- self . create_pending_payment_from_tx ( payment. clone ( ) , Vec :: new ( ) ) ;
265+ self . payment_store . insert_or_update ( payment. clone ( ) ) ?;
264266
265- self . payment_store . insert_or_update ( payment) ?;
266- self . pending_payment_store . insert_or_update ( pending_payment) ?;
267+ if payment_status == PaymentStatus :: Pending {
268+ let pending_payment =
269+ self . create_pending_payment_from_tx ( payment, Vec :: new ( ) ) ;
270+
271+ self . pending_payment_store . insert_or_update ( pending_payment) ?;
272+ }
267273 } ,
268274 WalletEvent :: ChainTipChanged { new_tip, .. } => {
269- // Get all on-chain payments that are Pending
270275 let pending_payments: Vec < PendingPaymentDetails > =
271276 self . pending_payment_store . list_filter ( |p| {
277+ debug_assert ! (
278+ p. details. status == PaymentStatus :: Pending ,
279+ "Non-pending payment {:?} found in pending store" ,
280+ p. details. id,
281+ ) ;
272282 p. details . status == PaymentStatus :: Pending
273283 && matches ! ( p. details. kind, PaymentKind :: Onchain { .. } )
274284 } ) ;
@@ -286,6 +296,11 @@ impl Wallet {
286296 payment. details . status = PaymentStatus :: Succeeded ;
287297 self . payment_store . insert_or_update ( payment. details ) ?;
288298 self . pending_payment_store . remove ( & payment_id) ?;
299+ debug_assert ! (
300+ !self . pending_payment_store. contains_key( & payment_id) ,
301+ "Payment {:?} still in pending store after removal" ,
302+ payment_id,
303+ ) ;
289304 }
290305 } ,
291306 PaymentKind :: Onchain {
@@ -307,7 +322,16 @@ impl Wallet {
307322 . collect ( ) ;
308323
309324 if !txs_to_broadcast. is_empty ( ) {
310- let tx_refs: Vec < & Transaction > = txs_to_broadcast. iter ( ) . collect ( ) ;
325+ let tx_refs: Vec < (
326+ & Transaction ,
327+ lightning:: chain:: chaininterface:: TransactionType ,
328+ ) > =
329+ txs_to_broadcast
330+ . iter ( )
331+ . map ( |tx| {
332+ ( tx, lightning:: chain:: chaininterface:: TransactionType :: Sweep { channels : vec ! [ ] } )
333+ } )
334+ . collect ( ) ;
311335 self . broadcaster . broadcast_transactions ( & tx_refs) ;
312336 log_info ! (
313337 self . logger,
@@ -335,7 +359,7 @@ impl Wallet {
335359 self . payment_store . insert_or_update ( payment) ?;
336360 self . pending_payment_store . insert_or_update ( pending_payment) ?;
337361 } ,
338- WalletEvent :: TxReplaced { txid, conflicts, tx , .. } => {
362+ WalletEvent :: TxReplaced { txid, conflicts, .. } => {
339363 let payment_id = self
340364 . find_payment_by_txid ( txid)
341365 . unwrap_or_else ( || PaymentId ( txid. to_byte_array ( ) ) ) ;
@@ -344,14 +368,14 @@ impl Wallet {
344368 let conflict_txids: Vec < Txid > =
345369 conflicts. iter ( ) . map ( |( _, conflict_txid) | * conflict_txid) . collect ( ) ;
346370
347- let payment = self . create_payment_from_tx (
348- locked_wallet,
349- txid,
371+ // We fetch payment details here since the replacement has updated the stored state
372+ debug_assert ! (
373+ self . payment_store. get( & payment_id) . is_some( ) ,
374+ "Payment {:?} expected in store during WalletEvent::TxReplaced but not found" ,
350375 payment_id,
351- & tx,
352- PaymentStatus :: Pending ,
353- ConfirmationStatus :: Unconfirmed ,
354376 ) ;
377+ let payment =
378+ self . payment_store . get ( & payment_id) . ok_or ( Error :: InvalidPaymentId ) ?;
355379 let pending_payment_details = self
356380 . create_pending_payment_from_tx ( payment. clone ( ) , conflict_txids. clone ( ) ) ;
357381
@@ -1026,6 +1050,175 @@ impl Wallet {
10261050
10271051 None
10281052 }
1053+
1054+ #[ allow( deprecated) ]
1055+ pub ( crate ) fn bump_fee_rbf (
1056+ & self , payment_id : PaymentId , fee_rate : Option < FeeRate > ,
1057+ ) -> Result < Txid , Error > {
1058+ let payment = self . payment_store . get ( & payment_id) . ok_or ( Error :: InvalidPaymentId ) ?;
1059+
1060+ if let PaymentKind :: Onchain { status, .. } = & payment. kind {
1061+ match status {
1062+ ConfirmationStatus :: Confirmed { .. } => {
1063+ log_error ! (
1064+ self . logger,
1065+ "Transaction {} is already confirmed and cannot be fee bumped" ,
1066+ payment_id
1067+ ) ;
1068+ return Err ( Error :: InvalidPaymentId ) ;
1069+ } ,
1070+ ConfirmationStatus :: Unconfirmed => { } ,
1071+ }
1072+ }
1073+
1074+ if payment. direction != PaymentDirection :: Outbound {
1075+ log_error ! ( self . logger, "Transaction {} is not an outbound payment" , payment_id) ;
1076+ return Err ( Error :: InvalidPaymentId ) ;
1077+ }
1078+
1079+ let txid = match & payment. kind {
1080+ PaymentKind :: Onchain { txid, .. } => * txid,
1081+ _ => return Err ( Error :: InvalidPaymentId ) ,
1082+ } ;
1083+
1084+ let mut locked_wallet = self . inner . lock ( ) . unwrap ( ) ;
1085+
1086+ debug_assert ! (
1087+ locked_wallet. tx_details( txid) . is_some( ) ,
1088+ "Transaction {} expected in wallet but not found" ,
1089+ txid,
1090+ ) ;
1091+ let old_tx =
1092+ locked_wallet. tx_details ( txid) . ok_or ( Error :: InvalidPaymentId ) ?. tx . deref ( ) . clone ( ) ;
1093+
1094+ let old_fee_rate = locked_wallet. calculate_fee_rate ( & old_tx) . map_err ( |e| {
1095+ log_error ! ( self . logger, "Failed to calculate fee rate of transaction {}: {}" , txid, e) ;
1096+ Error :: InvalidPaymentId
1097+ } ) ?;
1098+ let old_fee_rate_sat_per_kwu = old_fee_rate. to_sat_per_kwu ( ) ;
1099+
1100+ // BIP 125 requires the replacement to pay a higher fee rate than the original.
1101+ // The minimum increase is the incremental relay fee.
1102+ let min_required_fee_rate_sat_per_kwu =
1103+ old_fee_rate_sat_per_kwu + INCREMENTAL_RELAY_FEE_SAT_PER_1000_WEIGHT as u64 ;
1104+
1105+ let confirmation_target = ConfirmationTarget :: OnchainPayment ;
1106+ let estimated_fee_rate =
1107+ fee_rate. unwrap_or_else ( || self . fee_estimator . estimate_fee_rate ( confirmation_target) ) ;
1108+
1109+ // Use the higher of minimum RBF requirement or current network estimate
1110+ let final_fee_rate_sat_per_kwu =
1111+ min_required_fee_rate_sat_per_kwu. max ( estimated_fee_rate. to_sat_per_kwu ( ) ) ;
1112+ let final_fee_rate = FeeRate :: from_sat_per_kwu ( final_fee_rate_sat_per_kwu) ;
1113+
1114+ let mut psbt = {
1115+ let mut builder = locked_wallet. build_fee_bump ( txid) . map_err ( |e| {
1116+ log_error ! ( self . logger, "BDK fee bump failed for {}: {:?}" , txid, e) ;
1117+ match e {
1118+ BuildFeeBumpError :: TransactionNotFound ( _) => Error :: InvalidPaymentId ,
1119+ BuildFeeBumpError :: TransactionConfirmed ( _) => {
1120+ log_error ! ( self . logger, "Payment {} is already confirmed" , payment_id) ;
1121+ Error :: InvalidPaymentId
1122+ } ,
1123+ BuildFeeBumpError :: IrreplaceableTransaction ( _) => {
1124+ Error :: OnchainTxCreationFailed
1125+ } ,
1126+ BuildFeeBumpError :: FeeRateUnavailable => Error :: FeerateEstimationUpdateFailed ,
1127+ BuildFeeBumpError :: UnknownUtxo ( _) => Error :: OnchainTxCreationFailed ,
1128+ BuildFeeBumpError :: InvalidOutputIndex ( _) => Error :: OnchainTxCreationFailed ,
1129+ }
1130+ } ) ?;
1131+
1132+ builder. fee_rate ( final_fee_rate) ;
1133+
1134+ match builder. finish ( ) {
1135+ Ok ( psbt) => Ok ( psbt) ,
1136+ Err ( CreateTxError :: FeeRateTooLow { required : required_fee_rate } ) => {
1137+ log_info ! ( self . logger, "BDK requires higher fee rate: {}" , required_fee_rate) ;
1138+
1139+ // BDK may require a higher fee rate than our estimate due to
1140+ // differences in UTXO selection or transaction weight calculations.
1141+ // We cap the retry at 1.5x our target fee rate as a safety bound
1142+ // to avoid overpaying.
1143+ let max_allowed_fee_rate = FeeRate :: from_sat_per_kwu (
1144+ final_fee_rate_sat_per_kwu. saturating_mul ( 3 ) . saturating_div ( 2 ) ,
1145+ ) ;
1146+ if required_fee_rate > max_allowed_fee_rate {
1147+ log_error ! ( self . logger, "BDK required fee rate {} exceeds sanity cap {} (1.5x our estimate) for tx {}" , required_fee_rate, max_allowed_fee_rate, txid ) ;
1148+ return Err ( Error :: InvalidFeeRate ) ;
1149+ }
1150+
1151+ let mut builder = locked_wallet. build_fee_bump ( txid) . map_err ( |e| {
1152+ log_error ! ( self . logger, "BDK fee bump retry failed for {}: {:?}" , txid, e) ;
1153+ Error :: InvalidFeeRate
1154+ } ) ?;
1155+
1156+ builder. fee_rate ( required_fee_rate) ;
1157+ builder. finish ( ) . map_err ( |e| {
1158+ log_error ! (
1159+ self . logger,
1160+ "Failed to finish PSBT with required fee rate: {:?}" ,
1161+ e
1162+ ) ;
1163+ Error :: InvalidFeeRate
1164+ } )
1165+ } ,
1166+ Err ( e) => {
1167+ log_error ! ( self . logger, "Failed to create fee bump PSBT: {:?}" , e) ;
1168+ Err ( Error :: InvalidFeeRate )
1169+ } ,
1170+ } ?
1171+ } ;
1172+
1173+ match locked_wallet. sign ( & mut psbt, SignOptions :: default ( ) ) {
1174+ Ok ( finalized) => {
1175+ if !finalized {
1176+ return Err ( Error :: OnchainTxCreationFailed ) ;
1177+ }
1178+ } ,
1179+ Err ( err) => {
1180+ log_error ! ( self . logger, "Failed to create transaction: {}" , err) ;
1181+ return Err ( err. into ( ) ) ;
1182+ } ,
1183+ }
1184+
1185+ let mut locked_persister = self . persister . lock ( ) . unwrap ( ) ;
1186+ locked_wallet. persist ( & mut locked_persister) . map_err ( |e| {
1187+ log_error ! ( self . logger, "Failed to persist wallet: {}" , e) ;
1188+ Error :: PersistenceFailed
1189+ } ) ?;
1190+
1191+ let fee_bumped_tx = psbt. extract_tx ( ) . map_err ( |e| {
1192+ log_error ! ( self . logger, "Failed to extract transaction: {}" , e) ;
1193+ e
1194+ } ) ?;
1195+
1196+ let new_txid = fee_bumped_tx. compute_txid ( ) ;
1197+
1198+ self . broadcaster . broadcast_transactions ( & [ (
1199+ & fee_bumped_tx,
1200+ lightning:: chain:: chaininterface:: TransactionType :: Sweep { channels : vec ! [ ] } ,
1201+ ) ] ) ;
1202+
1203+ let new_payment = self . create_payment_from_tx (
1204+ & locked_wallet,
1205+ new_txid,
1206+ payment. id ,
1207+ & fee_bumped_tx,
1208+ PaymentStatus :: Pending ,
1209+ ConfirmationStatus :: Unconfirmed ,
1210+ ) ;
1211+
1212+ let pending_payment_store =
1213+ self . create_pending_payment_from_tx ( new_payment. clone ( ) , Vec :: new ( ) ) ;
1214+
1215+ self . pending_payment_store . insert_or_update ( pending_payment_store) ?;
1216+ self . payment_store . insert_or_update ( new_payment) ?;
1217+
1218+ log_info ! ( self . logger, "RBF successful: replaced {} with {}" , txid, new_txid) ;
1219+
1220+ Ok ( new_txid)
1221+ }
10291222}
10301223
10311224impl Listen for Wallet {
0 commit comments