Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Keysend custom tlv #411

Merged
merged 1 commit into from
Dec 9, 2024
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
12 changes: 10 additions & 2 deletions bindings/ldk_node.udl
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@ interface SpontaneousPayment {
[Throws=NodeError]
PaymentId send(u64 amount_msat, PublicKey node_id, SendingParameters? sending_parameters);
[Throws=NodeError]
PaymentId send_with_custom_tlvs(u64 amount_msat, PublicKey node_id, SendingParameters? sending_parameters, sequence<CustomTlvRecord> custom_tlvs);
[Throws=NodeError]
void send_probes(u64 amount_msat, PublicKey node_id);
};

Expand Down Expand Up @@ -182,6 +184,7 @@ enum NodeError {
"OfferCreationFailed",
"RefundCreationFailed",
"PaymentSendingFailed",
"InvalidCustomTlvs",
"ProbeSendingFailed",
"ChannelCreationFailed",
"ChannelClosingFailed",
Expand Down Expand Up @@ -275,8 +278,8 @@ enum VssHeaderProviderError {
interface Event {
PaymentSuccessful(PaymentId? payment_id, PaymentHash payment_hash, PaymentPreimage? payment_preimage, 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);
PaymentReceived(PaymentId? payment_id, PaymentHash payment_hash, u64 amount_msat, sequence<CustomTlvRecord> custom_records);
PaymentClaimable(PaymentId payment_id, PaymentHash payment_hash, u64 claimable_amount_msat, u32? claim_deadline, sequence<CustomTlvRecord> custom_records);
PaymentForwarded(ChannelId prev_channel_id, ChannelId next_channel_id, UserChannelId? prev_user_channel_id, UserChannelId? next_user_channel_id, u64? total_fee_earned_msat, u64? skimmed_fee_msat, boolean claim_from_onchain_tx, u64? outbound_amount_forwarded_msat);
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);
Expand Down Expand Up @@ -362,6 +365,11 @@ dictionary SendingParameters {
u8? max_channel_saturation_power_of_half;
};

dictionary CustomTlvRecord {
u64 type_num;
sequence<u8> value;
};

[Enum]
interface MaxTotalRoutingFeeLimit {
None ();
Expand Down
3 changes: 3 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ pub enum Error {
RefundCreationFailed,
/// Sending a payment has failed.
PaymentSendingFailed,
/// Sending of spontaneous payment with custom TLVs failed.
InvalidCustomTlvs,
/// Sending a payment probe has failed.
ProbeSendingFailed,
/// A channel could not be opened.
Expand Down Expand Up @@ -130,6 +132,7 @@ impl fmt::Display for Error {
Self::OfferCreationFailed => write!(f, "Failed to create offer."),
Self::RefundCreationFailed => write!(f, "Failed to create refund."),
Self::PaymentSendingFailed => write!(f, "Failed to send the given payment."),
Self::InvalidCustomTlvs => write!(f, "Failed to construct payment with custom TLVs."),
Self::ProbeSendingFailed => write!(f, "Failed to send the given payment probe."),
Self::ChannelCreationFailed => write!(f, "Failed to create channel."),
Self::ChannelClosingFailed => write!(f, "Failed to close channel."),
Expand Down
21 changes: 18 additions & 3 deletions src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// http://opensource.org/licenses/MIT>, at your option. You may not use this file except in
// accordance with one or both of these licenses.

use crate::types::{DynStore, Sweeper, Wallet};
use crate::types::{CustomTlvRecord, DynStore, Sweeper, Wallet};

use crate::{
hex_utils, BumpTransactionEventHandler, ChannelManager, Config, Error, Graph, PeerInfo,
Expand Down Expand Up @@ -102,6 +102,8 @@ pub enum Event {
payment_hash: PaymentHash,
/// The value, in thousandths of a satoshi, that has been received.
amount_msat: u64,
/// Custom TLV records received on the payment
custom_records: Vec<CustomTlvRecord>,
},
/// A payment has been forwarded.
PaymentForwarded {
Expand Down Expand Up @@ -168,6 +170,8 @@ pub enum Event {
/// The block height at which this payment will be failed back and will no longer be
/// eligible for claiming.
claim_deadline: Option<u32>,
/// Custom TLV records attached to the payment
custom_records: Vec<CustomTlvRecord>,
},
/// A channel has been created and is pending confirmation on-chain.
ChannelPending {
Expand Down Expand Up @@ -224,6 +228,7 @@ impl_writeable_tlv_based_enum!(Event,
(0, payment_hash, required),
(1, payment_id, option),
(2, amount_msat, required),
(3, custom_records, optional_vec),
},
(3, ChannelReady) => {
(0, channel_id, required),
Expand All @@ -248,6 +253,7 @@ impl_writeable_tlv_based_enum!(Event,
(2, payment_id, required),
(4, claimable_amount_msat, required),
(6, claim_deadline, option),
(7, custom_records, optional_vec),
},
(7, PaymentForwarded) => {
(0, prev_channel_id, required),
Expand Down Expand Up @@ -542,7 +548,7 @@ where
via_channel_id: _,
via_user_channel_id: _,
claim_deadline,
onion_fields: _,
onion_fields,
counterparty_skimmed_fee_msat,
} => {
let payment_id = PaymentId(payment_hash.0);
Expand Down Expand Up @@ -644,11 +650,17 @@ where
"We would have registered the preimage if we knew"
);

let custom_records = onion_fields
.map(|cf| {
cf.custom_tlvs().into_iter().map(|tlv| tlv.into()).collect()
})
.unwrap_or_default();
let event = Event::PaymentClaimable {
payment_id,
payment_hash,
claimable_amount_msat: amount_msat,
claim_deadline,
custom_records,
};
match self.event_queue.add_event(event) {
Ok(_) => return Ok(()),
Expand Down Expand Up @@ -799,7 +811,7 @@ where
receiver_node_id: _,
htlcs: _,
sender_intended_total_msat: _,
onion_fields: _,
onion_fields,
} => {
let payment_id = PaymentId(payment_hash.0);
log_info!(
Expand Down Expand Up @@ -875,6 +887,9 @@ where
payment_id: Some(payment_id),
payment_hash,
amount_msat,
custom_records: onion_fields
.map(|cf| cf.custom_tlvs().into_iter().map(|tlv| tlv.into()).collect())
.unwrap_or_default(),
};
match self.event_queue.add_event(event) {
Ok(_) => return Ok(()),
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ use types::{
Broadcaster, BumpTransactionEventHandler, ChainMonitor, ChannelManager, DynStore, Graph,
KeysManager, OnionMessenger, PeerManager, Router, Scorer, Sweeper, Wallet,
};
pub use types::{ChannelDetails, PeerDetails, UserChannelId};
pub use types::{ChannelDetails, CustomTlvRecord, PeerDetails, UserChannelId};

use logger::{log_error, log_info, log_trace, FilesystemLogger, Logger};

Expand Down
27 changes: 25 additions & 2 deletions src/payment/spontaneous.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use crate::payment::store::{
PaymentDetails, PaymentDirection, PaymentKind, PaymentStatus, PaymentStore,
};
use crate::payment::SendingParameters;
use crate::types::{ChannelManager, KeysManager};
use crate::types::{ChannelManager, CustomTlvRecord, KeysManager};

use lightning::ln::channelmanager::{PaymentId, RecipientOnionFields, Retry, RetryableSendFailure};
use lightning::ln::{PaymentHash, PaymentPreimage};
Expand Down Expand Up @@ -58,6 +58,21 @@ impl SpontaneousPayment {
/// node-wide parameters configured via [`Config::sending_parameters`] on a per-field basis.
pub fn send(
&self, amount_msat: u64, node_id: PublicKey, sending_parameters: Option<SendingParameters>,
) -> Result<PaymentId, Error> {
self.send_inner(amount_msat, node_id, sending_parameters, None)
}

/// Send a spontaneous payment including a list of custom TLVs.
pub fn send_with_custom_tlvs(
&self, amount_msat: u64, node_id: PublicKey, sending_parameters: Option<SendingParameters>,
custom_tlvs: Vec<CustomTlvRecord>,
) -> Result<PaymentId, Error> {
self.send_inner(amount_msat, node_id, sending_parameters, Some(custom_tlvs))
}

fn send_inner(
&self, amount_msat: u64, node_id: PublicKey, sending_parameters: Option<SendingParameters>,
custom_tlvs: Option<Vec<CustomTlvRecord>>,
) -> Result<PaymentId, Error> {
let rt_lock = self.runtime.read().unwrap();
if rt_lock.is_none() {
Expand Down Expand Up @@ -97,7 +112,15 @@ impl SpontaneousPayment {
.map(|s| route_params.payment_params.max_channel_saturation_power_of_half = s);
};

let recipient_fields = RecipientOnionFields::spontaneous_empty();
let recipient_fields = match custom_tlvs {
Some(tlvs) => RecipientOnionFields::spontaneous_empty()
.with_custom_tlvs(tlvs.into_iter().map(|tlv| (tlv.type_num, tlv.value)).collect())
.map_err(|e| {
log_error!(self.logger, "Failed to send payment with custom TLVs: {:?}", e);
Error::InvalidCustomTlvs
})?,
None => RecipientOnionFields::spontaneous_empty(),
};

match self.channel_manager.send_spontaneous_payment_with_retry(
Some(payment_preimage),
Expand Down
21 changes: 21 additions & 0 deletions src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use crate::logger::FilesystemLogger;
use crate::message_handler::NodeCustomMessageHandler;

use lightning::chain::chainmonitor;
use lightning::impl_writeable_tlv_based;
use lightning::ln::channel_state::ChannelDetails as LdkChannelDetails;
use lightning::ln::msgs::RoutingMessageHandler;
use lightning::ln::msgs::SocketAddress;
Expand Down Expand Up @@ -348,3 +349,23 @@ pub struct PeerDetails {
/// Indicates whether we currently have an active connection with the peer.
pub is_connected: bool,
}

/// Custom TLV entry.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CustomTlvRecord {
/// Type number.
pub type_num: u64,
/// Serialized value.
pub value: Vec<u8>,
}

impl_writeable_tlv_based!(CustomTlvRecord, {
(0, type_num, required),
(2, value, required),
});

impl From<&(u64, Vec<u8>)> for CustomTlvRecord {
fn from(tlv: &(u64, Vec<u8>)) -> Self {
CustomTlvRecord { type_num: tlv.0, value: tlv.1.clone() }
}
}
1 change: 1 addition & 0 deletions src/uniffi_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub use crate::config::{
pub use crate::graph::{ChannelInfo, ChannelUpdateInfo, NodeAnnouncementInfo, NodeInfo};
pub use crate::payment::store::{LSPFeeLimits, PaymentDirection, PaymentKind, PaymentStatus};
pub use crate::payment::{MaxTotalRoutingFeeLimit, QrPaymentResult, SendingParameters};
pub use crate::types::CustomTlvRecord;

pub use lightning::chain::channelmonitor::BalanceSource;
pub use lightning::events::{ClosureReason, PaymentFailureReason};
Expand Down
20 changes: 14 additions & 6 deletions tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
use ldk_node::config::{Config, EsploraSyncConfig};
use ldk_node::io::sqlite_store::SqliteStore;
use ldk_node::payment::{PaymentDirection, PaymentKind, PaymentStatus};
use ldk_node::{Builder, Event, LightningBalance, LogLevel, Node, NodeError, PendingSweepBalance};
use ldk_node::{
Builder, CustomTlvRecord, Event, LightningBalance, LogLevel, Node, NodeError,
PendingSweepBalance,
};

use lightning::ln::msgs::SocketAddress;
use lightning::ln::{PaymentHash, PaymentPreimage};
Expand Down Expand Up @@ -751,14 +754,18 @@ pub(crate) fn do_channel_full_cycle<E: ElectrumApi>(
// Test spontaneous/keysend payments
println!("\nA send_spontaneous_payment");
let keysend_amount_msat = 2500_000;
let keysend_payment_id =
node_a.spontaneous_payment().send(keysend_amount_msat, node_b.node_id(), None).unwrap();
let custom_tlvs = vec![CustomTlvRecord { type_num: 13377331, value: vec![1, 2, 3] }];
let keysend_payment_id = node_a
.spontaneous_payment()
.send_with_custom_tlvs(keysend_amount_msat, node_b.node_id(), None, custom_tlvs.clone())
.unwrap();
expect_event!(node_a, PaymentSuccessful);
let received_keysend_amount = match node_b.wait_next_event() {
ref e @ Event::PaymentReceived { amount_msat, .. } => {
let next_event = node_b.wait_next_event();
let (received_keysend_amount, received_custom_records) = match next_event {
ref e @ Event::PaymentReceived { amount_msat, ref custom_records, .. } => {
println!("{} got event {:?}", std::stringify!(node_b), e);
node_b.event_handled();
amount_msat
(amount_msat, custom_records)
},
ref e => {
panic!("{} got unexpected event!: {:?}", std::stringify!(node_b), e);
Expand All @@ -772,6 +779,7 @@ pub(crate) fn do_channel_full_cycle<E: ElectrumApi>(
node_a.payment(&keysend_payment_id).unwrap().kind,
PaymentKind::Spontaneous { .. }
));
assert_eq!(received_custom_records, &custom_tlvs);
assert_eq!(node_b.payment(&keysend_payment_id).unwrap().status, PaymentStatus::Succeeded);
assert_eq!(node_b.payment(&keysend_payment_id).unwrap().direction, PaymentDirection::Inbound);
assert_eq!(node_b.payment(&keysend_payment_id).unwrap().amount_msat, Some(keysend_amount_msat));
Expand Down
Loading