Skip to content

Commit 2b37d15

Browse files
committed
Introduce RetryableInvoiceRequest in AwaitingInvoice
1. To enable the retry of the Invoice Request message, it's necessary to store the essential data required to recreate the message. 2. A new struct is introduced to manage this data, ensuring the InvoiceRequest message can be reliably recreated for retries. 3. The addition of an `awaiting_invoice` flag allows tracking of retryable invoice requests, preventing the need to lock the `pending_outbound_payment` mutex.
1 parent 680d399 commit 2b37d15

File tree

3 files changed

+90
-32
lines changed

3 files changed

+90
-32
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ use crate::ln::onion_utils::{HTLCFailReason, INVALID_ONION_BLINDING};
6060
use crate::ln::msgs::{ChannelMessageHandler, DecodeError, LightningError};
6161
#[cfg(test)]
6262
use crate::ln::outbound_payment;
63-
use crate::ln::outbound_payment::{OutboundPayments, PaymentAttempts, PendingOutboundPayment, SendAlongPathArgs, StaleExpiration};
63+
use crate::ln::outbound_payment::{OutboundPayments, PaymentAttempts, PendingOutboundPayment, RetryableInvoiceRequest, SendAlongPathArgs, StaleExpiration};
6464
use crate::ln::wire::Encode;
6565
use crate::offers::invoice::{Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, DerivedSigningPubkey, ExplicitSigningPubkey, InvoiceBuilder, UnsignedBolt12Invoice};
6666
use crate::offers::invoice_error::InvoiceError;
@@ -3070,7 +3070,7 @@ where
30703070

30713071
outbound_scid_aliases: Mutex::new(new_hash_set()),
30723072
pending_inbound_payments: Mutex::new(new_hash_map()),
3073-
pending_outbound_payments: OutboundPayments::new(),
3073+
pending_outbound_payments: OutboundPayments::new(new_hash_map()),
30743074
forward_htlcs: Mutex::new(new_hash_map()),
30753075
decode_update_add_htlcs: Mutex::new(new_hash_map()),
30763076
claimable_payments: Mutex::new(ClaimablePayments { claimable_payments: new_hash_map(), pending_claiming_payments: new_hash_map() }),
@@ -8913,7 +8913,7 @@ macro_rules! create_refund_builder { ($self: ident, $builder: ty) => {
89138913

89148914
let nonce = Nonce::from_entropy_source(entropy);
89158915
let context = OffersContext::OutboundPayment { payment_id, nonce, hmac: None };
8916-
let path = $self.create_blinded_paths_using_absolute_expiry(context, Some(absolute_expiry))
8916+
let path = $self.create_blinded_paths_using_absolute_expiry(context.clone(), Some(absolute_expiry))
89178917
.and_then(|paths| paths.into_iter().next().ok_or(()))
89188918
.map_err(|_| Bolt12SemanticError::MissingPaths)?;
89198919

@@ -8929,7 +8929,7 @@ macro_rules! create_refund_builder { ($self: ident, $builder: ty) => {
89298929
let expiration = StaleExpiration::AbsoluteTimeout(absolute_expiry);
89308930
$self.pending_outbound_payments
89318931
.add_new_awaiting_invoice(
8932-
payment_id, expiration, retry_strategy, max_total_routing_fee_msat,
8932+
payment_id, expiration, retry_strategy, max_total_routing_fee_msat, None,
89338933
)
89348934
.map_err(|_| Bolt12SemanticError::DuplicatePaymentId)?;
89358935

@@ -9055,9 +9055,14 @@ where
90559055
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
90569056

90579057
let expiration = StaleExpiration::TimerTicks(1);
9058+
let retryable_invoice_request = RetryableInvoiceRequest {
9059+
invoice_request: invoice_request.clone(),
9060+
nonce,
9061+
};
90589062
self.pending_outbound_payments
90599063
.add_new_awaiting_invoice(
9060-
payment_id, expiration, retry_strategy, max_total_routing_fee_msat
9064+
payment_id, expiration, retry_strategy, max_total_routing_fee_msat,
9065+
Some(retryable_invoice_request)
90619066
)
90629067
.map_err(|_| Bolt12SemanticError::DuplicatePaymentId)?;
90639068

@@ -12139,10 +12144,7 @@ where
1213912144
}
1214012145
pending_outbound_payments = Some(outbounds);
1214112146
}
12142-
let pending_outbounds = OutboundPayments {
12143-
pending_outbound_payments: Mutex::new(pending_outbound_payments.unwrap()),
12144-
retry_lock: Mutex::new(())
12145-
};
12147+
let pending_outbounds = OutboundPayments::new(pending_outbound_payments.unwrap());
1214612148

1214712149
// We have to replay (or skip, if they were completed after we wrote the `ChannelManager`)
1214812150
// each `ChannelMonitorUpdate` in `in_flight_monitor_updates`. After doing so, we have to

lightning/src/ln/outbound_payment.rs

Lines changed: 72 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ use crate::ln::features::Bolt12InvoiceFeatures;
2222
use crate::ln::onion_utils;
2323
use crate::ln::onion_utils::{DecodedOnionFailure, HTLCFailReason};
2424
use crate::offers::invoice::Bolt12Invoice;
25+
use crate::offers::invoice_request::InvoiceRequest;
26+
use crate::offers::nonce::Nonce;
2527
use crate::routing::router::{BlindedTail, InFlightHtlcs, Path, PaymentParameters, Route, RouteParameters, Router};
2628
use crate::sign::{EntropySource, NodeSigner, Recipient};
2729
use crate::util::errors::APIError;
@@ -33,6 +35,7 @@ use crate::util::ser::ReadableArgs;
3335

3436
use core::fmt::{self, Display, Formatter};
3537
use core::ops::Deref;
38+
use core::sync::atomic::{AtomicBool, Ordering};
3639
use core::time::Duration;
3740

3841
use crate::prelude::*;
@@ -54,6 +57,7 @@ pub(crate) enum PendingOutboundPayment {
5457
expiration: StaleExpiration,
5558
retry_strategy: Retry,
5659
max_total_routing_fee_msat: Option<u64>,
60+
retryable_invoice_request: Option<RetryableInvoiceRequest>
5761
},
5862
InvoiceReceived {
5963
payment_hash: PaymentHash,
@@ -101,6 +105,16 @@ pub(crate) enum PendingOutboundPayment {
101105
},
102106
}
103107

108+
pub(crate) struct RetryableInvoiceRequest {
109+
pub(crate) invoice_request: InvoiceRequest,
110+
pub(crate) nonce: Nonce,
111+
}
112+
113+
impl_writeable_tlv_based!(RetryableInvoiceRequest, {
114+
(0, invoice_request, required),
115+
(2, nonce, required),
116+
});
117+
104118
impl PendingOutboundPayment {
105119
fn increment_attempts(&mut self) {
106120
if let PendingOutboundPayment::Retryable { attempts, .. } = self {
@@ -684,13 +698,19 @@ pub(super) struct SendAlongPathArgs<'a> {
684698

685699
pub(super) struct OutboundPayments {
686700
pub(super) pending_outbound_payments: Mutex<HashMap<PaymentId, PendingOutboundPayment>>,
687-
pub(super) retry_lock: Mutex<()>,
701+
awaiting_invoice: AtomicBool,
702+
retry_lock: Mutex<()>,
688703
}
689704

690705
impl OutboundPayments {
691-
pub(super) fn new() -> Self {
706+
pub(super) fn new(pending_outbound_payments: HashMap<PaymentId, PendingOutboundPayment>) -> Self {
707+
let has_invoice_requests = pending_outbound_payments.values().any(|payment| {
708+
matches!(payment, PendingOutboundPayment::AwaitingInvoice { retryable_invoice_request: Some(_), .. })
709+
});
710+
692711
Self {
693-
pending_outbound_payments: Mutex::new(new_hash_map()),
712+
pending_outbound_payments: Mutex::new(pending_outbound_payments),
713+
awaiting_invoice: AtomicBool::new(has_invoice_requests),
694714
retry_lock: Mutex::new(()),
695715
}
696716
}
@@ -1411,7 +1431,7 @@ impl OutboundPayments {
14111431

14121432
pub(super) fn add_new_awaiting_invoice(
14131433
&self, payment_id: PaymentId, expiration: StaleExpiration, retry_strategy: Retry,
1414-
max_total_routing_fee_msat: Option<u64>
1434+
max_total_routing_fee_msat: Option<u64>, retryable_invoice_request: Option<RetryableInvoiceRequest>
14151435
) -> Result<(), ()> {
14161436
let mut pending_outbounds = self.pending_outbound_payments.lock().unwrap();
14171437
match pending_outbounds.entry(payment_id) {
@@ -1421,7 +1441,9 @@ impl OutboundPayments {
14211441
expiration,
14221442
retry_strategy,
14231443
max_total_routing_fee_msat,
1444+
retryable_invoice_request,
14241445
});
1446+
self.awaiting_invoice.store(true, Ordering::Release);
14251447

14261448
Ok(())
14271449
},
@@ -1892,6 +1914,31 @@ impl OutboundPayments {
18921914
pub fn clear_pending_payments(&self) {
18931915
self.pending_outbound_payments.lock().unwrap().clear()
18941916
}
1917+
1918+
pub fn release_invoice_requests_awaiting_invoice(&self) -> Vec<(PaymentId, RetryableInvoiceRequest)> {
1919+
if !self.awaiting_invoice.load(Ordering::Acquire) {
1920+
return vec![];
1921+
}
1922+
1923+
let mut pending_outbound_payments = self.pending_outbound_payments.lock().unwrap();
1924+
let invoice_requests = pending_outbound_payments
1925+
.iter_mut()
1926+
.filter_map(|(payment_id, payment)| {
1927+
if let PendingOutboundPayment::AwaitingInvoice {
1928+
retryable_invoice_request, ..
1929+
} = payment {
1930+
retryable_invoice_request.take().map(|retryable_invoice_request| {
1931+
(*payment_id, retryable_invoice_request)
1932+
})
1933+
} else {
1934+
None
1935+
}
1936+
})
1937+
.collect();
1938+
1939+
self.awaiting_invoice.store(false, Ordering::Release);
1940+
invoice_requests
1941+
}
18951942
}
18961943

18971944
/// Returns whether a payment with the given [`PaymentHash`] and [`PaymentId`] is, in fact, a
@@ -1947,6 +1994,7 @@ impl_writeable_tlv_based_enum_upgradable!(PendingOutboundPayment,
19471994
(0, expiration, required),
19481995
(2, retry_strategy, required),
19491996
(4, max_total_routing_fee_msat, option),
1997+
(5, retryable_invoice_request, option),
19501998
},
19511999
(7, InvoiceReceived) => {
19522000
(0, payment_hash, required),
@@ -1977,6 +2025,7 @@ mod tests {
19772025
use crate::routing::router::{InFlightHtlcs, Path, PaymentParameters, Route, RouteHop, RouteParameters};
19782026
use crate::sync::{Arc, Mutex, RwLock};
19792027
use crate::util::errors::APIError;
2028+
use crate::util::hash_tables::new_hash_map;
19802029
use crate::util::test_utils;
19812030

19822031
use alloc::collections::VecDeque;
@@ -2011,7 +2060,7 @@ mod tests {
20112060
}
20122061
#[cfg(feature = "std")]
20132062
fn do_fails_paying_after_expiration(on_retry: bool) {
2014-
let outbound_payments = OutboundPayments::new();
2063+
let outbound_payments = OutboundPayments::new(new_hash_map());
20152064
let logger = test_utils::TestLogger::new();
20162065
let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &logger));
20172066
let scorer = RwLock::new(test_utils::TestScorer::new());
@@ -2055,7 +2104,7 @@ mod tests {
20552104
do_find_route_error(true);
20562105
}
20572106
fn do_find_route_error(on_retry: bool) {
2058-
let outbound_payments = OutboundPayments::new();
2107+
let outbound_payments = OutboundPayments::new(new_hash_map());
20592108
let logger = test_utils::TestLogger::new();
20602109
let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &logger));
20612110
let scorer = RwLock::new(test_utils::TestScorer::new());
@@ -2094,7 +2143,7 @@ mod tests {
20942143

20952144
#[test]
20962145
fn initial_send_payment_path_failed_evs() {
2097-
let outbound_payments = OutboundPayments::new();
2146+
let outbound_payments = OutboundPayments::new(new_hash_map());
20982147
let logger = test_utils::TestLogger::new();
20992148
let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &logger));
21002149
let scorer = RwLock::new(test_utils::TestScorer::new());
@@ -2176,7 +2225,7 @@ mod tests {
21762225
#[test]
21772226
fn removes_stale_awaiting_invoice_using_absolute_timeout() {
21782227
let pending_events = Mutex::new(VecDeque::new());
2179-
let outbound_payments = OutboundPayments::new();
2228+
let outbound_payments = OutboundPayments::new(new_hash_map());
21802229
let payment_id = PaymentId([0; 32]);
21812230
let absolute_expiry = 100;
21822231
let tick_interval = 10;
@@ -2185,7 +2234,7 @@ mod tests {
21852234
assert!(!outbound_payments.has_pending_payments());
21862235
assert!(
21872236
outbound_payments.add_new_awaiting_invoice(
2188-
payment_id, expiration, Retry::Attempts(0), None
2237+
payment_id, expiration, Retry::Attempts(0), None, None,
21892238
).is_ok()
21902239
);
21912240
assert!(outbound_payments.has_pending_payments());
@@ -2215,30 +2264,30 @@ mod tests {
22152264

22162265
assert!(
22172266
outbound_payments.add_new_awaiting_invoice(
2218-
payment_id, expiration, Retry::Attempts(0), None
2267+
payment_id, expiration, Retry::Attempts(0), None, None,
22192268
).is_ok()
22202269
);
22212270
assert!(outbound_payments.has_pending_payments());
22222271

22232272
assert!(
22242273
outbound_payments.add_new_awaiting_invoice(
2225-
payment_id, expiration, Retry::Attempts(0), None
2274+
payment_id, expiration, Retry::Attempts(0), None, None,
22262275
).is_err()
22272276
);
22282277
}
22292278

22302279
#[test]
22312280
fn removes_stale_awaiting_invoice_using_timer_ticks() {
22322281
let pending_events = Mutex::new(VecDeque::new());
2233-
let outbound_payments = OutboundPayments::new();
2282+
let outbound_payments = OutboundPayments::new(new_hash_map());
22342283
let payment_id = PaymentId([0; 32]);
22352284
let timer_ticks = 3;
22362285
let expiration = StaleExpiration::TimerTicks(timer_ticks);
22372286

22382287
assert!(!outbound_payments.has_pending_payments());
22392288
assert!(
22402289
outbound_payments.add_new_awaiting_invoice(
2241-
payment_id, expiration, Retry::Attempts(0), None
2290+
payment_id, expiration, Retry::Attempts(0), None, None,
22422291
).is_ok()
22432292
);
22442293
assert!(outbound_payments.has_pending_payments());
@@ -2268,29 +2317,29 @@ mod tests {
22682317

22692318
assert!(
22702319
outbound_payments.add_new_awaiting_invoice(
2271-
payment_id, expiration, Retry::Attempts(0), None
2320+
payment_id, expiration, Retry::Attempts(0), None, None,
22722321
).is_ok()
22732322
);
22742323
assert!(outbound_payments.has_pending_payments());
22752324

22762325
assert!(
22772326
outbound_payments.add_new_awaiting_invoice(
2278-
payment_id, expiration, Retry::Attempts(0), None
2327+
payment_id, expiration, Retry::Attempts(0), None, None,
22792328
).is_err()
22802329
);
22812330
}
22822331

22832332
#[test]
22842333
fn removes_abandoned_awaiting_invoice() {
22852334
let pending_events = Mutex::new(VecDeque::new());
2286-
let outbound_payments = OutboundPayments::new();
2335+
let outbound_payments = OutboundPayments::new(new_hash_map());
22872336
let payment_id = PaymentId([0; 32]);
22882337
let expiration = StaleExpiration::AbsoluteTimeout(Duration::from_secs(100));
22892338

22902339
assert!(!outbound_payments.has_pending_payments());
22912340
assert!(
22922341
outbound_payments.add_new_awaiting_invoice(
2293-
payment_id, expiration, Retry::Attempts(0), None
2342+
payment_id, expiration, Retry::Attempts(0), None, None,
22942343
).is_ok()
22952344
);
22962345
assert!(outbound_payments.has_pending_payments());
@@ -2320,13 +2369,13 @@ mod tests {
23202369
let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet);
23212370

23222371
let pending_events = Mutex::new(VecDeque::new());
2323-
let outbound_payments = OutboundPayments::new();
2372+
let outbound_payments = OutboundPayments::new(new_hash_map());
23242373
let payment_id = PaymentId([0; 32]);
23252374
let expiration = StaleExpiration::AbsoluteTimeout(Duration::from_secs(100));
23262375

23272376
assert!(
23282377
outbound_payments.add_new_awaiting_invoice(
2329-
payment_id, expiration, Retry::Attempts(0), None
2378+
payment_id, expiration, Retry::Attempts(0), None, None,
23302379
).is_ok()
23312380
);
23322381
assert!(outbound_payments.has_pending_payments());
@@ -2373,7 +2422,7 @@ mod tests {
23732422
let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet);
23742423

23752424
let pending_events = Mutex::new(VecDeque::new());
2376-
let outbound_payments = OutboundPayments::new();
2425+
let outbound_payments = OutboundPayments::new(new_hash_map());
23772426
let payment_id = PaymentId([0; 32]);
23782427
let expiration = StaleExpiration::AbsoluteTimeout(Duration::from_secs(100));
23792428

@@ -2390,7 +2439,7 @@ mod tests {
23902439
assert!(
23912440
outbound_payments.add_new_awaiting_invoice(
23922441
payment_id, expiration, Retry::Attempts(0),
2393-
Some(invoice.amount_msats() / 100 + 50_000)
2442+
Some(invoice.amount_msats() / 100 + 50_000), None,
23942443
).is_ok()
23952444
);
23962445
assert!(outbound_payments.has_pending_payments());
@@ -2434,7 +2483,7 @@ mod tests {
24342483
let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet);
24352484

24362485
let pending_events = Mutex::new(VecDeque::new());
2437-
let outbound_payments = OutboundPayments::new();
2486+
let outbound_payments = OutboundPayments::new(new_hash_map());
24382487
let payment_id = PaymentId([0; 32]);
24392488
let expiration = StaleExpiration::AbsoluteTimeout(Duration::from_secs(100));
24402489

@@ -2490,7 +2539,7 @@ mod tests {
24902539

24912540
assert!(
24922541
outbound_payments.add_new_awaiting_invoice(
2493-
payment_id, expiration, Retry::Attempts(0), Some(1234)
2542+
payment_id, expiration, Retry::Attempts(0), Some(1234), None,
24942543
).is_ok()
24952544
);
24962545
assert!(outbound_payments.has_pending_payments());

lightning/src/offers/invoice_request.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1039,6 +1039,13 @@ impl Writeable for InvoiceRequestContents {
10391039
}
10401040
}
10411041

1042+
impl Readable for InvoiceRequest {
1043+
fn read<R: io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
1044+
let bytes: WithoutLength<Vec<u8>> = Readable::read(reader)?;
1045+
Self::try_from(bytes.0).map_err(|_| DecodeError::InvalidValue)
1046+
}
1047+
}
1048+
10421049
/// Valid type range for invoice_request TLV records.
10431050
pub(super) const INVOICE_REQUEST_TYPES: core::ops::Range<u64> = 80..160;
10441051

0 commit comments

Comments
 (0)