@@ -16,7 +16,7 @@ use bitcoin::secp256k1::{self, Secp256k1, SecretKey};
16
16
use crate :: sign:: { EntropySource , NodeSigner , Recipient } ;
17
17
use crate :: events:: { self , PaymentFailureReason } ;
18
18
use crate :: ln:: { PaymentHash , PaymentPreimage , PaymentSecret } ;
19
- use crate :: ln:: channelmanager:: { ChannelDetails , EventCompletionAction , HTLCSource , IDEMPOTENCY_TIMEOUT_TICKS , PaymentId } ;
19
+ use crate :: ln:: channelmanager:: { ChannelDetails , EventCompletionAction , HTLCSource , IDEMPOTENCY_TIMEOUT_TICKS , INVOICE_REQUEST_TIMEOUT_TICKS , PaymentId } ;
20
20
use crate :: ln:: onion_utils:: { DecodedOnionFailure , HTLCFailReason } ;
21
21
use crate :: routing:: router:: { InFlightHtlcs , Path , PaymentParameters , Route , RouteParameters , Router } ;
22
22
use crate :: util:: errors:: APIError ;
@@ -38,6 +38,9 @@ pub(crate) enum PendingOutboundPayment {
38
38
Legacy {
39
39
session_privs : HashSet < [ u8 ; 32 ] > ,
40
40
} ,
41
+ AwaitingInvoice {
42
+ timer_ticks_without_response : u8 ,
43
+ } ,
41
44
Retryable {
42
45
retry_strategy : Option < Retry > ,
43
46
attempts : PaymentAttempts ,
@@ -108,6 +111,12 @@ impl PendingOutboundPayment {
108
111
params. previously_failed_channels . push ( scid) ;
109
112
}
110
113
}
114
+ fn is_awaiting_invoice ( & self ) -> bool {
115
+ match self {
116
+ PendingOutboundPayment :: AwaitingInvoice { .. } => true ,
117
+ _ => false ,
118
+ }
119
+ }
111
120
pub ( super ) fn is_fulfilled ( & self ) -> bool {
112
121
match self {
113
122
PendingOutboundPayment :: Fulfilled { .. } => true ,
@@ -130,6 +139,7 @@ impl PendingOutboundPayment {
130
139
fn payment_hash ( & self ) -> Option < PaymentHash > {
131
140
match self {
132
141
PendingOutboundPayment :: Legacy { .. } => None ,
142
+ PendingOutboundPayment :: AwaitingInvoice { .. } => None ,
133
143
PendingOutboundPayment :: Retryable { payment_hash, .. } => Some ( * payment_hash) ,
134
144
PendingOutboundPayment :: Fulfilled { payment_hash, .. } => * payment_hash,
135
145
PendingOutboundPayment :: Abandoned { payment_hash, .. } => Some ( * payment_hash) ,
@@ -142,8 +152,8 @@ impl PendingOutboundPayment {
142
152
PendingOutboundPayment :: Legacy { session_privs } |
143
153
PendingOutboundPayment :: Retryable { session_privs, .. } |
144
154
PendingOutboundPayment :: Fulfilled { session_privs, .. } |
145
- PendingOutboundPayment :: Abandoned { session_privs, .. }
146
- => session_privs ,
155
+ PendingOutboundPayment :: Abandoned { session_privs, .. } => session_privs ,
156
+ PendingOutboundPayment :: AwaitingInvoice { .. } => return ,
147
157
} ) ;
148
158
let payment_hash = self . payment_hash ( ) ;
149
159
* self = PendingOutboundPayment :: Fulfilled { session_privs, payment_hash, timer_ticks_without_htlcs : 0 } ;
@@ -169,7 +179,8 @@ impl PendingOutboundPayment {
169
179
PendingOutboundPayment :: Fulfilled { session_privs, .. } |
170
180
PendingOutboundPayment :: Abandoned { session_privs, .. } => {
171
181
session_privs. remove ( session_priv)
172
- }
182
+ } ,
183
+ PendingOutboundPayment :: AwaitingInvoice { .. } => false ,
173
184
} ;
174
185
if remove_res {
175
186
if let PendingOutboundPayment :: Retryable { ref mut pending_amt_msat, ref mut pending_fee_msat, .. } = self {
@@ -189,6 +200,7 @@ impl PendingOutboundPayment {
189
200
PendingOutboundPayment :: Retryable { session_privs, .. } => {
190
201
session_privs. insert ( session_priv)
191
202
}
203
+ PendingOutboundPayment :: AwaitingInvoice { .. } => false ,
192
204
PendingOutboundPayment :: Fulfilled { .. } => false ,
193
205
PendingOutboundPayment :: Abandoned { .. } => false ,
194
206
} ;
@@ -210,7 +222,8 @@ impl PendingOutboundPayment {
210
222
PendingOutboundPayment :: Fulfilled { session_privs, .. } |
211
223
PendingOutboundPayment :: Abandoned { session_privs, .. } => {
212
224
session_privs. len ( )
213
- }
225
+ } ,
226
+ PendingOutboundPayment :: AwaitingInvoice { .. } => 0 ,
214
227
}
215
228
}
216
229
}
@@ -679,7 +692,7 @@ impl OutboundPayments {
679
692
let mut outbounds = self . pending_outbound_payments . lock ( ) . unwrap ( ) ;
680
693
outbounds. retain ( |pmt_id, pmt| {
681
694
let mut retain = true ;
682
- if !pmt. is_auto_retryable_now ( ) && pmt. remaining_parts ( ) == 0 {
695
+ if !pmt. is_auto_retryable_now ( ) && pmt. remaining_parts ( ) == 0 && !pmt . is_awaiting_invoice ( ) {
683
696
pmt. mark_abandoned ( PaymentFailureReason :: RetriesExhausted ) ;
684
697
if let PendingOutboundPayment :: Abandoned { payment_hash, reason, .. } = pmt {
685
698
pending_events. lock ( ) . unwrap ( ) . push_back ( ( events:: Event :: PaymentFailed {
@@ -697,7 +710,8 @@ impl OutboundPayments {
697
710
pub ( super ) fn needs_abandon ( & self ) -> bool {
698
711
let outbounds = self . pending_outbound_payments . lock ( ) . unwrap ( ) ;
699
712
outbounds. iter ( ) . any ( |( _, pmt) |
700
- !pmt. is_auto_retryable_now ( ) && pmt. remaining_parts ( ) == 0 && !pmt. is_fulfilled ( ) )
713
+ !pmt. is_auto_retryable_now ( ) && pmt. remaining_parts ( ) == 0 && !pmt. is_fulfilled ( ) &&
714
+ !pmt. is_awaiting_invoice ( ) )
701
715
}
702
716
703
717
/// Errors immediately on [`RetryableSendFailure`] error conditions. Otherwise, further errors may
@@ -834,6 +848,10 @@ impl OutboundPayments {
834
848
log_error ! ( logger, "Unable to retry payments that were initially sent on LDK versions prior to 0.0.102" ) ;
835
849
return
836
850
} ,
851
+ PendingOutboundPayment :: AwaitingInvoice { .. } => {
852
+ log_error ! ( logger, "Payment not yet sent" ) ;
853
+ return
854
+ } ,
837
855
PendingOutboundPayment :: Fulfilled { .. } => {
838
856
log_error ! ( logger, "Payment already completed" ) ;
839
857
return
@@ -1006,36 +1024,58 @@ impl OutboundPayments {
1006
1024
keysend_preimage : Option < PaymentPreimage > , route : & Route , retry_strategy : Option < Retry > ,
1007
1025
payment_params : Option < PaymentParameters > , entropy_source : & ES , best_block_height : u32
1008
1026
) -> Result < Vec < [ u8 ; 32 ] > , PaymentSendFailure > where ES :: Target : EntropySource {
1027
+ let mut pending_outbounds = self . pending_outbound_payments . lock ( ) . unwrap ( ) ;
1028
+ let entry = pending_outbounds. entry ( payment_id) ;
1029
+ if let hash_map:: Entry :: Occupied ( entry) = & entry {
1030
+ if let PendingOutboundPayment :: AwaitingInvoice { .. } = entry. get ( ) { } else {
1031
+ return Err ( PaymentSendFailure :: DuplicatePayment )
1032
+ }
1033
+ }
1034
+
1009
1035
let mut onion_session_privs = Vec :: with_capacity ( route. paths . len ( ) ) ;
1010
1036
for _ in 0 ..route. paths . len ( ) {
1011
1037
onion_session_privs. push ( entropy_source. get_secure_random_bytes ( ) ) ;
1012
1038
}
1013
1039
1040
+ let mut payment = PendingOutboundPayment :: Retryable {
1041
+ retry_strategy,
1042
+ attempts : PaymentAttempts :: new ( ) ,
1043
+ payment_params,
1044
+ session_privs : HashSet :: new ( ) ,
1045
+ pending_amt_msat : 0 ,
1046
+ pending_fee_msat : Some ( 0 ) ,
1047
+ payment_hash,
1048
+ payment_secret : recipient_onion. payment_secret ,
1049
+ payment_metadata : recipient_onion. payment_metadata ,
1050
+ keysend_preimage,
1051
+ custom_tlvs : recipient_onion. custom_tlvs ,
1052
+ starting_block_height : best_block_height,
1053
+ total_msat : route. get_total_amount ( ) ,
1054
+ } ;
1055
+
1056
+ for ( path, session_priv_bytes) in route. paths . iter ( ) . zip ( onion_session_privs. iter ( ) ) {
1057
+ assert ! ( payment. insert( * session_priv_bytes, path) ) ;
1058
+ }
1059
+
1060
+ match entry {
1061
+ hash_map:: Entry :: Occupied ( mut entry) => { entry. insert ( payment) ; } ,
1062
+ hash_map:: Entry :: Vacant ( entry) => { entry. insert ( payment) ; } ,
1063
+ }
1064
+
1065
+ Ok ( onion_session_privs)
1066
+ }
1067
+
1068
+ #[ allow( unused) ]
1069
+ pub ( super ) fn add_new_awaiting_invoice ( & self , payment_id : PaymentId ) -> Result < ( ) , ( ) > {
1014
1070
let mut pending_outbounds = self . pending_outbound_payments . lock ( ) . unwrap ( ) ;
1015
1071
match pending_outbounds. entry ( payment_id) {
1016
- hash_map:: Entry :: Occupied ( _) => Err ( PaymentSendFailure :: DuplicatePayment ) ,
1072
+ hash_map:: Entry :: Occupied ( _) => Err ( ( ) ) ,
1017
1073
hash_map:: Entry :: Vacant ( entry) => {
1018
- let payment = entry. insert ( PendingOutboundPayment :: Retryable {
1019
- retry_strategy,
1020
- attempts : PaymentAttempts :: new ( ) ,
1021
- payment_params,
1022
- session_privs : HashSet :: new ( ) ,
1023
- pending_amt_msat : 0 ,
1024
- pending_fee_msat : Some ( 0 ) ,
1025
- payment_hash,
1026
- payment_secret : recipient_onion. payment_secret ,
1027
- payment_metadata : recipient_onion. payment_metadata ,
1028
- keysend_preimage,
1029
- custom_tlvs : recipient_onion. custom_tlvs ,
1030
- starting_block_height : best_block_height,
1031
- total_msat : route. get_total_amount ( ) ,
1074
+ entry. insert ( PendingOutboundPayment :: AwaitingInvoice {
1075
+ timer_ticks_without_response : 0 ,
1032
1076
} ) ;
1033
1077
1034
- for ( path, session_priv_bytes) in route. paths . iter ( ) . zip ( onion_session_privs. iter ( ) ) {
1035
- assert ! ( payment. insert( * session_priv_bytes, path) ) ;
1036
- }
1037
-
1038
- Ok ( onion_session_privs)
1078
+ Ok ( ( ) )
1039
1079
} ,
1040
1080
}
1041
1081
}
@@ -1244,19 +1284,19 @@ impl OutboundPayments {
1244
1284
}
1245
1285
}
1246
1286
1247
- pub ( super ) fn remove_stale_resolved_payments ( & self ,
1248
- pending_events : & Mutex < VecDeque < ( events:: Event , Option < EventCompletionAction > ) > > )
1287
+ pub ( super ) fn remove_stale_payments (
1288
+ & self , pending_events : & Mutex < VecDeque < ( events:: Event , Option < EventCompletionAction > ) > > )
1249
1289
{
1250
- // If an outbound payment was completed, and no pending HTLCs remain, we should remove it
1251
- // from the map. However, if we did that immediately when the last payment HTLC is claimed,
1252
- // this could race the user making a duplicate send_payment call and our idempotency
1253
- // guarantees would be violated. Instead, we wait a few timer ticks to do the actual
1254
- // removal. This should be more than sufficient to ensure the idempotency of any
1255
- // `send_payment` calls that were made at the same time the `PaymentSent` event was being
1256
- // processed.
1257
1290
let mut pending_outbound_payments = self . pending_outbound_payments . lock ( ) . unwrap ( ) ;
1258
- let pending_events = pending_events. lock ( ) . unwrap ( ) ;
1291
+ let mut pending_events = pending_events. lock ( ) . unwrap ( ) ;
1259
1292
pending_outbound_payments. retain ( |payment_id, payment| {
1293
+ // If an outbound payment was completed, and no pending HTLCs remain, we should remove it
1294
+ // from the map. However, if we did that immediately when the last payment HTLC is claimed,
1295
+ // this could race the user making a duplicate send_payment call and our idempotency
1296
+ // guarantees would be violated. Instead, we wait a few timer ticks to do the actual
1297
+ // removal. This should be more than sufficient to ensure the idempotency of any
1298
+ // `send_payment` calls that were made at the same time the `PaymentSent` event was being
1299
+ // processed.
1260
1300
if let PendingOutboundPayment :: Fulfilled { session_privs, timer_ticks_without_htlcs, .. } = payment {
1261
1301
let mut no_remaining_entries = session_privs. is_empty ( ) ;
1262
1302
if no_remaining_entries {
@@ -1281,6 +1321,16 @@ impl OutboundPayments {
1281
1321
* timer_ticks_without_htlcs = 0 ;
1282
1322
true
1283
1323
}
1324
+ } else if let PendingOutboundPayment :: AwaitingInvoice { timer_ticks_without_response, .. } = payment {
1325
+ * timer_ticks_without_response += 1 ;
1326
+ if * timer_ticks_without_response <= INVOICE_REQUEST_TIMEOUT_TICKS {
1327
+ true
1328
+ } else {
1329
+ pending_events. push_back (
1330
+ ( events:: Event :: InvoiceRequestFailed { payment_id : * payment_id } , None )
1331
+ ) ;
1332
+ false
1333
+ }
1284
1334
} else { true }
1285
1335
} ) ;
1286
1336
}
@@ -1489,6 +1539,9 @@ impl_writeable_tlv_based_enum_upgradable!(PendingOutboundPayment,
1489
1539
( 1 , reason, option) ,
1490
1540
( 2 , payment_hash, required) ,
1491
1541
} ,
1542
+ ( 5 , AwaitingInvoice ) => {
1543
+ ( 0 , timer_ticks_without_response, required) ,
1544
+ } ,
1492
1545
) ;
1493
1546
1494
1547
#[ cfg( test) ]
0 commit comments