diff --git a/lightning/src/events/mod.rs b/lightning/src/events/mod.rs index a0f26bfbac0..8d28c9b4191 100644 --- a/lightning/src/events/mod.rs +++ b/lightning/src/events/mod.rs @@ -23,11 +23,12 @@ use crate::blinded_path::payment::{Bolt12OfferContext, Bolt12RefundContext, Paym use crate::chain::transaction; use crate::ln::channelmanager::{InterceptId, PaymentId, RecipientOnionFields}; use crate::ln::channel::FUNDING_CONF_DEADLINE_BLOCKS; +use crate::offers::invoice::Bolt12Invoice; +use crate::offers::static_invoice::StaticInvoice; use crate::types::features::ChannelTypeFeatures; use crate::ln::msgs; use crate::ln::types::ChannelId; use crate::types::payment::{PaymentPreimage, PaymentHash, PaymentSecret}; -use crate::offers::invoice::Bolt12Invoice; use crate::onion_message::messenger::Responder; use crate::routing::gossip::NetworkUpdate; use crate::routing::router::{BlindedTail, Path, RouteHop, RouteParameters}; @@ -949,6 +950,18 @@ pub enum Event { /// /// [`Route::get_total_fees`]: crate::routing::router::Route::get_total_fees fee_paid_msat: Option, + /// The BOLT 12 invoice that was paid. `None` if the payment was a non BOLT 12 payment. + /// + /// The BOLT 12 invoice is useful for proof of payment because it contains the + /// payment hash. A third party can verify that the payment was made by + /// showing the invoice and confirming that the payment hash matches + /// the hash of the payment preimage. + /// + /// However, the [`PaidBolt12Invoice`] can also be of type [`StaticInvoice`], which + /// is a special [`Bolt12Invoice`] where proof of payment is not possible. + /// + /// [`StaticInvoice`]: crate::offers::static_invoice::StaticInvoice + bolt12_invoice: Option, }, /// Indicates an outbound payment failed. Individual [`Event::PaymentPathFailed`] events /// provide failure information for each path attempt in the payment, including retries. @@ -1556,7 +1569,7 @@ impl Writeable for Event { (13, payment_id, option), }); }, - &Event::PaymentSent { ref payment_id, ref payment_preimage, ref payment_hash, ref amount_msat, ref fee_paid_msat } => { + &Event::PaymentSent { ref payment_id, ref payment_preimage, ref payment_hash, ref amount_msat, ref fee_paid_msat, ref bolt12_invoice } => { 2u8.write(writer)?; write_tlv_fields!(writer, { (0, payment_preimage, required), @@ -1564,6 +1577,7 @@ impl Writeable for Event { (3, payment_id, option), (5, fee_paid_msat, option), (7, amount_msat, option), + (9, bolt12_invoice, option), }); }, &Event::PaymentPathFailed { @@ -1898,12 +1912,14 @@ impl MaybeReadable for Event { let mut payment_id = None; let mut amount_msat = None; let mut fee_paid_msat = None; + let mut bolt12_invoice = None; read_tlv_fields!(reader, { (0, payment_preimage, required), (1, payment_hash, option), (3, payment_id, option), (5, fee_paid_msat, option), (7, amount_msat, option), + (9, bolt12_invoice, option), }); if payment_hash.is_none() { payment_hash = Some(PaymentHash(Sha256::hash(&payment_preimage.0[..]).to_byte_array())); @@ -1914,6 +1930,7 @@ impl MaybeReadable for Event { payment_hash: payment_hash.unwrap(), amount_msat, fee_paid_msat, + bolt12_invoice, })) }; f() @@ -2438,3 +2455,19 @@ impl EventHandler for Arc { self.deref().handle_event(event) } } + +/// The BOLT 12 invoice that was paid, surfaced in [`Event::PaymentSent::bolt12_invoice`]. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum PaidBolt12Invoice { + /// The BOLT 12 invoice specified by the BOLT 12 specification, + /// allowing the user to perform proof of payment. + Bolt12Invoice(Bolt12Invoice), + /// The Static invoice, used in the async payment specification update proposal, + /// where the user cannot perform proof of payment. + StaticInvoice(StaticInvoice), + } + +impl_writeable_tlv_based_enum!(PaidBolt12Invoice, + {0, Bolt12Invoice} => (), + {2, StaticInvoice} => (), +); diff --git a/lightning/src/ln/async_payments_tests.rs b/lightning/src/ln/async_payments_tests.rs index b888b9ceb5c..5e0c7efcc17 100644 --- a/lightning/src/ln/async_payments_tests.rs +++ b/lightning/src/ln/async_payments_tests.rs @@ -11,7 +11,7 @@ use crate::blinded_path::message::{MessageContext, OffersContext}; use crate::blinded_path::payment::PaymentContext; use crate::blinded_path::payment::{AsyncBolt12OfferContext, BlindedPaymentTlvs}; use crate::chain::channelmonitor::{HTLC_FAIL_BACK_BUFFER, LATENCY_GRACE_PERIOD_BLOCKS}; -use crate::events::{Event, HTLCDestination, PaymentFailureReason}; +use crate::events::{Event, HTLCDestination, PaidBolt12Invoice, PaymentFailureReason}; use crate::ln::blinded_payment_tests::{fail_blinded_htlc_backwards, get_blinded_route_parameters}; use crate::ln::channelmanager::{PaymentId, RecipientOnionFields}; use crate::ln::functional_test_utils::*; @@ -420,7 +420,7 @@ fn async_receive_flow_success() { .pay_for_offer(&offer, None, Some(amt_msat), None, payment_id, Retry::Attempts(0), params) .unwrap(); let release_held_htlc_om = - pass_async_payments_oms(static_invoice, &nodes[0], &nodes[1], &nodes[2]).1; + pass_async_payments_oms(static_invoice.clone(), &nodes[0], &nodes[1], &nodes[2]).1; nodes[0] .onion_messenger .handle_onion_message(nodes[2].node.get_our_node_id(), &release_held_htlc_om); @@ -441,7 +441,10 @@ fn async_receive_flow_success() { let args = PassAlongPathArgs::new(&nodes[0], route[0], amt_msat, payment_hash, ev) .with_payment_preimage(keysend_preimage); do_pass_along_path(args); - claim_payment_along_route(ClaimAlongRouteArgs::new(&nodes[0], route, keysend_preimage)); + let res = + claim_payment_along_route(ClaimAlongRouteArgs::new(&nodes[0], route, keysend_preimage)); + assert!(res.is_some()); + assert_eq!(res, Some(PaidBolt12Invoice::StaticInvoice(static_invoice))); } #[cfg_attr(feature = "std", ignore)] diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index 56cd64a5567..a9be7ee3a57 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -13,7 +13,7 @@ use crate::chain::{BestBlock, ChannelMonitorUpdateStatus, Confirm, Listen, Watch, chainmonitor::Persist}; use crate::chain::channelmonitor::ChannelMonitor; use crate::chain::transaction::OutPoint; -use crate::events::{ClaimedHTLC, ClosureReason, Event, HTLCDestination, PathFailure, PaymentPurpose, PaymentFailureReason}; +use crate::events::{ClaimedHTLC, ClosureReason, Event, HTLCDestination, PaidBolt12Invoice, PathFailure, PaymentFailureReason, PaymentPurpose}; use crate::events::bump_transaction::{BumpTransactionEvent, BumpTransactionEventHandler, Wallet, WalletSource}; use crate::ln::types::ChannelId; use crate::types::payment::{PaymentPreimage, PaymentHash, PaymentSecret}; @@ -2294,7 +2294,7 @@ macro_rules! expect_payment_claimed { pub fn expect_payment_sent>(node: &H, expected_payment_preimage: PaymentPreimage, expected_fee_msat_opt: Option>, expect_per_path_claims: bool, expect_post_ev_mon_update: bool, -) { +) -> Option { let events = node.node().get_and_clear_pending_events(); let expected_payment_hash = PaymentHash( bitcoin::hashes::sha256::Hash::hash(&expected_payment_preimage.0).to_byte_array()); @@ -2306,8 +2306,10 @@ pub fn expect_payment_sent>(node: &H, if expect_post_ev_mon_update { check_added_monitors(node, 1); } + // We return the invoice because some test may want to check the invoice details. + let invoice; let expected_payment_id = match events[0] { - Event::PaymentSent { ref payment_id, ref payment_preimage, ref payment_hash, ref amount_msat, ref fee_paid_msat } => { + Event::PaymentSent { ref payment_id, ref payment_preimage, ref payment_hash, ref amount_msat, ref fee_paid_msat, ref bolt12_invoice } => { assert_eq!(expected_payment_preimage, *payment_preimage); assert_eq!(expected_payment_hash, *payment_hash); assert!(amount_msat.is_some()); @@ -2316,6 +2318,7 @@ pub fn expect_payment_sent>(node: &H, } else { assert!(fee_paid_msat.is_some()); } + invoice = bolt12_invoice.clone(); payment_id.unwrap() }, _ => panic!("Unexpected event"), @@ -2331,19 +2334,20 @@ pub fn expect_payment_sent>(node: &H, } } } + invoice } #[macro_export] macro_rules! expect_payment_sent { ($node: expr, $expected_payment_preimage: expr) => { - $crate::expect_payment_sent!($node, $expected_payment_preimage, None::, true); + $crate::expect_payment_sent!($node, $expected_payment_preimage, None::, true) }; ($node: expr, $expected_payment_preimage: expr, $expected_fee_msat_opt: expr) => { - $crate::expect_payment_sent!($node, $expected_payment_preimage, $expected_fee_msat_opt, true); + $crate::expect_payment_sent!($node, $expected_payment_preimage, $expected_fee_msat_opt, true) }; ($node: expr, $expected_payment_preimage: expr, $expected_fee_msat_opt: expr, $expect_paths: expr) => { $crate::ln::functional_test_utils::expect_payment_sent(&$node, $expected_payment_preimage, - $expected_fee_msat_opt.map(|o| Some(o)), $expect_paths, true); + $expected_fee_msat_opt.map(|o| Some(o)), $expect_paths, true) } } @@ -3106,20 +3110,22 @@ pub fn pass_claimed_payment_along_route(args: ClaimAlongRouteArgs) -> u64 { expected_total_fee_msat } -pub fn claim_payment_along_route(args: ClaimAlongRouteArgs) { +pub fn claim_payment_along_route(args: ClaimAlongRouteArgs) -> Option { let origin_node = args.origin_node; let payment_preimage = args.payment_preimage; let skip_last = args.skip_last; let expected_total_fee_msat = do_claim_payment_along_route(args); if !skip_last { - expect_payment_sent!(origin_node, payment_preimage, Some(expected_total_fee_msat)); + expect_payment_sent!(origin_node, payment_preimage, Some(expected_total_fee_msat)) + } else { + None } } -pub fn claim_payment<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_route: &[&Node<'a, 'b, 'c>], our_payment_preimage: PaymentPreimage) { +pub fn claim_payment<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_route: &[&Node<'a, 'b, 'c>], our_payment_preimage: PaymentPreimage) -> Option { claim_payment_along_route( ClaimAlongRouteArgs::new(origin_node, &[expected_route], our_payment_preimage) - ); + ) } pub const TEST_FINAL_CLTV: u32 = 70; diff --git a/lightning/src/ln/offers_tests.rs b/lightning/src/ln/offers_tests.rs index c8419a14239..78cd40e9e34 100644 --- a/lightning/src/ln/offers_tests.rs +++ b/lightning/src/ln/offers_tests.rs @@ -47,7 +47,7 @@ use crate::blinded_path::IntroductionNode; use crate::blinded_path::message::BlindedMessagePath; use crate::blinded_path::payment::{Bolt12OfferContext, Bolt12RefundContext, PaymentContext}; use crate::blinded_path::message::OffersContext; -use crate::events::{ClosureReason, Event, HTLCDestination, PaymentFailureReason, PaymentPurpose}; +use crate::events::{ClosureReason, Event, HTLCDestination, PaidBolt12Invoice, PaymentFailureReason, PaymentPurpose}; use crate::ln::channelmanager::{Bolt12PaymentError, MAX_SHORT_LIVED_RELATIVE_EXPIRY, PaymentId, RecentPaymentDetails, RecipientOnionFields, Retry, self}; use crate::types::features::Bolt12InvoiceFeatures; use crate::ln::functional_test_utils::*; @@ -167,7 +167,7 @@ fn route_bolt12_payment<'a, 'b, 'c>( } fn claim_bolt12_payment<'a, 'b, 'c>( - node: &Node<'a, 'b, 'c>, path: &[&Node<'a, 'b, 'c>], expected_payment_context: PaymentContext + node: &Node<'a, 'b, 'c>, path: &[&Node<'a, 'b, 'c>], expected_payment_context: PaymentContext, invoice: &Bolt12Invoice ) { let recipient = &path[path.len() - 1]; let payment_purpose = match get_event!(recipient, Event::PaymentClaimable) { @@ -187,7 +187,11 @@ fn claim_bolt12_payment<'a, 'b, 'c>( }, _ => panic!("Unexpected payment purpose: {:?}", payment_purpose), } - claim_payment(node, path, payment_preimage); + if let Some(inv) = claim_payment(node, path, payment_preimage) { + assert_eq!(inv, PaidBolt12Invoice::Bolt12Invoice(invoice.to_owned())); + } else { + panic!("Expected PaidInvoice::Bolt12Invoice"); + }; } fn extract_offer_nonce<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, message: &OnionMessage) -> Nonce { @@ -591,7 +595,7 @@ fn creates_and_pays_for_offer_using_two_hop_blinded_path() { route_bolt12_payment(david, &[charlie, bob, alice], &invoice); expect_recent_payment!(david, RecentPaymentDetails::Pending, payment_id); - claim_bolt12_payment(david, &[charlie, bob, alice], payment_context); + claim_bolt12_payment(david, &[charlie, bob, alice], payment_context, &invoice); expect_recent_payment!(david, RecentPaymentDetails::Fulfilled, payment_id); } @@ -674,7 +678,7 @@ fn creates_and_pays_for_refund_using_two_hop_blinded_path() { route_bolt12_payment(david, &[charlie, bob, alice], &invoice); expect_recent_payment!(david, RecentPaymentDetails::Pending, payment_id); - claim_bolt12_payment(david, &[charlie, bob, alice], payment_context); + claim_bolt12_payment(david, &[charlie, bob, alice], payment_context, &invoice); expect_recent_payment!(david, RecentPaymentDetails::Fulfilled, payment_id); } @@ -741,7 +745,7 @@ fn creates_and_pays_for_offer_using_one_hop_blinded_path() { route_bolt12_payment(bob, &[alice], &invoice); expect_recent_payment!(bob, RecentPaymentDetails::Pending, payment_id); - claim_bolt12_payment(bob, &[alice], payment_context); + claim_bolt12_payment(bob, &[alice], payment_context, &invoice); expect_recent_payment!(bob, RecentPaymentDetails::Fulfilled, payment_id); } @@ -797,7 +801,7 @@ fn creates_and_pays_for_refund_using_one_hop_blinded_path() { route_bolt12_payment(bob, &[alice], &invoice); expect_recent_payment!(bob, RecentPaymentDetails::Pending, payment_id); - claim_bolt12_payment(bob, &[alice], payment_context); + claim_bolt12_payment(bob, &[alice], payment_context, &invoice); expect_recent_payment!(bob, RecentPaymentDetails::Fulfilled, payment_id); } @@ -851,7 +855,7 @@ fn pays_for_offer_without_blinded_paths() { route_bolt12_payment(bob, &[alice], &invoice); expect_recent_payment!(bob, RecentPaymentDetails::Pending, payment_id); - claim_bolt12_payment(bob, &[alice], payment_context); + claim_bolt12_payment(bob, &[alice], payment_context, &invoice); expect_recent_payment!(bob, RecentPaymentDetails::Fulfilled, payment_id); } @@ -894,7 +898,7 @@ fn pays_for_refund_without_blinded_paths() { route_bolt12_payment(bob, &[alice], &invoice); expect_recent_payment!(bob, RecentPaymentDetails::Pending, payment_id); - claim_bolt12_payment(bob, &[alice], payment_context); + claim_bolt12_payment(bob, &[alice], payment_context, &invoice); expect_recent_payment!(bob, RecentPaymentDetails::Fulfilled, payment_id); } @@ -1132,7 +1136,7 @@ fn creates_and_pays_for_offer_with_retry() { } route_bolt12_payment(bob, &[alice], &invoice); expect_recent_payment!(bob, RecentPaymentDetails::Pending, payment_id); - claim_bolt12_payment(bob, &[alice], payment_context); + claim_bolt12_payment(bob, &[alice], payment_context, &invoice); expect_recent_payment!(bob, RecentPaymentDetails::Fulfilled, payment_id); } @@ -1203,7 +1207,7 @@ fn pays_bolt12_invoice_asynchronously() { route_bolt12_payment(bob, &[alice], &invoice); expect_recent_payment!(bob, RecentPaymentDetails::Pending, payment_id); - claim_bolt12_payment(bob, &[alice], payment_context); + claim_bolt12_payment(bob, &[alice], payment_context, &invoice); expect_recent_payment!(bob, RecentPaymentDetails::Fulfilled, payment_id); assert_eq!( @@ -1283,7 +1287,7 @@ fn creates_offer_with_blinded_path_using_unannounced_introduction_node() { route_bolt12_payment(bob, &[alice], &invoice); expect_recent_payment!(bob, RecentPaymentDetails::Pending, payment_id); - claim_bolt12_payment(bob, &[alice], payment_context); + claim_bolt12_payment(bob, &[alice], payment_context, &invoice); expect_recent_payment!(bob, RecentPaymentDetails::Fulfilled, payment_id); } @@ -2139,7 +2143,7 @@ fn fails_paying_invoice_more_than_once() { assert!(david.node.get_and_clear_pending_msg_events().is_empty()); // Complete paying the first invoice - claim_bolt12_payment(david, &[charlie, bob, alice], payment_context); + claim_bolt12_payment(david, &[charlie, bob, alice], payment_context, &invoice1); expect_recent_payment!(david, RecentPaymentDetails::Fulfilled, payment_id); } diff --git a/lightning/src/ln/outbound_payment.rs b/lightning/src/ln/outbound_payment.rs index 3033ffd3a2d..ea4fd458ce0 100644 --- a/lightning/src/ln/outbound_payment.rs +++ b/lightning/src/ln/outbound_payment.rs @@ -15,18 +15,19 @@ use bitcoin::secp256k1::{self, Secp256k1, SecretKey}; use lightning_invoice::Bolt11Invoice; use crate::blinded_path::{IntroductionNode, NodeIdLookUp}; -use crate::events::{self, PaymentFailureReason}; -use crate::types::payment::{PaymentHash, PaymentPreimage, PaymentSecret}; +use crate::events::{self, PaidBolt12Invoice, PaymentFailureReason}; use crate::ln::channel_state::ChannelDetails; use crate::ln::channelmanager::{EventCompletionAction, HTLCSource, PaymentId}; -use crate::types::features::Bolt12InvoiceFeatures; use crate::ln::onion_utils; use crate::ln::onion_utils::{DecodedOnionFailure, HTLCFailReason}; use crate::offers::invoice::Bolt12Invoice; use crate::offers::invoice_request::InvoiceRequest; +use crate::offers::static_invoice::StaticInvoice; use crate::offers::nonce::Nonce; -use crate::routing::router::{BlindedTail, InFlightHtlcs, RouteParametersConfig, Path, PaymentParameters, Route, RouteParameters, Router}; +use crate::routing::router::{BlindedTail, InFlightHtlcs, Path, PaymentParameters, Route, RouteParameters, RouteParametersConfig, Router}; use crate::sign::{EntropySource, NodeSigner, Recipient}; +use crate::types::features::Bolt12InvoiceFeatures; +use crate::types::payment::{PaymentHash, PaymentPreimage, PaymentSecret}; use crate::util::errors::APIError; use crate::util::logger::Logger; #[cfg(feature = "std")] @@ -34,10 +35,7 @@ use crate::util::time::Instant; use crate::util::ser::ReadableArgs; #[cfg(async_payments)] -use { - crate::offers::invoice::{DerivedSigningPubkey, InvoiceBuilder}, - crate::offers::static_invoice::StaticInvoice, -}; +use crate::offers::invoice::{DerivedSigningPubkey, InvoiceBuilder}; use core::fmt::{self, Display, Formatter}; use core::ops::Deref; @@ -95,6 +93,7 @@ pub(crate) enum PendingOutboundPayment { retry_strategy: Retry, route_params: RouteParameters, invoice_request: InvoiceRequest, + static_invoice: StaticInvoice, }, Retryable { retry_strategy: Option, @@ -106,6 +105,9 @@ pub(crate) enum PendingOutboundPayment { payment_metadata: Option>, keysend_preimage: Option, invoice_request: Option, + // Storing the BOLT 12 invoice here to allow Proof of Payment after + // the payment is made. + bolt12_invoice: Option, custom_tlvs: Vec<(u64, Vec)>, pending_amt_msat: u64, /// Used to track the fee paid. Present iff the payment was serialized on 0.0.103+. @@ -155,6 +157,12 @@ impl_writeable_tlv_based!(RetryableInvoiceRequest, { }); impl PendingOutboundPayment { + fn bolt12_invoice(&self) -> Option<&PaidBolt12Invoice> { + match self { + PendingOutboundPayment::Retryable { bolt12_invoice, .. } => bolt12_invoice.as_ref(), + _ => None, + } + } fn increment_attempts(&mut self) { if let PendingOutboundPayment::Retryable { attempts, .. } = self { attempts.count += 1; @@ -831,7 +839,7 @@ impl OutboundPayments { IH: Fn() -> InFlightHtlcs, SP: Fn(SendAlongPathArgs) -> Result<(), APIError>, { - self.send_payment_internal(payment_id, payment_hash, recipient_onion, None, retry_strategy, + self.send_payment_for_non_bolt12_invoice(payment_id, payment_hash, recipient_onion, None, retry_strategy, route_params, router, first_hops, &compute_inflight_htlcs, entropy_source, node_signer, best_block_height, logger, pending_events, &send_payment_along_path) } @@ -854,7 +862,7 @@ impl OutboundPayments { let preimage = payment_preimage .unwrap_or_else(|| PaymentPreimage(entropy_source.get_secure_random_bytes())); let payment_hash = PaymentHash(Sha256::hash(&preimage.0).to_byte_array()); - self.send_payment_internal(payment_id, payment_hash, recipient_onion, Some(preimage), + self.send_payment_for_non_bolt12_invoice(payment_id, payment_hash, recipient_onion, Some(preimage), retry_strategy, route_params, router, first_hops, inflight_htlcs, entropy_source, node_signer, best_block_height, logger, pending_events, send_payment_along_path) .map(|()| payment_hash) @@ -897,7 +905,7 @@ impl OutboundPayments { route_params.max_total_routing_fee_msat = Some(max_fee_msat); } - self.send_payment_internal(payment_id, payment_hash, recipient_onion, None, retry_strategy, route_params, + self.send_payment_for_non_bolt12_invoice(payment_id, payment_hash, recipient_onion, None, retry_strategy, route_params, router, first_hops, compute_inflight_htlcs, entropy_source, node_signer, best_block_height, logger, pending_events, send_payment_along_path @@ -958,8 +966,9 @@ impl OutboundPayments { if let Some(max_fee_msat) = params_config.max_total_routing_fee_msat { route_params.max_total_routing_fee_msat = Some(max_fee_msat); } + let invoice = PaidBolt12Invoice::Bolt12Invoice(invoice.clone()); self.send_payment_for_bolt12_invoice_internal( - payment_id, payment_hash, None, None, route_params, retry_strategy, router, first_hops, + payment_id, payment_hash, None, None, invoice, route_params, retry_strategy, router, first_hops, inflight_htlcs, entropy_source, node_signer, node_id_lookup, secp_ctx, best_block_height, logger, pending_events, send_payment_along_path ) @@ -970,6 +979,7 @@ impl OutboundPayments { >( &self, payment_id: PaymentId, payment_hash: PaymentHash, keysend_preimage: Option, invoice_request: Option<&InvoiceRequest>, + bolt12_invoice: PaidBolt12Invoice, mut route_params: RouteParameters, retry_strategy: Retry, router: &R, first_hops: Vec, inflight_htlcs: IH, entropy_source: &ES, node_signer: &NS, node_id_lookup: &NL, secp_ctx: &Secp256k1, best_block_height: u32, logger: &L, @@ -1032,8 +1042,8 @@ impl OutboundPayments { hash_map::Entry::Occupied(entry) => match entry.get() { PendingOutboundPayment::InvoiceReceived { .. } => { let (retryable_payment, onion_session_privs) = Self::create_pending_payment( - payment_hash, recipient_onion.clone(), keysend_preimage, None, &route, - Some(retry_strategy), payment_params, entropy_source, best_block_height + payment_hash, recipient_onion.clone(), keysend_preimage, None, Some(bolt12_invoice), &route, + Some(retry_strategy), payment_params, entropy_source, best_block_height, ); *entry.into_mut() = retryable_payment; onion_session_privs @@ -1043,7 +1053,7 @@ impl OutboundPayments { invoice_request } else { unreachable!() }; let (retryable_payment, onion_session_privs) = Self::create_pending_payment( - payment_hash, recipient_onion.clone(), keysend_preimage, Some(invreq), &route, + payment_hash, recipient_onion.clone(), keysend_preimage, Some(invreq), Some(bolt12_invoice), &route, Some(retry_strategy), payment_params, entropy_source, best_block_height ); outbounds.insert(payment_id, retryable_payment); @@ -1151,6 +1161,7 @@ impl OutboundPayments { .take() .ok_or(Bolt12PaymentError::UnexpectedInvoice)? .invoice_request, + static_invoice: invoice.clone(), }; return Ok(()) }, @@ -1179,22 +1190,22 @@ impl OutboundPayments { IH: Fn() -> InFlightHtlcs, SP: Fn(SendAlongPathArgs) -> Result<(), APIError>, { - let (payment_hash, keysend_preimage, route_params, retry_strategy, invoice_request) = + let (payment_hash, keysend_preimage, route_params, retry_strategy, invoice_request, invoice) = match self.pending_outbound_payments.lock().unwrap().entry(payment_id) { hash_map::Entry::Occupied(entry) => match entry.get() { PendingOutboundPayment::StaticInvoiceReceived { - payment_hash, route_params, retry_strategy, keysend_preimage, invoice_request, .. + payment_hash, route_params, retry_strategy, keysend_preimage, invoice_request, static_invoice, .. } => { (*payment_hash, *keysend_preimage, route_params.clone(), *retry_strategy, - invoice_request.clone()) + invoice_request.clone(), static_invoice.clone()) }, _ => return Err(Bolt12PaymentError::DuplicateInvoice), }, hash_map::Entry::Vacant(_) => return Err(Bolt12PaymentError::UnexpectedInvoice), }; - + let invoice = PaidBolt12Invoice::StaticInvoice(invoice); self.send_payment_for_bolt12_invoice_internal( - payment_id, payment_hash, Some(keysend_preimage), Some(&invoice_request), route_params, + payment_id, payment_hash, Some(keysend_preimage), Some(&invoice_request), invoice, route_params, retry_strategy, router, first_hops, inflight_htlcs, entropy_source, node_signer, node_id_lookup, secp_ctx, best_block_height, logger, pending_events, send_payment_along_path ) @@ -1318,7 +1329,7 @@ impl OutboundPayments { /// /// [`Event::PaymentPathFailed`]: crate::events::Event::PaymentPathFailed /// [`Event::PaymentFailed`]: crate::events::Event::PaymentFailed - fn send_payment_internal( + fn send_payment_for_non_bolt12_invoice( &self, payment_id: PaymentId, payment_hash: PaymentHash, recipient_onion: RecipientOnionFields, keysend_preimage: Option, retry_strategy: Retry, mut route_params: RouteParameters, router: &R, first_hops: Vec, inflight_htlcs: IH, entropy_source: &ES, @@ -1340,7 +1351,7 @@ impl OutboundPayments { let onion_session_privs = self.add_new_pending_payment(payment_hash, recipient_onion.clone(), payment_id, keysend_preimage, &route, Some(retry_strategy), - Some(route_params.payment_params.clone()), entropy_source, best_block_height) + Some(route_params.payment_params.clone()), entropy_source, best_block_height, None) .map_err(|_| { log_error!(logger, "Payment with id {} is already pending. New payment had payment hash {}", payment_id, payment_hash); @@ -1654,7 +1665,7 @@ impl OutboundPayments { let route = Route { paths: vec![path], route_params: None }; let onion_session_privs = self.add_new_pending_payment(payment_hash, RecipientOnionFields::secret_only(payment_secret), payment_id, None, &route, None, None, - entropy_source, best_block_height + entropy_source, best_block_height, None ).map_err(|e| { debug_assert!(matches!(e, PaymentSendFailure::DuplicatePayment)); ProbeSendFailure::DuplicateProbe @@ -1709,20 +1720,21 @@ impl OutboundPayments { &self, payment_hash: PaymentHash, recipient_onion: RecipientOnionFields, payment_id: PaymentId, route: &Route, retry_strategy: Option, entropy_source: &ES, best_block_height: u32 ) -> Result, PaymentSendFailure> where ES::Target: EntropySource { - self.add_new_pending_payment(payment_hash, recipient_onion, payment_id, None, route, retry_strategy, None, entropy_source, best_block_height) + self.add_new_pending_payment(payment_hash, recipient_onion, payment_id, None, route, retry_strategy, None, entropy_source, best_block_height, None) } pub(super) fn add_new_pending_payment( &self, payment_hash: PaymentHash, recipient_onion: RecipientOnionFields, payment_id: PaymentId, keysend_preimage: Option, route: &Route, retry_strategy: Option, - payment_params: Option, entropy_source: &ES, best_block_height: u32 + payment_params: Option, entropy_source: &ES, best_block_height: u32, + bolt12_invoice: Option ) -> Result, PaymentSendFailure> where ES::Target: EntropySource { let mut pending_outbounds = self.pending_outbound_payments.lock().unwrap(); match pending_outbounds.entry(payment_id) { hash_map::Entry::Occupied(_) => Err(PaymentSendFailure::DuplicatePayment), hash_map::Entry::Vacant(entry) => { let (payment, onion_session_privs) = Self::create_pending_payment( - payment_hash, recipient_onion, keysend_preimage, None, route, retry_strategy, + payment_hash, recipient_onion, keysend_preimage, None, bolt12_invoice, route, retry_strategy, payment_params, entropy_source, best_block_height ); entry.insert(payment); @@ -1734,8 +1746,8 @@ impl OutboundPayments { fn create_pending_payment( payment_hash: PaymentHash, recipient_onion: RecipientOnionFields, keysend_preimage: Option, invoice_request: Option, - route: &Route, retry_strategy: Option, payment_params: Option, - entropy_source: &ES, best_block_height: u32 + bolt12_invoice: Option, route: &Route, retry_strategy: Option, + payment_params: Option, entropy_source: &ES, best_block_height: u32 ) -> (PendingOutboundPayment, Vec<[u8; 32]>) where ES::Target: EntropySource, @@ -1757,6 +1769,7 @@ impl OutboundPayments { payment_metadata: recipient_onion.payment_metadata, keysend_preimage, invoice_request, + bolt12_invoice, custom_tlvs: recipient_onion.custom_tlvs, starting_block_height: best_block_height, total_msat: route.get_total_amount(), @@ -2016,6 +2029,7 @@ impl OutboundPayments { payment_hash, amount_msat, fee_paid_msat, + bolt12_invoice: payment.get().bolt12_invoice().cloned(), }, Some(ev_completion_action.clone()))); payment.get_mut().mark_fulfilled(); } @@ -2374,6 +2388,7 @@ impl OutboundPayments { payment_metadata: None, // only used for retries, and we'll never retry on startup keysend_preimage: None, // only used for retries, and we'll never retry on startup invoice_request: None, // only used for retries, and we'll never retry on startup + bolt12_invoice: None, // only used for retries, and we'll never retry on startup! custom_tlvs: Vec::new(), // only used for retries, and we'll never retry on startup pending_amt_msat: path_amt, pending_fee_msat: Some(path_fee), @@ -2463,6 +2478,7 @@ impl_writeable_tlv_based_enum_upgradable!(PendingOutboundPayment, (10, starting_block_height, required), (11, remaining_max_total_routing_fee_msat, option), (13, invoice_request, option), + (15, bolt12_invoice, option), (not_written, retry_strategy, (static_value, None)), (not_written, attempts, (static_value, PaymentAttempts::new())), }, @@ -2513,6 +2529,7 @@ impl_writeable_tlv_based_enum_upgradable!(PendingOutboundPayment, (4, retry_strategy, required), (6, route_params, required), (8, invoice_request, required), + (10, static_invoice, required), }, // Added in 0.1. Prior versions will drop these outbounds on downgrade, which is safe because // no HTLCs are in-flight. @@ -2613,7 +2630,7 @@ mod tests { outbound_payments.add_new_pending_payment(PaymentHash([0; 32]), RecipientOnionFields::spontaneous_empty(), PaymentId([0; 32]), None, &Route { paths: vec![], route_params: None }, Some(Retry::Attempts(1)), Some(expired_route_params.payment_params.clone()), - &&keys_manager, 0).unwrap(); + &&keys_manager, 0, None).unwrap(); outbound_payments.find_route_and_send_payment( PaymentHash([0; 32]), PaymentId([0; 32]), expired_route_params, &&router, vec![], &|| InFlightHtlcs::new(), &&keys_manager, &&keys_manager, 0, &&logger, &pending_events, @@ -2656,7 +2673,7 @@ mod tests { outbound_payments.add_new_pending_payment(PaymentHash([0; 32]), RecipientOnionFields::spontaneous_empty(), PaymentId([0; 32]), None, &Route { paths: vec![], route_params: None }, Some(Retry::Attempts(1)), Some(route_params.payment_params.clone()), - &&keys_manager, 0).unwrap(); + &&keys_manager, 0, None).unwrap(); outbound_payments.find_route_and_send_payment( PaymentHash([0; 32]), PaymentId([0; 32]), route_params, &&router, vec![], &|| InFlightHtlcs::new(), &&keys_manager, &&keys_manager, 0, &&logger, &pending_events, @@ -3143,6 +3160,7 @@ mod tests { retry_strategy: Retry::Attempts(0), route_params, invoice_request: dummy_invoice_request(), + static_invoice: dummy_static_invoice(), }; outbounds.insert(payment_id, outbound); core::mem::drop(outbounds); @@ -3190,6 +3208,7 @@ mod tests { retry_strategy: Retry::Attempts(0), route_params, invoice_request: dummy_invoice_request(), + static_invoice: dummy_static_invoice(), }; outbounds.insert(payment_id, outbound); core::mem::drop(outbounds); diff --git a/lightning/src/offers/invoice_macros.rs b/lightning/src/offers/invoice_macros.rs index 2b276a37d29..af3c2a6155e 100644 --- a/lightning/src/offers/invoice_macros.rs +++ b/lightning/src/offers/invoice_macros.rs @@ -87,7 +87,7 @@ macro_rules! invoice_builder_methods_test_common { ( $self: ident, $self_type: ty, $invoice_fields: expr, $return_type: ty, $return_value: expr $(, $self_mut: tt)? ) => { - #[cfg_attr(c_bindings, allow(dead_code))] + #[allow(dead_code)] // TODO: mode to `#[cfg_attr(c_bindings, allow(dead_code))]` once we remove the `async_payments` cfg flag pub(crate) fn features_unchecked( $($self_mut)* $self: $self_type, features: Bolt12InvoiceFeatures ) -> $return_type { diff --git a/lightning/src/offers/invoice_request.rs b/lightning/src/offers/invoice_request.rs index eecb68e0826..cdd9cff2177 100644 --- a/lightning/src/offers/invoice_request.rs +++ b/lightning/src/offers/invoice_request.rs @@ -858,7 +858,7 @@ impl InvoiceRequest { ); invoice_request_verify_method!(self, Self); - #[cfg(async_payments)] + #[allow(unused)] // TODO: remove this once we remove the `async_payments` cfg flag pub(super) fn bytes(&self) -> &Vec { &self.bytes } @@ -874,6 +874,11 @@ impl InvoiceRequest { InvoiceWithExplicitSigningPubkeyBuilder ); invoice_request_verify_method!(self, &Self); + + #[allow(unused)] // TODO: remove this once we remove the `async_payments` cfg flag + pub(super) fn bytes(&self) -> &Vec { + &self.bytes + } } impl InvoiceRequest { diff --git a/lightning/src/offers/mod.rs b/lightning/src/offers/mod.rs index e4fe7d789db..49a95b96f86 100644 --- a/lightning/src/offers/mod.rs +++ b/lightning/src/offers/mod.rs @@ -25,7 +25,6 @@ pub mod parse; mod payer; pub mod refund; pub(crate) mod signer; -#[cfg(async_payments)] pub mod static_invoice; #[cfg(test)] pub(crate) mod test_utils; diff --git a/lightning/src/offers/offer.rs b/lightning/src/offers/offer.rs index d4330df3223..f84b39f9a4e 100644 --- a/lightning/src/offers/offer.rs +++ b/lightning/src/offers/offer.rs @@ -743,7 +743,6 @@ impl Offer { .chain(TlvStream::new(bytes).range(EXPERIMENTAL_OFFER_TYPES)) } - #[cfg(async_payments)] pub(super) fn verify( &self, nonce: Nonce, key: &ExpandedKey, secp_ctx: &Secp256k1, ) -> Result<(OfferId, Option), ()> { diff --git a/lightning/src/offers/static_invoice.rs b/lightning/src/offers/static_invoice.rs index 0ebf33a23dc..d48a83eb9b7 100644 --- a/lightning/src/offers/static_invoice.rs +++ b/lightning/src/offers/static_invoice.rs @@ -33,7 +33,9 @@ use crate::offers::offer::{ }; use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError, ParsedMessage}; use crate::types::features::{Bolt12InvoiceFeatures, OfferFeatures}; -use crate::util::ser::{CursorReadable, Iterable, WithoutLength, Writeable, Writer}; +use crate::util::ser::{ + CursorReadable, Iterable, LengthLimitedRead, LengthReadable, WithoutLength, Writeable, Writer, +}; use crate::util::string::PrintableString; use bitcoin::address::Address; use bitcoin::constants::ChainHash; @@ -70,6 +72,14 @@ pub struct StaticInvoice { signature: Signature, } +impl PartialEq for StaticInvoice { + fn eq(&self, other: &Self) -> bool { + self.bytes.eq(&other.bytes) + } +} + +impl Eq for StaticInvoice {} + /// The contents of a [`StaticInvoice`] for responding to an [`Offer`]. /// /// [`Offer`]: crate::offers::offer::Offer @@ -379,6 +389,7 @@ impl StaticInvoice { self.signature } + #[allow(unused)] // TODO: remove this once we remove the `async_payments` cfg flag pub(crate) fn from_same_offer(&self, invreq: &InvoiceRequest) -> bool { let invoice_offer_tlv_stream = Offer::tlv_stream_iter(&self.bytes).map(|tlv_record| tlv_record.record_bytes); @@ -533,6 +544,12 @@ impl Writeable for StaticInvoice { WithoutLength(&self.bytes).write(writer) } } +impl LengthReadable for StaticInvoice { + fn read_from_fixed_length_buffer(r: &mut R) -> Result { + let bytes: WithoutLength> = LengthReadable::read_from_fixed_length_buffer(r)?; + Self::try_from(bytes.0).map_err(|_| DecodeError::InvalidValue) + } +} impl TryFrom> for StaticInvoice { type Error = Bolt12ParseError; diff --git a/lightning/src/offers/test_utils.rs b/lightning/src/offers/test_utils.rs index 4b088705a46..9888f0303af 100644 --- a/lightning/src/offers/test_utils.rs +++ b/lightning/src/offers/test_utils.rs @@ -12,8 +12,10 @@ use bitcoin::secp256k1::schnorr::Signature; use bitcoin::secp256k1::{Keypair, PublicKey, Secp256k1, SecretKey}; +use crate::blinded_path::message::BlindedMessagePath; use crate::blinded_path::payment::{BlindedPayInfo, BlindedPaymentPath}; use crate::blinded_path::BlindedHop; +use crate::ln::inbound_payment::ExpandedKey; use crate::offers::merkle::TaggedHash; use crate::sign::EntropySource; use crate::types::features::BlindedHopFeatures; @@ -23,6 +25,10 @@ use core::time::Duration; #[allow(unused_imports)] use crate::prelude::*; +use super::nonce::Nonce; +use super::offer::OfferBuilder; +use super::static_invoice::{StaticInvoice, StaticInvoiceBuilder}; + pub(crate) fn fail_sign>(_message: &T) -> Result { Err(()) } @@ -120,3 +126,42 @@ impl EntropySource for FixedEntropy { [42; 32] } } + +pub fn blinded_path() -> BlindedMessagePath { + BlindedMessagePath::from_blinded_path( + pubkey(40), + pubkey(41), + vec![ + BlindedHop { blinded_node_id: pubkey(42), encrypted_payload: vec![0; 43] }, + BlindedHop { blinded_node_id: pubkey(43), encrypted_payload: vec![0; 44] }, + ], + ) +} + +pub fn dummy_static_invoice() -> StaticInvoice { + let node_id = recipient_pubkey(); + let payment_paths = payment_paths(); + let now = now(); + let expanded_key = ExpandedKey::new([42; 32]); + let entropy = FixedEntropy {}; + let nonce = Nonce::from_entropy_source(&entropy); + let secp_ctx = Secp256k1::new(); + + let offer = OfferBuilder::deriving_signing_pubkey(node_id, &expanded_key, nonce, &secp_ctx) + .path(blinded_path()) + .build() + .unwrap(); + + StaticInvoiceBuilder::for_offer_using_derived_keys( + &offer, + payment_paths.clone(), + vec![blinded_path()], + now, + &expanded_key, + nonce, + &secp_ctx, + ) + .unwrap() + .build_and_sign(&secp_ctx) + .unwrap() +} diff --git a/lightning/src/util/ser_macros.rs b/lightning/src/util/ser_macros.rs index 8c0e4bd3d10..c3cf2044deb 100644 --- a/lightning/src/util/ser_macros.rs +++ b/lightning/src/util/ser_macros.rs @@ -1225,7 +1225,7 @@ macro_rules! impl_writeable_tlv_based_enum { $($tuple_variant_id => { let length: $crate::util::ser::BigSize = $crate::util::ser::Readable::read(reader)?; let mut s = $crate::util::ser::FixedLengthReader::new(reader, length.0); - let res = $crate::util::ser::Readable::read(&mut s)?; + let res = $crate::util::ser::LengthReadable::read_from_fixed_length_buffer(&mut s)?; if s.bytes_remain() { s.eat_remaining()?; // Return ShortRead if there's actually not enough bytes return Err($crate::ln::msgs::DecodeError::InvalidValue);