Skip to content

Commit 1059f5f

Browse files
authored
Merge pull request #3010 from shaavan/issue2836
Introduce Retry InvoiceRequest Flow
2 parents db905e8 + b1cd887 commit 1059f5f

File tree

8 files changed

+223
-35
lines changed

8 files changed

+223
-35
lines changed

lightning-net-tokio/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -786,6 +786,7 @@ mod tests {
786786
fn get_chain_hashes(&self) -> Option<Vec<ChainHash>> {
787787
Some(vec![ChainHash::using_genesis_block(Network::Testnet)])
788788
}
789+
fn message_received(&self) {}
789790
}
790791
impl MessageSendEventsProvider for MsgHandler {
791792
fn get_and_clear_pending_msg_events(&self) -> Vec<MessageSendEvent> {

lightning/src/ln/channelmanager.rs

+55-12
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,11 @@ use crate::ln::onion_utils::{HTLCFailReason, INVALID_ONION_BLINDING};
6161
use crate::ln::msgs::{ChannelMessageHandler, DecodeError, LightningError};
6262
#[cfg(test)]
6363
use crate::ln::outbound_payment;
64-
use crate::ln::outbound_payment::{OutboundPayments, PaymentAttempts, PendingOutboundPayment, SendAlongPathArgs, StaleExpiration};
64+
use crate::ln::outbound_payment::{OutboundPayments, PaymentAttempts, PendingOutboundPayment, RetryableInvoiceRequest, SendAlongPathArgs, StaleExpiration};
6565
use crate::ln::wire::Encode;
6666
use crate::offers::invoice::{Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, DerivedSigningPubkey, ExplicitSigningPubkey, InvoiceBuilder, UnsignedBolt12Invoice};
6767
use crate::offers::invoice_error::InvoiceError;
68-
use crate::offers::invoice_request::{DerivedPayerId, InvoiceRequestBuilder};
68+
use crate::offers::invoice_request::{DerivedPayerId, InvoiceRequest, InvoiceRequestBuilder};
6969
use crate::offers::nonce::Nonce;
7070
use crate::offers::offer::{Offer, OfferBuilder};
7171
use crate::offers::parse::Bolt12SemanticError;
@@ -3105,7 +3105,7 @@ where
31053105

31063106
outbound_scid_aliases: Mutex::new(new_hash_set()),
31073107
pending_inbound_payments: Mutex::new(new_hash_map()),
3108-
pending_outbound_payments: OutboundPayments::new(),
3108+
pending_outbound_payments: OutboundPayments::new(new_hash_map()),
31093109
forward_htlcs: Mutex::new(new_hash_map()),
31103110
decode_update_add_htlcs: Mutex::new(new_hash_map()),
31113111
claimable_payments: Mutex::new(ClaimablePayments { claimable_payments: new_hash_map(), pending_claiming_payments: new_hash_map() }),
@@ -9005,7 +9005,7 @@ macro_rules! create_refund_builder { ($self: ident, $builder: ty) => {
90059005
let expiration = StaleExpiration::AbsoluteTimeout(absolute_expiry);
90069006
$self.pending_outbound_payments
90079007
.add_new_awaiting_invoice(
9008-
payment_id, expiration, retry_strategy, max_total_routing_fee_msat,
9008+
payment_id, expiration, retry_strategy, max_total_routing_fee_msat, None,
90099009
)
90109010
.map_err(|_| Bolt12SemanticError::DuplicatePaymentId)?;
90119011

@@ -9131,17 +9131,30 @@ where
91319131
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
91329132

91339133
let expiration = StaleExpiration::TimerTicks(1);
9134+
let retryable_invoice_request = RetryableInvoiceRequest {
9135+
invoice_request: invoice_request.clone(),
9136+
nonce,
9137+
};
91349138
self.pending_outbound_payments
91359139
.add_new_awaiting_invoice(
9136-
payment_id, expiration, retry_strategy, max_total_routing_fee_msat
9140+
payment_id, expiration, retry_strategy, max_total_routing_fee_msat,
9141+
Some(retryable_invoice_request)
91379142
)
91389143
.map_err(|_| Bolt12SemanticError::DuplicatePaymentId)?;
91399144

9145+
self.enqueue_invoice_request(invoice_request, reply_paths)
9146+
}
9147+
9148+
fn enqueue_invoice_request(
9149+
&self,
9150+
invoice_request: InvoiceRequest,
9151+
reply_paths: Vec<BlindedMessagePath>,
9152+
) -> Result<(), Bolt12SemanticError> {
91409153
let mut pending_offers_messages = self.pending_offers_messages.lock().unwrap();
9141-
if !offer.paths().is_empty() {
9154+
if !invoice_request.paths().is_empty() {
91429155
reply_paths
91439156
.iter()
9144-
.flat_map(|reply_path| offer.paths().iter().map(move |path| (path, reply_path)))
9157+
.flat_map(|reply_path| invoice_request.paths().iter().map(move |path| (path, reply_path)))
91459158
.take(OFFERS_MESSAGE_REQUEST_LIMIT)
91469159
.for_each(|(path, reply_path)| {
91479160
let instructions = MessageSendInstructions::WithSpecifiedReplyPath {
@@ -9151,7 +9164,7 @@ where
91519164
let message = OffersMessage::InvoiceRequest(invoice_request.clone());
91529165
pending_offers_messages.push((message, instructions));
91539166
});
9154-
} else if let Some(signing_pubkey) = offer.signing_pubkey() {
9167+
} else if let Some(signing_pubkey) = invoice_request.signing_pubkey() {
91559168
for reply_path in reply_paths {
91569169
let instructions = MessageSendInstructions::WithSpecifiedReplyPath {
91579170
destination: Destination::Node(signing_pubkey),
@@ -10811,6 +10824,39 @@ where
1081110824
"Dual-funded channels not supported".to_owned(),
1081210825
msg.channel_id.clone())), counterparty_node_id);
1081310826
}
10827+
10828+
fn message_received(&self) {
10829+
for (payment_id, retryable_invoice_request) in self
10830+
.pending_outbound_payments
10831+
.release_invoice_requests_awaiting_invoice()
10832+
{
10833+
let RetryableInvoiceRequest { invoice_request, nonce } = retryable_invoice_request;
10834+
let hmac = payment_id.hmac_for_offer_payment(nonce, &self.inbound_payment_key);
10835+
let context = OffersContext::OutboundPayment {
10836+
payment_id,
10837+
nonce,
10838+
hmac: Some(hmac)
10839+
};
10840+
match self.create_blinded_paths(context) {
10841+
Ok(reply_paths) => match self.enqueue_invoice_request(invoice_request, reply_paths) {
10842+
Ok(_) => {}
10843+
Err(_) => {
10844+
log_warn!(self.logger,
10845+
"Retry failed for an invoice request with payment_id: {}",
10846+
payment_id
10847+
);
10848+
}
10849+
},
10850+
Err(_) => {
10851+
log_warn!(self.logger,
10852+
"Retry failed for an invoice request with payment_id: {}. \
10853+
Reason: router could not find a blinded path to include as the reply path",
10854+
payment_id
10855+
);
10856+
}
10857+
}
10858+
}
10859+
}
1081410860
}
1081510861

1081610862
impl<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref>
@@ -12227,10 +12273,7 @@ where
1222712273
}
1222812274
pending_outbound_payments = Some(outbounds);
1222912275
}
12230-
let pending_outbounds = OutboundPayments {
12231-
pending_outbound_payments: Mutex::new(pending_outbound_payments.unwrap()),
12232-
retry_lock: Mutex::new(())
12233-
};
12276+
let pending_outbounds = OutboundPayments::new(pending_outbound_payments.unwrap());
1223412277

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

lightning/src/ln/msgs.rs

+8
Original file line numberDiff line numberDiff line change
@@ -1605,6 +1605,14 @@ pub trait ChannelMessageHandler : MessageSendEventsProvider {
16051605
/// If it's `None`, then no particular network chain hash compatibility will be enforced when
16061606
/// connecting to peers.
16071607
fn get_chain_hashes(&self) -> Option<Vec<ChainHash>>;
1608+
1609+
/// Indicates that a message was received from any peer for any handler.
1610+
/// Called before the message is passed to the appropriate handler.
1611+
/// Useful for indicating that a network connection is active.
1612+
///
1613+
/// Note: Since this function is called frequently, it should be as
1614+
/// efficient as possible for its intended purpose.
1615+
fn message_received(&self);
16081616
}
16091617

16101618
/// A trait to describe an object which can receive routing messages.

lightning/src/ln/offers_tests.rs

+72
Original file line numberDiff line numberDiff line change
@@ -1070,6 +1070,78 @@ fn send_invoice_for_refund_with_distinct_reply_path() {
10701070
assert_eq!(reply_path.introduction_node(), &IntroductionNode::NodeId(nodes[6].node.get_our_node_id()));
10711071
}
10721072

1073+
/// Verifies that the invoice request message can be retried if it fails to reach the
1074+
/// payee on the first attempt.
1075+
#[test]
1076+
fn creates_and_pays_for_offer_with_retry() {
1077+
let chanmon_cfgs = create_chanmon_cfgs(2);
1078+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
1079+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
1080+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
1081+
1082+
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000);
1083+
1084+
let alice = &nodes[0];
1085+
let alice_id = alice.node.get_our_node_id();
1086+
let bob = &nodes[1];
1087+
let bob_id = bob.node.get_our_node_id();
1088+
1089+
let offer = alice.node
1090+
.create_offer_builder(None).unwrap()
1091+
.amount_msats(10_000_000)
1092+
.build().unwrap();
1093+
assert_ne!(offer.signing_pubkey(), Some(alice_id));
1094+
assert!(!offer.paths().is_empty());
1095+
for path in offer.paths() {
1096+
assert_eq!(path.introduction_node(), &IntroductionNode::NodeId(alice_id));
1097+
}
1098+
let payment_id = PaymentId([1; 32]);
1099+
bob.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None).unwrap();
1100+
expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id);
1101+
1102+
let _lost_onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap();
1103+
1104+
// Simulate a scenario where the original onion_message is lost before reaching Alice.
1105+
// Use handle_message_received to regenerate the message.
1106+
bob.node.message_received();
1107+
let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap();
1108+
1109+
alice.onion_messenger.handle_onion_message(bob_id, &onion_message);
1110+
1111+
let (invoice_request, reply_path) = extract_invoice_request(alice, &onion_message);
1112+
let payment_context = PaymentContext::Bolt12Offer(Bolt12OfferContext {
1113+
offer_id: offer.id(),
1114+
invoice_request: InvoiceRequestFields {
1115+
payer_id: invoice_request.payer_id(),
1116+
quantity: None,
1117+
payer_note_truncated: None,
1118+
},
1119+
});
1120+
assert_eq!(invoice_request.amount_msats(), None);
1121+
assert_ne!(invoice_request.payer_id(), bob_id);
1122+
assert_eq!(reply_path.introduction_node(), &IntroductionNode::NodeId(bob_id));
1123+
let onion_message = alice.onion_messenger.next_onion_message_for_peer(bob_id).unwrap();
1124+
bob.onion_messenger.handle_onion_message(alice_id, &onion_message);
1125+
1126+
// Expect no more OffersMessage to be enqueued by this point, even after calling
1127+
// handle_message_received.
1128+
bob.node.message_received();
1129+
1130+
assert!(bob.onion_messenger.next_onion_message_for_peer(alice_id).is_none());
1131+
1132+
let (invoice, _) = extract_invoice(bob, &onion_message);
1133+
assert_eq!(invoice.amount_msats(), 10_000_000);
1134+
assert_ne!(invoice.signing_pubkey(), alice_id);
1135+
assert!(!invoice.payment_paths().is_empty());
1136+
for path in invoice.payment_paths() {
1137+
assert_eq!(path.introduction_node(), &IntroductionNode::NodeId(alice_id));
1138+
}
1139+
route_bolt12_payment(bob, &[alice], &invoice);
1140+
expect_recent_payment!(bob, RecentPaymentDetails::Pending, payment_id);
1141+
claim_bolt12_payment(bob, &[alice], payment_context);
1142+
expect_recent_payment!(bob, RecentPaymentDetails::Fulfilled, payment_id);
1143+
}
1144+
10731145
/// Checks that a deferred invoice can be paid asynchronously from an Event::InvoiceReceived.
10741146
#[test]
10751147
fn pays_bolt12_invoice_asynchronously() {

0 commit comments

Comments
 (0)