Skip to content

Commit d5dbc5c

Browse files
committed
OffersMessageHandler impl for ChannelManager
Define the BOLT 12 message flow in ChannelManager's OffersMessageHandler implementation. - An invoice_request message results in responding with an invoice message if it can be verified that the request is for a valid offer. - An invoice is paid if it can be verified to have originated from a sent invoice_request or a refund. - An invoice_error is sent in some failure cases.
1 parent 7d67e92 commit d5dbc5c

File tree

4 files changed

+197
-4
lines changed

4 files changed

+197
-4
lines changed

lightning/src/ln/channelmanager.rs

+133
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,11 @@ use crate::ln::msgs::{ChannelMessageHandler, DecodeError, LightningError};
5555
use crate::ln::outbound_payment;
5656
use crate::ln::outbound_payment::{OutboundPayments, PaymentAttempts, PendingOutboundPayment, SendAlongPathArgs};
5757
use crate::ln::wire::Encode;
58+
use crate::offers::invoice::{Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, DerivedSigningPubkey, InvoiceBuilder};
59+
use crate::offers::invoice_error::InvoiceError;
60+
use crate::offers::merkle::{SignError, TaggedHash};
61+
use crate::offers::parse::Bolt12SemanticError;
62+
use crate::onion_message::{OffersMessage, OffersMessageHandler};
5863
use crate::sign::{EntropySource, KeysManager, NodeSigner, Recipient, SignerProvider, ChannelSigner, WriteableEcdsaChannelSigner};
5964
use crate::util::config::{UserConfig, ChannelConfig, ChannelConfigUpdate};
6065
use crate::util::wakers::{Future, Notifier};
@@ -3291,6 +3296,17 @@ where
32913296
self.pending_outbound_payments.test_set_payment_metadata(payment_id, new_payment_metadata);
32923297
}
32933298

3299+
pub(super) fn send_payment_for_bolt12_invoice(&self, invoice: &Bolt12Invoice, payment_id: PaymentId) -> Result<(), RetryableSendFailure> {
3300+
let best_block_height = self.best_block.read().unwrap().height();
3301+
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
3302+
self.pending_outbound_payments
3303+
.send_payment_for_bolt12_invoice(
3304+
invoice, payment_id, &self.router, self.list_usable_channels(),
3305+
|| self.compute_inflight_htlcs(), &self.entropy_source, &self.node_signer,
3306+
best_block_height, &self.logger, &self.pending_events,
3307+
|args| self.send_payment_along_path(args)
3308+
)
3309+
}
32943310

32953311
/// Signals that no further retries for the given payment should occur. Useful if you have a
32963312
/// pending outbound payment with retries remaining, but wish to stop retrying the payment before
@@ -7440,6 +7456,123 @@ where
74407456
}
74417457
}
74427458

7459+
impl<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref>
7460+
OffersMessageHandler for ChannelManager<M, T, ES, NS, SP, F, R, L>
7461+
where
7462+
M::Target: chain::Watch<<SP::Target as SignerProvider>::Signer>,
7463+
T::Target: BroadcasterInterface,
7464+
ES::Target: EntropySource,
7465+
NS::Target: NodeSigner,
7466+
SP::Target: SignerProvider,
7467+
F::Target: FeeEstimator,
7468+
R::Target: Router,
7469+
L::Target: Logger,
7470+
{
7471+
fn handle_message(&self, message: OffersMessage) -> Option<OffersMessage> {
7472+
let secp_ctx = &self.secp_ctx;
7473+
let expanded_key = &self.inbound_payment_key;
7474+
7475+
match message {
7476+
OffersMessage::InvoiceRequest(invoice_request) => {
7477+
let amount_msats = match InvoiceBuilder::<DerivedSigningPubkey>::amount_msats(
7478+
&invoice_request
7479+
) {
7480+
Ok(amount_msats) => Some(amount_msats),
7481+
Err(error) => return Some(OffersMessage::InvoiceError(error.into())),
7482+
};
7483+
let invoice_request = match invoice_request.verify(expanded_key, secp_ctx) {
7484+
Ok(invoice_request) => invoice_request,
7485+
Err(()) => {
7486+
let error = Bolt12SemanticError::InvalidMetadata;
7487+
return Some(OffersMessage::InvoiceError(error.into()));
7488+
},
7489+
};
7490+
let relative_expiry = DEFAULT_RELATIVE_EXPIRY.as_secs() as u32;
7491+
7492+
match self.create_inbound_payment(amount_msats, relative_expiry, None) {
7493+
Ok((payment_hash, _payment_secret)) if invoice_request.keys.is_some() => {
7494+
// TODO: Include payment_secret in payment_paths.
7495+
let payment_paths = vec![];
7496+
#[cfg(not(feature = "no-std"))]
7497+
let builder = invoice_request.respond_using_derived_keys(
7498+
payment_paths, payment_hash
7499+
);
7500+
#[cfg(feature = "no-std")]
7501+
let created_at = Duration::from_secs(
7502+
self.highest_seen_timestamp.load(Ordering::Acquire) as u64
7503+
);
7504+
#[cfg(feature = "no-std")]
7505+
let builder = invoice_request.respond_using_derived_keys_no_std(
7506+
payment_paths, payment_hash, created_at
7507+
);
7508+
match builder.and_then(|b| b.allow_mpp().build_and_sign(secp_ctx)) {
7509+
Ok(invoice) => Some(OffersMessage::Invoice(invoice)),
7510+
Err(error) => Some(OffersMessage::InvoiceError(error.into())),
7511+
}
7512+
},
7513+
Ok((payment_hash, _payment_secret)) => {
7514+
// TODO: Include payment_secret in payment_paths.
7515+
let payment_paths = vec![];
7516+
#[cfg(not(feature = "no-std"))]
7517+
let builder = invoice_request.respond_with(payment_paths, payment_hash);
7518+
#[cfg(feature = "no-std")]
7519+
let created_at = Duration::from_secs(
7520+
self.highest_seen_timestamp.load(Ordering::Acquire) as u64
7521+
);
7522+
#[cfg(feature = "no-std")]
7523+
let builder = invoice_request.respond_with_no_std(
7524+
payment_paths, payment_hash, created_at
7525+
);
7526+
let response = builder.and_then(|builder| builder.allow_mpp().build())
7527+
.map_err(|e| OffersMessage::InvoiceError(e.into()))
7528+
.and_then(|invoice|
7529+
match invoice.sign(|message: &TaggedHash, metadata: &[u8]|
7530+
self.node_signer.sign_bolt12_message(message, metadata)
7531+
) {
7532+
Ok(invoice) => Ok(OffersMessage::Invoice(invoice)),
7533+
Err(SignError::Signing(())) => Err(OffersMessage::InvoiceError(
7534+
InvoiceError::from_str("Failed signing invoice")
7535+
)),
7536+
Err(SignError::Verification(_)) => Err(OffersMessage::InvoiceError(
7537+
InvoiceError::from_str("Failed invoice signature verification")
7538+
)),
7539+
});
7540+
match response {
7541+
Ok(invoice) => Some(invoice),
7542+
Err(error) => Some(error),
7543+
}
7544+
},
7545+
Err(()) => {
7546+
Some(OffersMessage::InvoiceError(Bolt12SemanticError::InvalidAmount.into()))
7547+
},
7548+
}
7549+
},
7550+
OffersMessage::Invoice(invoice) => {
7551+
match invoice.verify(expanded_key, secp_ctx) {
7552+
Err(()) => {
7553+
Some(OffersMessage::InvoiceError(InvoiceError::from_str("Unrecognized invoice")))
7554+
},
7555+
Ok(_) if invoice.features().requires_unknown_bits() => {
7556+
Some(OffersMessage::InvoiceError(Bolt12SemanticError::UnknownRequiredFeatures.into()))
7557+
},
7558+
Ok(payment_id) => {
7559+
if let Err(e) = self.send_payment_for_bolt12_invoice(&invoice, payment_id) {
7560+
log_error!(self.logger, "Failed paying invoice: {:?}", e);
7561+
Some(OffersMessage::InvoiceError(InvoiceError::from_str(&format!("{:?}", e))))
7562+
} else {
7563+
None
7564+
}
7565+
},
7566+
}
7567+
},
7568+
OffersMessage::InvoiceError(invoice_error) => {
7569+
log_error!(self.logger, "Received invoice_error: {}", invoice_error);
7570+
None
7571+
},
7572+
}
7573+
}
7574+
}
7575+
74437576
/// Fetches the set of [`NodeFeatures`] flags which are provided by or required by
74447577
/// [`ChannelManager`].
74457578
pub(crate) fn provided_node_features(config: &UserConfig) -> NodeFeatures {

lightning/src/ln/outbound_payment.rs

+48
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use crate::events::{self, PaymentFailureReason};
1818
use crate::ln::{PaymentHash, PaymentPreimage, PaymentSecret};
1919
use crate::ln::channelmanager::{ChannelDetails, EventCompletionAction, HTLCSource, IDEMPOTENCY_TIMEOUT_TICKS, INVOICE_REQUEST_TIMEOUT_TICKS, PaymentId};
2020
use crate::ln::onion_utils::HTLCFailReason;
21+
use crate::offers::invoice::Bolt12Invoice;
2122
use crate::routing::router::{InFlightHtlcs, Path, PaymentParameters, Route, RouteParameters, Router};
2223
use crate::util::errors::APIError;
2324
use crate::util::logger::Logger;
@@ -341,6 +342,10 @@ pub enum RetryableSendFailure {
341342
/// [`Event::PaymentSent`]: crate::events::Event::PaymentSent
342343
/// [`Event::PaymentFailed`]: crate::events::Event::PaymentFailed
343344
DuplicatePayment,
345+
/// Indicates that a payment for the provided [`PaymentId`] was unexpected, either because the
346+
/// invoice was not explicitly requested (or for a known refund) or the invoice was not received
347+
/// in time (or was already paid).
348+
UnexpectedPayment,
344349
}
345350

346351
/// If a payment fails to send with [`ChannelManager::send_payment_with_route`], it can be in one
@@ -498,6 +503,8 @@ pub(super) struct SendAlongPathArgs<'a> {
498503
pub session_priv_bytes: [u8; 32],
499504
}
500505

506+
const BOLT_12_INVOICE_RETRY_STRATEGY: Retry = Retry::Attempts(3);
507+
501508
pub(super) struct OutboundPayments {
502509
pub(super) pending_outbound_payments: Mutex<HashMap<PaymentId, PendingOutboundPayment>>,
503510
pub(super) retry_lock: Mutex<()>,
@@ -531,6 +538,47 @@ impl OutboundPayments {
531538
best_block_height, logger, pending_events, &send_payment_along_path)
532539
}
533540

541+
pub(super) fn send_payment_for_bolt12_invoice<R: Deref, ES: Deref, NS: Deref, IH, SP, L: Deref>(
542+
&self, invoice: &Bolt12Invoice, payment_id: PaymentId, router: &R,
543+
first_hops: Vec<ChannelDetails>, compute_inflight_htlcs: IH, entropy_source: &ES,
544+
node_signer: &NS, best_block_height: u32, logger: &L,
545+
pending_events: &Mutex<VecDeque<(events::Event, Option<EventCompletionAction>)>>,
546+
send_payment_along_path: SP,
547+
) -> Result<(), RetryableSendFailure>
548+
where
549+
R::Target: Router,
550+
ES::Target: EntropySource,
551+
NS::Target: NodeSigner,
552+
L::Target: Logger,
553+
IH: Fn() -> InFlightHtlcs,
554+
SP: Fn(SendAlongPathArgs) -> Result<(), APIError>,
555+
{
556+
match self.pending_outbound_payments.lock().unwrap().entry(payment_id) {
557+
hash_map::Entry::Occupied(entry) => {
558+
if !entry.get().is_awaiting_invoice() {
559+
return Err(RetryableSendFailure::DuplicatePayment)
560+
}
561+
},
562+
hash_map::Entry::Vacant(_) => return Err(RetryableSendFailure::UnexpectedPayment),
563+
}
564+
565+
let recipient_onion = RecipientOnionFields {
566+
payment_secret: None,
567+
payment_metadata: None,
568+
};
569+
let payment_hash = invoice.payment_hash();
570+
let route_params = RouteParameters {
571+
payment_params: PaymentParameters::from_bolt12_invoice(&invoice),
572+
final_value_msat: invoice.amount_msats(),
573+
};
574+
575+
self.send_payment(
576+
payment_hash, recipient_onion, payment_id, BOLT_12_INVOICE_RETRY_STRATEGY, route_params,
577+
router, first_hops, compute_inflight_htlcs, entropy_source, node_signer,
578+
best_block_height, logger, pending_events, send_payment_along_path
579+
)
580+
}
581+
534582
pub(super) fn send_payment_with_route<ES: Deref, NS: Deref, F>(
535583
&self, route: &Route, payment_hash: PaymentHash, recipient_onion: RecipientOnionFields,
536584
payment_id: PaymentId, entropy_source: &ES, node_signer: &NS, best_block_height: u32,

lightning/src/offers/invoice.rs

+6-4
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ use crate::prelude::*;
130130
#[cfg(feature = "std")]
131131
use std::time::SystemTime;
132132

133-
const DEFAULT_RELATIVE_EXPIRY: Duration = Duration::from_secs(7200);
133+
pub(crate) const DEFAULT_RELATIVE_EXPIRY: Duration = Duration::from_secs(7200);
134134

135135
/// Tag for the hash function used when signing a [`Bolt12Invoice`]'s merkle root.
136136
pub const SIGNATURE_TAG: &'static str = concat!("lightning", "invoice", "signature");
@@ -176,7 +176,7 @@ impl<'a> InvoiceBuilder<'a, ExplicitSigningPubkey> {
176176
invoice_request: &'a InvoiceRequest, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>,
177177
created_at: Duration, payment_hash: PaymentHash
178178
) -> Result<Self, Bolt12SemanticError> {
179-
let amount_msats = Self::check_amount_msats(invoice_request)?;
179+
let amount_msats = Self::amount_msats(invoice_request)?;
180180
let signing_pubkey = invoice_request.contents.inner.offer.signing_pubkey();
181181
let contents = InvoiceContents::ForOffer {
182182
invoice_request: invoice_request.contents.clone(),
@@ -209,7 +209,7 @@ impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> {
209209
invoice_request: &'a InvoiceRequest, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>,
210210
created_at: Duration, payment_hash: PaymentHash, keys: KeyPair
211211
) -> Result<Self, Bolt12SemanticError> {
212-
let amount_msats = Self::check_amount_msats(invoice_request)?;
212+
let amount_msats = Self::amount_msats(invoice_request)?;
213213
let signing_pubkey = invoice_request.contents.inner.offer.signing_pubkey();
214214
let contents = InvoiceContents::ForOffer {
215215
invoice_request: invoice_request.contents.clone(),
@@ -239,7 +239,9 @@ impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> {
239239
}
240240

241241
impl<'a, S: SigningPubkeyStrategy> InvoiceBuilder<'a, S> {
242-
fn check_amount_msats(invoice_request: &InvoiceRequest) -> Result<u64, Bolt12SemanticError> {
242+
pub(crate) fn amount_msats(
243+
invoice_request: &InvoiceRequest
244+
) -> Result<u64, Bolt12SemanticError> {
243245
match invoice_request.amount_msats() {
244246
Some(amount_msats) => Ok(amount_msats),
245247
None => match invoice_request.contents.inner.offer.amount() {

lightning/src/offers/invoice_error.rs

+10
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,16 @@ pub struct ErroneousField {
4848
pub suggested_value: Option<Vec<u8>>,
4949
}
5050

51+
impl InvoiceError {
52+
/// Creates an [`InvoiceError`] with the given message.
53+
pub fn from_str(s: &str) -> Self {
54+
Self {
55+
erroneous_field: None,
56+
message: UntrustedString(s.to_string()),
57+
}
58+
}
59+
}
60+
5161
impl core::fmt::Display for InvoiceError {
5262
fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
5363
self.message.fmt(f)

0 commit comments

Comments
 (0)