Skip to content

Commit 3ec0dcd

Browse files
authored
Merge pull request #3078 from jkczyz/2024-05-invoice-event
Asynchronous `Bolt12Invoice` payment
2 parents f267ffe + 97c1d65 commit 3ec0dcd

File tree

8 files changed

+301
-96
lines changed

8 files changed

+301
-96
lines changed

fuzz/src/full_stack.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -870,7 +870,7 @@ mod tests {
870870
// our network key
871871
ext_from_hex("0100000000000000000000000000000000000000000000000000000000000000", &mut test);
872872
// config
873-
ext_from_hex("0000000000900000000000000000640001000000000001ffff0000000000000000ffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff000000ffffffff00ffff1a000400010000020400000000040200000a08ffffffffffffffff0001000000", &mut test);
873+
ext_from_hex("0000000000900000000000000000640001000000000001ffff0000000000000000ffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff000000ffffffff00ffff1a000400010000020400000000040200000a08ffffffffffffffff000100000000", &mut test);
874874

875875
// new outbound connection with id 0
876876
ext_from_hex("00", &mut test);
@@ -1383,7 +1383,7 @@ mod tests {
13831383
// our network key
13841384
ext_from_hex("0100000000000000000000000000000000000000000000000000000000000000", &mut test);
13851385
// config
1386-
ext_from_hex("0000000000900000000000000000640001000000000001ffff0000000000000000ffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff000000ffffffff00ffff1a000400010000020400000000040200000a08ffffffffffffffff0001000000", &mut test);
1386+
ext_from_hex("0000000000900000000000000000640001000000000001ffff0000000000000000ffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff000000ffffffff00ffff1a000400010000020400000000040200000a08ffffffffffffffff000100000000", &mut test);
13871387

13881388
// new outbound connection with id 0
13891389
ext_from_hex("00", &mut test);

lightning/src/events/mod.rs

+54-4
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,20 @@ pub mod bump_transaction;
1919
pub use bump_transaction::BumpTransactionEvent;
2020

2121
use crate::blinded_path::payment::{Bolt12OfferContext, Bolt12RefundContext, PaymentContext, PaymentContextRef};
22-
use crate::sign::SpendableOutputDescriptor;
22+
use crate::chain::transaction;
2323
use crate::ln::channelmanager::{InterceptId, PaymentId, RecipientOnionFields};
2424
use crate::ln::channel::FUNDING_CONF_DEADLINE_BLOCKS;
2525
use crate::ln::features::ChannelTypeFeatures;
2626
use crate::ln::msgs;
2727
use crate::ln::types::{ChannelId, PaymentPreimage, PaymentHash, PaymentSecret};
28-
use crate::chain::transaction;
28+
use crate::offers::invoice::Bolt12Invoice;
29+
use crate::onion_message::messenger::Responder;
2930
use crate::routing::gossip::NetworkUpdate;
31+
use crate::routing::router::{BlindedTail, Path, RouteHop, RouteParameters};
32+
use crate::sign::SpendableOutputDescriptor;
3033
use crate::util::errors::APIError;
3134
use crate::util::ser::{BigSize, FixedLengthReader, Writeable, Writer, MaybeReadable, Readable, RequiredWrapper, UpgradableRequired, WithoutLength};
3235
use crate::util::string::UntrustedString;
33-
use crate::routing::router::{BlindedTail, Path, RouteHop, RouteParameters};
3436

3537
use bitcoin::{Transaction, OutPoint};
3638
use bitcoin::blockdata::locktime::absolute::LockTime;
@@ -736,6 +738,31 @@ pub enum Event {
736738
/// The `payment_id` to have been associated with payment for the requested invoice.
737739
payment_id: PaymentId,
738740
},
741+
/// Indicates a [`Bolt12Invoice`] in response to an [`InvoiceRequest`] or a [`Refund`] was
742+
/// received.
743+
///
744+
/// This event will only be generated if [`UserConfig::manually_handle_bolt12_invoices`] is set.
745+
/// Use [`ChannelManager::send_payment_for_bolt12_invoice`] to pay the invoice or
746+
/// [`ChannelManager::abandon_payment`] to abandon the associated payment. See those docs for
747+
/// further details.
748+
///
749+
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
750+
/// [`Refund`]: crate::offers::refund::Refund
751+
/// [`UserConfig::manually_handle_bolt12_invoices`]: crate::util::config::UserConfig::manually_handle_bolt12_invoices
752+
/// [`ChannelManager::send_payment_for_bolt12_invoice`]: crate::ln::channelmanager::ChannelManager::send_payment_for_bolt12_invoice
753+
/// [`ChannelManager::abandon_payment`]: crate::ln::channelmanager::ChannelManager::abandon_payment
754+
InvoiceReceived {
755+
/// The `payment_id` associated with payment for the invoice.
756+
payment_id: PaymentId,
757+
/// The invoice to pay.
758+
invoice: Bolt12Invoice,
759+
/// A responder for replying with an [`InvoiceError`] if needed.
760+
///
761+
/// `None` if the invoice wasn't sent with a reply path.
762+
///
763+
/// [`InvoiceError`]: crate::offers::invoice_error::InvoiceError
764+
responder: Option<Responder>,
765+
},
739766
/// Indicates an outbound payment we made succeeded (i.e. it made it all the way to its target
740767
/// and we got back the payment preimage for it).
741768
///
@@ -1492,7 +1519,15 @@ impl Writeable for Event {
14921519
write_tlv_fields!(writer, {
14931520
(0, peer_node_id, required),
14941521
});
1495-
}
1522+
},
1523+
&Event::InvoiceReceived { ref payment_id, ref invoice, ref responder } => {
1524+
41u8.write(writer)?;
1525+
write_tlv_fields!(writer, {
1526+
(0, payment_id, required),
1527+
(2, invoice, required),
1528+
(4, responder, option),
1529+
})
1530+
},
14961531
// Note that, going forward, all new events must only write data inside of
14971532
// `write_tlv_fields`. Versions 0.0.101+ will ignore odd-numbered events that write
14981533
// data via `write_tlv_fields`.
@@ -1929,6 +1964,21 @@ impl MaybeReadable for Event {
19291964
};
19301965
f()
19311966
},
1967+
41u8 => {
1968+
let mut f = || {
1969+
_init_and_read_len_prefixed_tlv_fields!(reader, {
1970+
(0, payment_id, required),
1971+
(2, invoice, required),
1972+
(4, responder, option),
1973+
});
1974+
Ok(Some(Event::InvoiceReceived {
1975+
payment_id: payment_id.0.unwrap(),
1976+
invoice: invoice.0.unwrap(),
1977+
responder,
1978+
}))
1979+
};
1980+
f()
1981+
},
19321982
// Versions prior to 0.0.100 did not ignore odd types, instead returning InvalidValue.
19331983
// Version 0.0.100 failed to properly ignore odd types, possibly resulting in corrupt
19341984
// reads.

lightning/src/ln/channelmanager.rs

+54-22
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ use crate::ln::onion_utils::{HTLCFailReason, INVALID_ONION_BLINDING};
5858
use crate::ln::msgs::{ChannelMessageHandler, DecodeError, LightningError};
5959
#[cfg(test)]
6060
use crate::ln::outbound_payment;
61-
use crate::ln::outbound_payment::{Bolt12PaymentError, OutboundPayments, PaymentAttempts, PendingOutboundPayment, SendAlongPathArgs, StaleExpiration};
61+
use crate::ln::outbound_payment::{OutboundPayments, PaymentAttempts, PendingOutboundPayment, SendAlongPathArgs, StaleExpiration};
6262
use crate::ln::wire::Encode;
6363
use crate::offers::invoice::{BlindedPayInfo, Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, DerivedSigningPubkey, ExplicitSigningPubkey, InvoiceBuilder, UnsignedBolt12Invoice};
6464
use crate::offers::invoice_error::InvoiceError;
@@ -105,7 +105,7 @@ use core::time::Duration;
105105
use core::ops::Deref;
106106

107107
// Re-export this for use in the public API.
108-
pub use crate::ln::outbound_payment::{PaymentSendFailure, ProbeSendFailure, Retry, RetryableSendFailure, RecipientOnionFields};
108+
pub use crate::ln::outbound_payment::{Bolt12PaymentError, PaymentSendFailure, ProbeSendFailure, Retry, RetryableSendFailure, RecipientOnionFields};
109109
use crate::ln::script::ShutdownScript;
110110

111111
// We hold various information about HTLC relay in the HTLC objects in Channel itself:
@@ -3996,7 +3996,36 @@ where
39963996
self.pending_outbound_payments.test_set_payment_metadata(payment_id, new_payment_metadata);
39973997
}
39983998

3999-
pub(super) fn send_payment_for_bolt12_invoice(&self, invoice: &Bolt12Invoice, payment_id: PaymentId) -> Result<(), Bolt12PaymentError> {
3999+
/// Pays the [`Bolt12Invoice`] associated with the `payment_id` encoded in its `payer_metadata`.
4000+
///
4001+
/// The invoice's `payer_metadata` is used to authenticate that the invoice was indeed requested
4002+
/// before attempting a payment. [`Bolt12PaymentError::UnexpectedInvoice`] is returned if this
4003+
/// fails or if the encoded `payment_id` is not recognized. The latter may happen once the
4004+
/// payment is no longer tracked because the payment was attempted after:
4005+
/// - an invoice for the `payment_id` was already paid,
4006+
/// - one full [timer tick] has elapsed since initially requesting the invoice when paying an
4007+
/// offer, or
4008+
/// - the refund corresponding to the invoice has already expired.
4009+
///
4010+
/// To retry the payment, request another invoice using a new `payment_id`.
4011+
///
4012+
/// Attempting to pay the same invoice twice while the first payment is still pending will
4013+
/// result in a [`Bolt12PaymentError::DuplicateInvoice`].
4014+
///
4015+
/// Otherwise, either [`Event::PaymentSent`] or [`Event::PaymentFailed`] are used to indicate
4016+
/// whether or not the payment was successful.
4017+
///
4018+
/// [timer tick]: Self::timer_tick_occurred
4019+
pub fn send_payment_for_bolt12_invoice(&self, invoice: &Bolt12Invoice) -> Result<(), Bolt12PaymentError> {
4020+
let secp_ctx = &self.secp_ctx;
4021+
let expanded_key = &self.inbound_payment_key;
4022+
match invoice.verify(expanded_key, secp_ctx) {
4023+
Ok(payment_id) => self.send_payment_for_verified_bolt12_invoice(invoice, payment_id),
4024+
Err(()) => Err(Bolt12PaymentError::UnexpectedInvoice),
4025+
}
4026+
}
4027+
4028+
fn send_payment_for_verified_bolt12_invoice(&self, invoice: &Bolt12Invoice, payment_id: PaymentId) -> Result<(), Bolt12PaymentError> {
40004029
let best_block_height = self.best_block.read().unwrap().height;
40014030
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
40024031
self.pending_outbound_payments
@@ -10272,42 +10301,45 @@ where
1027210301
};
1027310302

1027410303
match response {
10275-
Ok(invoice) => return responder.respond(OffersMessage::Invoice(invoice)),
10276-
Err(error) => return responder.respond(OffersMessage::InvoiceError(error.into())),
10304+
Ok(invoice) => responder.respond(OffersMessage::Invoice(invoice)),
10305+
Err(error) => responder.respond(OffersMessage::InvoiceError(error.into())),
1027710306
}
1027810307
},
1027910308
OffersMessage::Invoice(invoice) => {
10280-
let response = invoice
10281-
.verify(expanded_key, secp_ctx)
10282-
.map_err(|()| InvoiceError::from_string("Unrecognized invoice".to_owned()))
10283-
.and_then(|payment_id| {
10309+
let result = match invoice.verify(expanded_key, secp_ctx) {
10310+
Ok(payment_id) => {
1028410311
let features = self.bolt12_invoice_features();
1028510312
if invoice.invoice_features().requires_unknown_bits_from(&features) {
1028610313
Err(InvoiceError::from(Bolt12SemanticError::UnknownRequiredFeatures))
10314+
} else if self.default_configuration.manually_handle_bolt12_invoices {
10315+
let event = Event::InvoiceReceived { payment_id, invoice, responder };
10316+
self.pending_events.lock().unwrap().push_back((event, None));
10317+
return ResponseInstruction::NoResponse;
1028710318
} else {
10288-
self.send_payment_for_bolt12_invoice(&invoice, payment_id)
10319+
self.send_payment_for_verified_bolt12_invoice(&invoice, payment_id)
1028910320
.map_err(|e| {
1029010321
log_trace!(self.logger, "Failed paying invoice: {:?}", e);
1029110322
InvoiceError::from_string(format!("{:?}", e))
1029210323
})
1029310324
}
10294-
});
10325+
},
10326+
Err(()) => Err(InvoiceError::from_string("Unrecognized invoice".to_owned())),
10327+
};
1029510328

10296-
match (responder, response) {
10297-
(Some(responder), Err(e)) => responder.respond(OffersMessage::InvoiceError(e)),
10298-
(None, Err(_)) => {
10299-
log_trace!(
10300-
self.logger,
10301-
"A response was generated, but there is no reply_path specified for sending the response."
10302-
);
10303-
return ResponseInstruction::NoResponse;
10304-
}
10305-
_ => return ResponseInstruction::NoResponse,
10329+
match result {
10330+
Ok(()) => ResponseInstruction::NoResponse,
10331+
Err(e) => match responder {
10332+
Some(responder) => responder.respond(OffersMessage::InvoiceError(e)),
10333+
None => {
10334+
log_trace!(self.logger, "No reply path for sending invoice error: {:?}", e);
10335+
ResponseInstruction::NoResponse
10336+
},
10337+
},
1030610338
}
1030710339
},
1030810340
OffersMessage::InvoiceError(invoice_error) => {
1030910341
log_trace!(self.logger, "Received invoice_error: {}", invoice_error);
10310-
return ResponseInstruction::NoResponse;
10342+
ResponseInstruction::NoResponse
1031110343
},
1031210344
}
1031310345
}

lightning/src/ln/offers_tests.rs

+86-1
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,10 @@ use core::time::Duration;
4646
use crate::blinded_path::{BlindedPath, IntroductionNode};
4747
use crate::blinded_path::payment::{Bolt12OfferContext, Bolt12RefundContext, PaymentContext};
4848
use crate::events::{Event, MessageSendEventsProvider, PaymentPurpose};
49-
use crate::ln::channelmanager::{MAX_SHORT_LIVED_RELATIVE_EXPIRY, PaymentId, RecentPaymentDetails, Retry, self};
49+
use crate::ln::channelmanager::{Bolt12PaymentError, MAX_SHORT_LIVED_RELATIVE_EXPIRY, PaymentId, RecentPaymentDetails, Retry, self};
5050
use crate::ln::functional_test_utils::*;
5151
use crate::ln::msgs::{ChannelMessageHandler, Init, NodeAnnouncement, OnionMessage, OnionMessageHandler, RoutingMessageHandler, SocketAddress, UnsignedGossipMessage, UnsignedNodeAnnouncement};
52+
use crate::ln::outbound_payment::IDEMPOTENCY_TIMEOUT_TICKS;
5253
use crate::offers::invoice::Bolt12Invoice;
5354
use crate::offers::invoice_error::InvoiceError;
5455
use crate::offers::invoice_request::{InvoiceRequest, InvoiceRequestFields};
@@ -865,6 +866,90 @@ fn pays_for_refund_without_blinded_paths() {
865866
expect_recent_payment!(bob, RecentPaymentDetails::Fulfilled, payment_id);
866867
}
867868

869+
/// Checks that a deferred invoice can be paid asynchronously from an Event::InvoiceReceived.
870+
#[test]
871+
fn pays_bolt12_invoice_asynchronously() {
872+
let mut manually_pay_cfg = test_default_channel_config();
873+
manually_pay_cfg.manually_handle_bolt12_invoices = true;
874+
875+
let chanmon_cfgs = create_chanmon_cfgs(2);
876+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
877+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, Some(manually_pay_cfg)]);
878+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
879+
880+
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000);
881+
882+
let alice = &nodes[0];
883+
let alice_id = alice.node.get_our_node_id();
884+
let bob = &nodes[1];
885+
let bob_id = bob.node.get_our_node_id();
886+
887+
let offer = alice.node
888+
.create_offer_builder(None).unwrap()
889+
.amount_msats(10_000_000)
890+
.build().unwrap();
891+
892+
let payment_id = PaymentId([1; 32]);
893+
bob.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None).unwrap();
894+
expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id);
895+
896+
let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap();
897+
alice.onion_messenger.handle_onion_message(&bob_id, &onion_message);
898+
899+
let (invoice_request, _) = extract_invoice_request(alice, &onion_message);
900+
let payment_context = PaymentContext::Bolt12Offer(Bolt12OfferContext {
901+
offer_id: offer.id(),
902+
invoice_request: InvoiceRequestFields {
903+
payer_id: invoice_request.payer_id(),
904+
quantity: None,
905+
payer_note_truncated: None,
906+
},
907+
});
908+
909+
let onion_message = alice.onion_messenger.next_onion_message_for_peer(bob_id).unwrap();
910+
bob.onion_messenger.handle_onion_message(&alice_id, &onion_message);
911+
912+
let invoice = match get_event!(bob, Event::InvoiceReceived) {
913+
Event::InvoiceReceived { payment_id: actual_payment_id, invoice, .. } => {
914+
assert_eq!(actual_payment_id, payment_id);
915+
invoice
916+
},
917+
_ => panic!("No Event::InvoiceReceived"),
918+
};
919+
assert_eq!(invoice.amount_msats(), 10_000_000);
920+
assert_ne!(invoice.signing_pubkey(), alice_id);
921+
assert!(!invoice.payment_paths().is_empty());
922+
for (_, path) in invoice.payment_paths() {
923+
assert_eq!(path.introduction_node, IntroductionNode::NodeId(alice_id));
924+
}
925+
926+
assert!(bob.node.send_payment_for_bolt12_invoice(&invoice).is_ok());
927+
assert_eq!(
928+
bob.node.send_payment_for_bolt12_invoice(&invoice),
929+
Err(Bolt12PaymentError::DuplicateInvoice),
930+
);
931+
932+
route_bolt12_payment(bob, &[alice], &invoice);
933+
expect_recent_payment!(bob, RecentPaymentDetails::Pending, payment_id);
934+
935+
claim_bolt12_payment(bob, &[alice], payment_context);
936+
expect_recent_payment!(bob, RecentPaymentDetails::Fulfilled, payment_id);
937+
938+
assert_eq!(
939+
bob.node.send_payment_for_bolt12_invoice(&invoice),
940+
Err(Bolt12PaymentError::DuplicateInvoice),
941+
);
942+
943+
for _ in 0..=IDEMPOTENCY_TIMEOUT_TICKS {
944+
bob.node.timer_tick_occurred();
945+
}
946+
947+
assert_eq!(
948+
bob.node.send_payment_for_bolt12_invoice(&invoice),
949+
Err(Bolt12PaymentError::UnexpectedInvoice),
950+
);
951+
}
952+
868953
/// Fails creating an offer when a blinded path cannot be created without exposing the node's id.
869954
#[test]
870955
fn fails_creating_offer_without_blinded_paths() {

lightning/src/ln/outbound_payment.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -501,9 +501,9 @@ pub enum PaymentSendFailure {
501501
},
502502
}
503503

504-
/// An error when attempting to pay a BOLT 12 invoice.
504+
/// An error when attempting to pay a [`Bolt12Invoice`].
505505
#[derive(Clone, Debug, PartialEq, Eq)]
506-
pub(super) enum Bolt12PaymentError {
506+
pub enum Bolt12PaymentError {
507507
/// The invoice was not requested.
508508
UnexpectedInvoice,
509509
/// Payment for an invoice with the corresponding [`PaymentId`] was already initiated.

lightning/src/offers/invoice.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError, ParsedMessage}
124124
use crate::offers::payer::{PAYER_METADATA_TYPE, PayerTlvStream, PayerTlvStreamRef};
125125
use crate::offers::refund::{IV_BYTES as REFUND_IV_BYTES, Refund, RefundContents};
126126
use crate::offers::signer;
127-
use crate::util::ser::{HighZeroBytesDroppedBigSize, Iterable, SeekReadable, WithoutLength, Writeable, Writer};
127+
use crate::util::ser::{HighZeroBytesDroppedBigSize, Iterable, Readable, SeekReadable, WithoutLength, Writeable, Writer};
128128
use crate::util::string::PrintableString;
129129

130130
#[allow(unused_imports)]
@@ -1119,6 +1119,13 @@ impl Writeable for Bolt12Invoice {
11191119
}
11201120
}
11211121

1122+
impl Readable for Bolt12Invoice {
1123+
fn read<R: io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
1124+
let bytes: WithoutLength<Vec<u8>> = Readable::read(reader)?;
1125+
Self::try_from(bytes.0).map_err(|_| DecodeError::InvalidValue)
1126+
}
1127+
}
1128+
11221129
impl Writeable for InvoiceContents {
11231130
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
11241131
self.as_tlv_stream().write(writer)

lightning/src/onion_message/messenger.rs

+6
Original file line numberDiff line numberDiff line change
@@ -325,12 +325,18 @@ impl OnionMessageRecipient {
325325

326326
/// The `Responder` struct creates an appropriate [`ResponseInstruction`]
327327
/// for responding to a message.
328+
#[derive(Clone, Debug, Eq, PartialEq)]
328329
pub struct Responder {
329330
/// The path along which a response can be sent.
330331
reply_path: BlindedPath,
331332
path_id: Option<[u8; 32]>
332333
}
333334

335+
impl_writeable_tlv_based!(Responder, {
336+
(0, reply_path, required),
337+
(2, path_id, option),
338+
});
339+
334340
impl Responder {
335341
/// Creates a new [`Responder`] instance with the provided reply path.
336342
pub(super) fn new(reply_path: BlindedPath, path_id: Option<[u8; 32]>) -> Self {

0 commit comments

Comments
 (0)