Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions bindings/ldk_node.udl
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,18 @@ interface Bolt11Payment {
[Throws=NodeError]
void send_probes_using_amount([ByRef]Bolt11Invoice invoice, u64 amount_msat);
[Throws=NodeError]
void claim_for_hash(PaymentHash payment_hash, u64 claimable_amount_msat, PaymentPreimage preimage);
[Throws=NodeError]
void fail_for_hash(PaymentHash payment_hash);
[Throws=NodeError]
Bolt11Invoice receive(u64 amount_msat, [ByRef]string description, u32 expiry_secs);
[Throws=NodeError]
Bolt11Invoice receive_for_hash(u64 amount_msat, [ByRef]string description, u32 expiry_secs, PaymentHash payment_hash);
[Throws=NodeError]
Bolt11Invoice receive_variable_amount([ByRef]string description, u32 expiry_secs);
[Throws=NodeError]
Bolt11Invoice receive_variable_amount_for_hash([ByRef]string description, u32 expiry_secs, PaymentHash payment_hash);
[Throws=NodeError]
Bolt11Invoice receive_via_jit_channel(u64 amount_msat, [ByRef]string description, u32 expiry_secs, u64? max_lsp_fee_limit_msat);
[Throws=NodeError]
Bolt11Invoice receive_variable_amount_via_jit_channel([ByRef]string description, u32 expiry_secs, u64? max_proportional_lsp_fee_limit_ppm_msat);
Expand Down Expand Up @@ -222,6 +230,7 @@ interface Event {
PaymentSuccessful(PaymentId? payment_id, PaymentHash payment_hash, u64? fee_paid_msat);
PaymentFailed(PaymentId? payment_id, PaymentHash payment_hash, PaymentFailureReason? reason);
PaymentReceived(PaymentId? payment_id, PaymentHash payment_hash, u64 amount_msat);
PaymentClaimable(PaymentId payment_id, PaymentHash payment_hash, u64 claimable_amount_msat, u32? claim_deadline);
ChannelPending(ChannelId channel_id, UserChannelId user_channel_id, ChannelId former_temporary_channel_id, PublicKey counterparty_node_id, OutPoint funding_txo);
ChannelReady(ChannelId channel_id, UserChannelId user_channel_id, PublicKey? counterparty_node_id);
ChannelClosed(ChannelId channel_id, UserChannelId user_channel_id, PublicKey? counterparty_node_id, ClosureReason? reason);
Expand Down Expand Up @@ -285,6 +294,7 @@ dictionary PaymentDetails {
u64? amount_msat;
PaymentDirection direction;
PaymentStatus status;
u64 latest_update_timestamp;
};

[NonExhaustive]
Expand Down
125 changes: 104 additions & 21 deletions src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,28 @@ pub enum Event {
/// The value, in thousandths of a satoshi, that has been received.
amount_msat: u64,
},
/// A payment for a previously-registered payment hash has been received.
///
/// This needs to be manually claimed by supplying the correct preimage to [`claim_for_hash`].
///
/// If the the provided parameters don't match the expectations or the preimage can't be
/// retrieved in time, should be failed-back via [`fail_for_hash`].
///
/// Note claiming will necessarily fail after the `claim_deadline` has been reached.
///
/// [`claim_for_hash`]: crate::payment::Bolt11Payment::claim_for_hash
/// [`fail_for_hash`]: crate::payment::Bolt11Payment::fail_for_hash
PaymentClaimable {
/// A local identifier used to track the payment.
payment_id: PaymentId,
/// The hash of the payment.
payment_hash: PaymentHash,
/// The value, in thousandths of a satoshi, that is claimable.
claimable_amount_msat: u64,
/// The block height at which this payment will be failed back and will no longer be
/// eligible for claiming.
claim_deadline: Option<u32>,
},
/// A channel has been created and is pending confirmation on-chain.
ChannelPending {
/// The `channel_id` of the channel.
Expand Down Expand Up @@ -156,6 +178,12 @@ impl_writeable_tlv_based_enum!(Event,
(1, counterparty_node_id, option),
(2, user_channel_id, required),
(3, reason, upgradable_option),
},
(6, PaymentClaimable) => {
(0, payment_hash, required),
(2, payment_id, required),
(4, claimable_amount_msat, required),
(6, claim_deadline, option),
};
);

Expand Down Expand Up @@ -434,12 +462,31 @@ where
receiver_node_id: _,
via_channel_id: _,
via_user_channel_id: _,
claim_deadline: _,
claim_deadline,
onion_fields: _,
counterparty_skimmed_fee_msat,
} => {
let payment_id = PaymentId(payment_hash.0);
if let Some(info) = self.payment_store.get(&payment_id) {
if info.direction == PaymentDirection::Outbound {
log_info!(
self.logger,
"Refused inbound payment with ID {}: circular payments are unsupported.",
payment_id
);
self.channel_manager.fail_htlc_backwards(&payment_hash);

let update = PaymentDetailsUpdate {
status: Some(PaymentStatus::Failed),
..PaymentDetailsUpdate::new(payment_id)
};
self.payment_store.update(&update).unwrap_or_else(|e| {
log_error!(self.logger, "Failed to access payment store: {}", e);
panic!("Failed to access payment store");
});
return;
}

if info.status == PaymentStatus::Succeeded
|| matches!(info.kind, PaymentKind::Spontaneous { .. })
{
Expand Down Expand Up @@ -500,6 +547,38 @@ where
});
return;
}

// If this is known by the store but ChannelManager doesn't know the preimage,
// the payment has been registered via `_for_hash` variants and needs to be manually claimed via
// user interaction.
match info.kind {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I'm currently not fully sure how we'll be able to cover the BOLT12 flow, I refrained from adding a new PaymentKind for these kind of payments here. Instead, I'm just leaning on us knowing the payment but ChannelManager not knowing the preimage. We might however want to 'upgrade' to a more expressive PaymentKind once we include the BOLT12 flow. Not sure if you have an opinion on this, @jkczyz?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to differentiate them? Checking PaymentPurpose::preimage().is_none() irrespective of the variant should work, IIUC.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to differentiate them? Checking PaymentPurpose::preimage().is_none() irrespective of the variant should work, IIUC.

Well, for BOLT12 the flow will be a bit different, as we currently don't register inbound payments with the payment store. We'll then need to mark them as pending in the new event I guess. At that point we could consider whether we want to add a different PaymentKind variant, or, as we'll generally aim to move the invoice/offer registration to an to-be-added invoice/offer storage could consider whether to add a is_hold or similar flag there. At least these are things we could discuss in the future, in any case for that reason I'd kept it as simple as possible for now.

PaymentKind::Bolt11 { preimage, .. } => {
if purpose.preimage().is_none() {
debug_assert!(
preimage.is_none(),
"We would have registered the preimage if we knew"
);

self.event_queue
.add_event(Event::PaymentClaimable {
payment_id,
payment_hash,
claimable_amount_msat: amount_msat,
claim_deadline,
})
.unwrap_or_else(|e| {
log_error!(
self.logger,
"Failed to push to event queue: {}",
e
);
panic!("Failed to push to event queue");
});
return;
}
},
_ => {},
}
}

log_info!(
Expand All @@ -519,19 +598,21 @@ where
..
} => {
let offer_id = payment_context.offer_id;
let payment = PaymentDetails {
id: payment_id,
kind: PaymentKind::Bolt12Offer {
hash: Some(payment_hash),
preimage: payment_preimage,
secret: Some(payment_secret),
offer_id,
},
amount_msat: Some(amount_msat),
direction: PaymentDirection::Inbound,
status: PaymentStatus::Pending,
let kind = PaymentKind::Bolt12Offer {
hash: Some(payment_hash),
preimage: payment_preimage,
secret: Some(payment_secret),
offer_id,
};

let payment = PaymentDetails::new(
payment_id,
kind,
Some(amount_msat),
PaymentDirection::Inbound,
PaymentStatus::Pending,
);

match self.payment_store.insert(payment) {
Ok(false) => (),
Ok(true) => {
Expand Down Expand Up @@ -559,17 +640,19 @@ where
},
PaymentPurpose::SpontaneousPayment(preimage) => {
// Since it's spontaneous, we insert it now into our store.
let payment = PaymentDetails {
id: payment_id,
kind: PaymentKind::Spontaneous {
hash: payment_hash,
preimage: Some(preimage),
},
amount_msat: Some(amount_msat),
direction: PaymentDirection::Inbound,
status: PaymentStatus::Pending,
let kind = PaymentKind::Spontaneous {
hash: payment_hash,
preimage: Some(preimage),
};

let payment = PaymentDetails::new(
payment_id,
kind,
Some(amount_msat),
PaymentDirection::Inbound,
PaymentStatus::Pending,
);

match self.payment_store.insert(payment) {
Ok(false) => (),
Ok(true) => {
Expand Down
Loading