Skip to content

Commit

Permalink
Move spontaneous payments API to SpontaneousPaymentsHandler
Browse files Browse the repository at this point in the history
  • Loading branch information
tnull committed Mar 5, 2024
1 parent 0e60eb7 commit 0f6b7e3
Show file tree
Hide file tree
Showing 6 changed files with 173 additions and 123 deletions.
12 changes: 8 additions & 4 deletions bindings/ldk_node.udl
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ interface Node {
PublicKey node_id();
sequence<SocketAddress>? listening_addresses();
Bolt11PaymentsHandler bolt11_payment();
SpontaneousPaymentsHandler spontaneous_payment();
[Throws=NodeError]
Address new_onchain_address();
[Throws=NodeError]
Expand All @@ -70,10 +71,6 @@ interface Node {
void update_channel_config([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id, ChannelConfig channel_config);
[Throws=NodeError]
void sync_wallets();
[Throws=NodeError]
PaymentHash send_spontaneous_payment(u64 amount_msat, PublicKey node_id);
[Throws=NodeError]
void send_spontaneous_payment_probes(u64 amount_msat, PublicKey node_id);
PaymentDetails? payment([ByRef]PaymentHash payment_hash);
[Throws=NodeError]
void remove_payment([ByRef]PaymentHash payment_hash);
Expand Down Expand Up @@ -106,6 +103,13 @@ interface Bolt11PaymentsHandler {
Bolt11Invoice receive_variable_amount_via_jit_channel([ByRef]string description, u32 expiry_secs, u64? max_proportional_lsp_fee_limit_ppm_msat);
};

interface SpontaneousPaymentsHandler {
[Throws=NodeError]
PaymentHash send(u64 amount_msat, PublicKey node_id);
[Throws=NodeError]
void send_probes(u64 amount_msat, PublicKey node_id);
};

[Error]
enum NodeError {
"AlreadyRunning",
Expand Down
131 changes: 16 additions & 115 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,14 +123,14 @@ pub use builder::BuildError;
pub use builder::NodeBuilder as Builder;

use config::{
LDK_PAYMENT_RETRY_TIMEOUT, NODE_ANN_BCAST_INTERVAL, PEER_RECONNECTION_INTERVAL,
RGS_SYNC_INTERVAL, WALLET_SYNC_INTERVAL_MINIMUM_SECS,
NODE_ANN_BCAST_INTERVAL, PEER_RECONNECTION_INTERVAL, RGS_SYNC_INTERVAL,
WALLET_SYNC_INTERVAL_MINIMUM_SECS,
};
use connection::ConnectionManager;
use event::{EventHandler, EventQueue};
use gossip::GossipSource;
use liquidity::LiquiditySource;
use payment::Bolt11PaymentsHandler;
use payment::{Bolt11PaymentsHandler, SpontaneousPaymentsHandler};
use payment_store::PaymentStore;
pub use payment_store::{LSPFeeLimits, PaymentDetails, PaymentDirection, PaymentStatus};
use peer_store::{PeerInfo, PeerStore};
Expand All @@ -143,11 +143,8 @@ pub use types::{ChannelDetails, PeerDetails, UserChannelId};
use logger::{log_error, log_info, log_trace, FilesystemLogger, Logger};

use lightning::chain::Confirm;
use lightning::ln::channelmanager::{self, PaymentId, RecipientOnionFields, Retry};
use lightning::ln::msgs::SocketAddress;
use lightning::ln::{PaymentHash, PaymentPreimage};

use lightning::sign::EntropySource;
use lightning::ln::PaymentHash;

use lightning::util::config::{ChannelHandshakeConfig, UserConfig};
pub use lightning::util::logger::Level as LogLevel;
Expand All @@ -156,8 +153,6 @@ use lightning_background_processor::process_events_async;

use lightning_transaction_sync::EsploraSyncClient;

use lightning::routing::router::{PaymentParameters, RouteParameters};

use bitcoin::secp256k1::PublicKey;
use bitcoin::{Address, Txid};

Expand Down Expand Up @@ -763,6 +758,18 @@ impl Node {
))
}

/// Returns a payment handler allowing to send spontaneous ("keysend") payments.
pub fn spontaneous_payment(&self) -> Arc<SpontaneousPaymentsHandler> {
Arc::new(SpontaneousPaymentsHandler::new(
Arc::clone(&self.runtime),
Arc::clone(&self.channel_manager),
Arc::clone(&self.keys_manager),
Arc::clone(&self.payment_store),
Arc::clone(&self.config),
Arc::clone(&self.logger),
))
}

/// Retrieve a new on-chain/funding address.
pub fn new_onchain_address(&self) -> Result<Address, Error> {
let funding_address = self.wallet.get_new_address()?;
Expand Down Expand Up @@ -1049,112 +1056,6 @@ impl Node {
}
}

/// Send a spontaneous, aka. "keysend", payment
pub fn send_spontaneous_payment(
&self, amount_msat: u64, node_id: PublicKey,
) -> Result<PaymentHash, Error> {
let rt_lock = self.runtime.read().unwrap();
if rt_lock.is_none() {
return Err(Error::NotRunning);
}

let payment_preimage = PaymentPreimage(self.keys_manager.get_secure_random_bytes());
let payment_hash = PaymentHash::from(payment_preimage);

if let Some(payment) = self.payment_store.get(&payment_hash) {
if payment.status == PaymentStatus::Pending
|| payment.status == PaymentStatus::Succeeded
{
log_error!(self.logger, "Payment error: must not send duplicate payments.");
return Err(Error::DuplicatePayment);
}
}

let route_params = RouteParameters::from_payment_params_and_value(
PaymentParameters::from_node_id(node_id, self.config.default_cltv_expiry_delta),
amount_msat,
);
let recipient_fields = RecipientOnionFields::spontaneous_empty();

match self.channel_manager.send_spontaneous_payment_with_retry(
Some(payment_preimage),
recipient_fields,
PaymentId(payment_hash.0),
route_params,
Retry::Timeout(LDK_PAYMENT_RETRY_TIMEOUT),
) {
Ok(_payment_id) => {
log_info!(self.logger, "Initiated sending {}msat to {}.", amount_msat, node_id);

let payment = PaymentDetails {
hash: payment_hash,
preimage: Some(payment_preimage),
secret: None,
status: PaymentStatus::Pending,
direction: PaymentDirection::Outbound,
amount_msat: Some(amount_msat),
lsp_fee_limits: None,
};
self.payment_store.insert(payment)?;

Ok(payment_hash)
},
Err(e) => {
log_error!(self.logger, "Failed to send payment: {:?}", e);

match e {
channelmanager::RetryableSendFailure::DuplicatePayment => {
Err(Error::DuplicatePayment)
},
_ => {
let payment = PaymentDetails {
hash: payment_hash,
preimage: Some(payment_preimage),
secret: None,
status: PaymentStatus::Failed,
direction: PaymentDirection::Outbound,
amount_msat: Some(amount_msat),
lsp_fee_limits: None,
};

self.payment_store.insert(payment)?;
Err(Error::PaymentSendingFailed)
},
}
},
}
}

/// Sends payment probes over all paths of a route that would be used to pay the given
/// amount to the given `node_id`.
///
/// See [`Bolt11PaymentsHandler::send_probes`] for more information.
pub fn send_spontaneous_payment_probes(
&self, amount_msat: u64, node_id: PublicKey,
) -> Result<(), Error> {
let rt_lock = self.runtime.read().unwrap();
if rt_lock.is_none() {
return Err(Error::NotRunning);
}

let liquidity_limit_multiplier = Some(self.config.probing_liquidity_limit_multiplier);
let cltv_expiry_delta = self.config.default_cltv_expiry_delta;

self.channel_manager
.send_spontaneous_preflight_probes(
node_id,
amount_msat,
cltv_expiry_delta,
liquidity_limit_multiplier,
)
.map_err(|e| {
log_error!(self.logger, "Failed to send payment probes: {:?}", e);
Error::ProbeSendingFailed
})?;

Ok(())
}

/// Retrieve the details of a specific payment with the given hash.
///
/// Returns `Some` if the payment was known and `None` otherwise.
Expand Down
2 changes: 2 additions & 0 deletions src/payment/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
//! Handlers for different types of payments.
mod bolt11;
mod spontaneous;

pub use bolt11::Bolt11PaymentsHandler;
pub use spontaneous::SpontaneousPaymentsHandler;
145 changes: 145 additions & 0 deletions src/payment/spontaneous.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
//! Holds a payment handler allowing to send spontaneous ("keysend") payments.
//!
//! [`BOLT11`]: https://github.com/lightning/bolts/blob/master/11-payment-encoding.md
use crate::config::{Config, LDK_PAYMENT_RETRY_TIMEOUT};
use crate::error::Error;
use crate::logger::{log_error, log_info, FilesystemLogger, Logger};
use crate::payment_store::{PaymentDetails, PaymentDirection, PaymentStatus, PaymentStore};
use crate::types::{ChannelManager, KeysManager};

use lightning::ln::channelmanager::{PaymentId, RecipientOnionFields, Retry, RetryableSendFailure};
use lightning::ln::{PaymentHash, PaymentPreimage};
use lightning::routing::router::{PaymentParameters, RouteParameters};
use lightning::sign::EntropySource;

use bitcoin::secp256k1::PublicKey;

use std::sync::{Arc, RwLock};

/// A payment handler allowing to send spontaneous ("keysend") payments.
///
/// Should be retrieved by calling [`Node::spontaneous_payment`].
///
/// [`Node::spontaneous_payment`]: crate::Node::spontaneous_payment
pub struct SpontaneousPaymentsHandler {
runtime: Arc<RwLock<Option<tokio::runtime::Runtime>>>,
channel_manager: Arc<ChannelManager>,
keys_manager: Arc<KeysManager>,
payment_store: Arc<PaymentStore<Arc<FilesystemLogger>>>,
config: Arc<Config>,
logger: Arc<FilesystemLogger>,
}

impl SpontaneousPaymentsHandler {
pub(crate) fn new(
runtime: Arc<RwLock<Option<tokio::runtime::Runtime>>>,
channel_manager: Arc<ChannelManager>, keys_manager: Arc<KeysManager>,
payment_store: Arc<PaymentStore<Arc<FilesystemLogger>>>, config: Arc<Config>,
logger: Arc<FilesystemLogger>,
) -> Self {
Self { runtime, channel_manager, keys_manager, payment_store, config, logger }
}

/// Send a spontaneous, aka. "keysend", payment
pub fn send(&self, amount_msat: u64, node_id: PublicKey) -> Result<PaymentHash, Error> {
let rt_lock = self.runtime.read().unwrap();
if rt_lock.is_none() {
return Err(Error::NotRunning);
}

let payment_preimage = PaymentPreimage(self.keys_manager.get_secure_random_bytes());
let payment_hash = PaymentHash::from(payment_preimage);

if let Some(payment) = self.payment_store.get(&payment_hash) {
if payment.status == PaymentStatus::Pending
|| payment.status == PaymentStatus::Succeeded
{
log_error!(self.logger, "Payment error: must not send duplicate payments.");
return Err(Error::DuplicatePayment);
}
}

let route_params = RouteParameters::from_payment_params_and_value(
PaymentParameters::from_node_id(node_id, self.config.default_cltv_expiry_delta),
amount_msat,
);
let recipient_fields = RecipientOnionFields::spontaneous_empty();

match self.channel_manager.send_spontaneous_payment_with_retry(
Some(payment_preimage),
recipient_fields,
PaymentId(payment_hash.0),
route_params,
Retry::Timeout(LDK_PAYMENT_RETRY_TIMEOUT),
) {
Ok(_payment_id) => {
log_info!(self.logger, "Initiated sending {}msat to {}.", amount_msat, node_id);

let payment = PaymentDetails {
hash: payment_hash,
preimage: Some(payment_preimage),
secret: None,
status: PaymentStatus::Pending,
direction: PaymentDirection::Outbound,
amount_msat: Some(amount_msat),
lsp_fee_limits: None,
};
self.payment_store.insert(payment)?;

Ok(payment_hash)
},
Err(e) => {
log_error!(self.logger, "Failed to send payment: {:?}", e);

match e {
RetryableSendFailure::DuplicatePayment => Err(Error::DuplicatePayment),
_ => {
let payment = PaymentDetails {
hash: payment_hash,
preimage: Some(payment_preimage),
secret: None,
status: PaymentStatus::Failed,
direction: PaymentDirection::Outbound,
amount_msat: Some(amount_msat),
lsp_fee_limits: None,
};

self.payment_store.insert(payment)?;
Err(Error::PaymentSendingFailed)
},
}
},
}
}

/// Sends payment probes over all paths of a route that would be used to pay the given
/// amount to the given `node_id`.
///
/// See [`Bolt11PaymentsHandler::send_probes`] for more information.
///
/// [`Bolt11PaymentsHandler::send_probes`]: crate::payment::Bolt11PaymentsHandler
pub fn send_probes(&self, amount_msat: u64, node_id: PublicKey) -> Result<(), Error> {
let rt_lock = self.runtime.read().unwrap();
if rt_lock.is_none() {
return Err(Error::NotRunning);
}

let liquidity_limit_multiplier = Some(self.config.probing_liquidity_limit_multiplier);
let cltv_expiry_delta = self.config.default_cltv_expiry_delta;

self.channel_manager
.send_spontaneous_preflight_probes(
node_id,
amount_msat,
cltv_expiry_delta,
liquidity_limit_multiplier,
)
.map_err(|e| {
log_error!(self.logger, "Failed to send payment probes: {:?}", e);
Error::ProbeSendingFailed
})?;

Ok(())
}
}
4 changes: 1 addition & 3 deletions src/uniffi_types.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
pub use lightning::events::{ClosureReason, PaymentFailureReason};
pub use lightning::ln::ChannelId;
pub use lightning::ln::PaymentSecret;
pub use lightning::ln::{ChannelId, PaymentHash, PaymentPreimage, PaymentSecret};
pub use lightning::util::string::UntrustedString;

pub use lightning_invoice::Bolt11Invoice;
Expand All @@ -19,7 +18,6 @@ use bitcoin::hashes::sha256::Hash as Sha256;
use bitcoin::hashes::Hash;
use bitcoin::secp256k1::PublicKey;
use bitcoin::{Address, Txid};
use lightning::ln::{PaymentHash, PaymentPreimage};
use lightning_invoice::SignedRawBolt11Invoice;

use std::convert::TryInto;
Expand Down
2 changes: 1 addition & 1 deletion tests/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,7 @@ pub(crate) fn do_channel_full_cycle<E: ElectrumApi>(
println!("\nA send_spontaneous_payment");
let keysend_amount_msat = 2500_000;
let keysend_payment_hash =
node_a.send_spontaneous_payment(keysend_amount_msat, node_b.node_id()).unwrap();
node_a.spontaneous_payment().send(keysend_amount_msat, node_b.node_id()).unwrap();
expect_event!(node_a, PaymentSuccessful);
let received_keysend_amount = match node_b.wait_next_event() {
ref e @ Event::PaymentReceived { amount_msat, .. } => {
Expand Down

0 comments on commit 0f6b7e3

Please sign in to comment.