Skip to content

Commit

Permalink
Merge pull request #3010 from shaavan/issue2836
Browse files Browse the repository at this point in the history
Introduce Retry InvoiceRequest Flow
  • Loading branch information
TheBlueMatt authored Sep 12, 2024
2 parents db905e8 + b1cd887 commit 1059f5f
Show file tree
Hide file tree
Showing 8 changed files with 223 additions and 35 deletions.
1 change: 1 addition & 0 deletions lightning-net-tokio/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -786,6 +786,7 @@ mod tests {
fn get_chain_hashes(&self) -> Option<Vec<ChainHash>> {
Some(vec![ChainHash::using_genesis_block(Network::Testnet)])
}
fn message_received(&self) {}
}
impl MessageSendEventsProvider for MsgHandler {
fn get_and_clear_pending_msg_events(&self) -> Vec<MessageSendEvent> {
Expand Down
67 changes: 55 additions & 12 deletions lightning/src/ln/channelmanager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,11 @@ use crate::ln::onion_utils::{HTLCFailReason, INVALID_ONION_BLINDING};
use crate::ln::msgs::{ChannelMessageHandler, DecodeError, LightningError};
#[cfg(test)]
use crate::ln::outbound_payment;
use crate::ln::outbound_payment::{OutboundPayments, PaymentAttempts, PendingOutboundPayment, SendAlongPathArgs, StaleExpiration};
use crate::ln::outbound_payment::{OutboundPayments, PaymentAttempts, PendingOutboundPayment, RetryableInvoiceRequest, SendAlongPathArgs, StaleExpiration};
use crate::ln::wire::Encode;
use crate::offers::invoice::{Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, DerivedSigningPubkey, ExplicitSigningPubkey, InvoiceBuilder, UnsignedBolt12Invoice};
use crate::offers::invoice_error::InvoiceError;
use crate::offers::invoice_request::{DerivedPayerId, InvoiceRequestBuilder};
use crate::offers::invoice_request::{DerivedPayerId, InvoiceRequest, InvoiceRequestBuilder};
use crate::offers::nonce::Nonce;
use crate::offers::offer::{Offer, OfferBuilder};
use crate::offers::parse::Bolt12SemanticError;
Expand Down Expand Up @@ -3105,7 +3105,7 @@ where

outbound_scid_aliases: Mutex::new(new_hash_set()),
pending_inbound_payments: Mutex::new(new_hash_map()),
pending_outbound_payments: OutboundPayments::new(),
pending_outbound_payments: OutboundPayments::new(new_hash_map()),
forward_htlcs: Mutex::new(new_hash_map()),
decode_update_add_htlcs: Mutex::new(new_hash_map()),
claimable_payments: Mutex::new(ClaimablePayments { claimable_payments: new_hash_map(), pending_claiming_payments: new_hash_map() }),
Expand Down Expand Up @@ -9005,7 +9005,7 @@ macro_rules! create_refund_builder { ($self: ident, $builder: ty) => {
let expiration = StaleExpiration::AbsoluteTimeout(absolute_expiry);
$self.pending_outbound_payments
.add_new_awaiting_invoice(
payment_id, expiration, retry_strategy, max_total_routing_fee_msat,
payment_id, expiration, retry_strategy, max_total_routing_fee_msat, None,
)
.map_err(|_| Bolt12SemanticError::DuplicatePaymentId)?;

Expand Down Expand Up @@ -9131,17 +9131,30 @@ where
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);

let expiration = StaleExpiration::TimerTicks(1);
let retryable_invoice_request = RetryableInvoiceRequest {
invoice_request: invoice_request.clone(),
nonce,
};
self.pending_outbound_payments
.add_new_awaiting_invoice(
payment_id, expiration, retry_strategy, max_total_routing_fee_msat
payment_id, expiration, retry_strategy, max_total_routing_fee_msat,
Some(retryable_invoice_request)
)
.map_err(|_| Bolt12SemanticError::DuplicatePaymentId)?;

self.enqueue_invoice_request(invoice_request, reply_paths)
}

fn enqueue_invoice_request(
&self,
invoice_request: InvoiceRequest,
reply_paths: Vec<BlindedMessagePath>,
) -> Result<(), Bolt12SemanticError> {
let mut pending_offers_messages = self.pending_offers_messages.lock().unwrap();
if !offer.paths().is_empty() {
if !invoice_request.paths().is_empty() {
reply_paths
.iter()
.flat_map(|reply_path| offer.paths().iter().map(move |path| (path, reply_path)))
.flat_map(|reply_path| invoice_request.paths().iter().map(move |path| (path, reply_path)))
.take(OFFERS_MESSAGE_REQUEST_LIMIT)
.for_each(|(path, reply_path)| {
let instructions = MessageSendInstructions::WithSpecifiedReplyPath {
Expand All @@ -9151,7 +9164,7 @@ where
let message = OffersMessage::InvoiceRequest(invoice_request.clone());
pending_offers_messages.push((message, instructions));
});
} else if let Some(signing_pubkey) = offer.signing_pubkey() {
} else if let Some(signing_pubkey) = invoice_request.signing_pubkey() {
for reply_path in reply_paths {
let instructions = MessageSendInstructions::WithSpecifiedReplyPath {
destination: Destination::Node(signing_pubkey),
Expand Down Expand Up @@ -10811,6 +10824,39 @@ where
"Dual-funded channels not supported".to_owned(),
msg.channel_id.clone())), counterparty_node_id);
}

fn message_received(&self) {
for (payment_id, retryable_invoice_request) in self
.pending_outbound_payments
.release_invoice_requests_awaiting_invoice()
{
let RetryableInvoiceRequest { invoice_request, nonce } = retryable_invoice_request;
let hmac = payment_id.hmac_for_offer_payment(nonce, &self.inbound_payment_key);
let context = OffersContext::OutboundPayment {
payment_id,
nonce,
hmac: Some(hmac)
};
match self.create_blinded_paths(context) {
Ok(reply_paths) => match self.enqueue_invoice_request(invoice_request, reply_paths) {
Ok(_) => {}
Err(_) => {
log_warn!(self.logger,
"Retry failed for an invoice request with payment_id: {}",
payment_id
);
}
},
Err(_) => {
log_warn!(self.logger,
"Retry failed for an invoice request with payment_id: {}. \
Reason: router could not find a blinded path to include as the reply path",
payment_id
);
}
}
}
}
}

impl<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref>
Expand Down Expand Up @@ -12227,10 +12273,7 @@ where
}
pending_outbound_payments = Some(outbounds);
}
let pending_outbounds = OutboundPayments {
pending_outbound_payments: Mutex::new(pending_outbound_payments.unwrap()),
retry_lock: Mutex::new(())
};
let pending_outbounds = OutboundPayments::new(pending_outbound_payments.unwrap());

// We have to replay (or skip, if they were completed after we wrote the `ChannelManager`)
// each `ChannelMonitorUpdate` in `in_flight_monitor_updates`. After doing so, we have to
Expand Down
8 changes: 8 additions & 0 deletions lightning/src/ln/msgs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1605,6 +1605,14 @@ pub trait ChannelMessageHandler : MessageSendEventsProvider {
/// If it's `None`, then no particular network chain hash compatibility will be enforced when
/// connecting to peers.
fn get_chain_hashes(&self) -> Option<Vec<ChainHash>>;

/// Indicates that a message was received from any peer for any handler.
/// Called before the message is passed to the appropriate handler.
/// Useful for indicating that a network connection is active.
///
/// Note: Since this function is called frequently, it should be as
/// efficient as possible for its intended purpose.
fn message_received(&self);
}

/// A trait to describe an object which can receive routing messages.
Expand Down
72 changes: 72 additions & 0 deletions lightning/src/ln/offers_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1070,6 +1070,78 @@ fn send_invoice_for_refund_with_distinct_reply_path() {
assert_eq!(reply_path.introduction_node(), &IntroductionNode::NodeId(nodes[6].node.get_our_node_id()));
}

/// Verifies that the invoice request message can be retried if it fails to reach the
/// payee on the first attempt.
#[test]
fn creates_and_pays_for_offer_with_retry() {
let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);

create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000);

let alice = &nodes[0];
let alice_id = alice.node.get_our_node_id();
let bob = &nodes[1];
let bob_id = bob.node.get_our_node_id();

let offer = alice.node
.create_offer_builder(None).unwrap()
.amount_msats(10_000_000)
.build().unwrap();
assert_ne!(offer.signing_pubkey(), Some(alice_id));
assert!(!offer.paths().is_empty());
for path in offer.paths() {
assert_eq!(path.introduction_node(), &IntroductionNode::NodeId(alice_id));
}
let payment_id = PaymentId([1; 32]);
bob.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None).unwrap();
expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id);

let _lost_onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap();

// Simulate a scenario where the original onion_message is lost before reaching Alice.
// Use handle_message_received to regenerate the message.
bob.node.message_received();
let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap();

alice.onion_messenger.handle_onion_message(bob_id, &onion_message);

let (invoice_request, reply_path) = extract_invoice_request(alice, &onion_message);
let payment_context = PaymentContext::Bolt12Offer(Bolt12OfferContext {
offer_id: offer.id(),
invoice_request: InvoiceRequestFields {
payer_id: invoice_request.payer_id(),
quantity: None,
payer_note_truncated: None,
},
});
assert_eq!(invoice_request.amount_msats(), None);
assert_ne!(invoice_request.payer_id(), bob_id);
assert_eq!(reply_path.introduction_node(), &IntroductionNode::NodeId(bob_id));
let onion_message = alice.onion_messenger.next_onion_message_for_peer(bob_id).unwrap();
bob.onion_messenger.handle_onion_message(alice_id, &onion_message);

// Expect no more OffersMessage to be enqueued by this point, even after calling
// handle_message_received.
bob.node.message_received();

assert!(bob.onion_messenger.next_onion_message_for_peer(alice_id).is_none());

let (invoice, _) = extract_invoice(bob, &onion_message);
assert_eq!(invoice.amount_msats(), 10_000_000);
assert_ne!(invoice.signing_pubkey(), alice_id);
assert!(!invoice.payment_paths().is_empty());
for path in invoice.payment_paths() {
assert_eq!(path.introduction_node(), &IntroductionNode::NodeId(alice_id));
}
route_bolt12_payment(bob, &[alice], &invoice);
expect_recent_payment!(bob, RecentPaymentDetails::Pending, payment_id);
claim_bolt12_payment(bob, &[alice], payment_context);
expect_recent_payment!(bob, RecentPaymentDetails::Fulfilled, payment_id);
}

/// Checks that a deferred invoice can be paid asynchronously from an Event::InvoiceReceived.
#[test]
fn pays_bolt12_invoice_asynchronously() {
Expand Down
Loading

0 comments on commit 1059f5f

Please sign in to comment.