diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index 5f9d91855..3f41b9ad1 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -52,6 +52,7 @@ interface Node { PublicKey node_id(); sequence? listening_addresses(); Bolt11PaymentsHandler bolt11_payment(); + SpontaneousPaymentsHandler spontaneous_payment(); [Throws=NodeError] Address new_onchain_address(); [Throws=NodeError] @@ -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); @@ -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", diff --git a/src/lib.rs b/src/lib.rs index 8b1c3101b..3f83f77c4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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}; @@ -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; @@ -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}; @@ -763,6 +758,18 @@ impl Node { )) } + /// Returns a payment handler allowing to send spontaneous ("keysend") payments. + pub fn spontaneous_payment(&self) -> Arc { + 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 { let funding_address = self.wallet.get_new_address()?; @@ -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 { - 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. diff --git a/src/payment/mod.rs b/src/payment/mod.rs index d2864a70a..8821d7cc5 100644 --- a/src/payment/mod.rs +++ b/src/payment/mod.rs @@ -1,5 +1,7 @@ //! Handlers for different types of payments. mod bolt11; +mod spontaneous; pub use bolt11::Bolt11PaymentsHandler; +pub use spontaneous::SpontaneousPaymentsHandler; diff --git a/src/payment/spontaneous.rs b/src/payment/spontaneous.rs new file mode 100644 index 000000000..efc9f053e --- /dev/null +++ b/src/payment/spontaneous.rs @@ -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>>, + channel_manager: Arc, + keys_manager: Arc, + payment_store: Arc>>, + config: Arc, + logger: Arc, +} + +impl SpontaneousPaymentsHandler { + pub(crate) fn new( + runtime: Arc>>, + channel_manager: Arc, keys_manager: Arc, + payment_store: Arc>>, config: Arc, + logger: Arc, + ) -> 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 { + 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(()) + } +} diff --git a/src/uniffi_types.rs b/src/uniffi_types.rs index 959c6e9ed..0cef466a1 100644 --- a/src/uniffi_types.rs +++ b/src/uniffi_types.rs @@ -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; @@ -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; diff --git a/tests/common.rs b/tests/common.rs index ea88f7318..a89fdd837 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -495,7 +495,7 @@ pub(crate) fn do_channel_full_cycle( 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, .. } => {