From 0d2ea7153d2c64d6adae359b45f08a7da850a916 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Tue, 5 Mar 2024 11:58:42 +0100 Subject: [PATCH] Move BOLT11 payments API to `Bolt11PaymentsHandler` --- README.md | 4 +- .../lightningdevkit/ldknode/LibraryTest.kt | 4 +- bindings/ldk_node.udl | 36 +- bindings/python/src/ldk_node/test_ldk_node.py | 4 +- src/lib.rs | 484 +--------------- src/payment/bolt11.rs | 517 ++++++++++++++++++ src/payment/mod.rs | 5 + src/uniffi_types.rs | 4 +- tests/common.rs | 64 ++- tests/integration_tests_cln.rs | 4 +- tests/integration_tests_rust.rs | 4 +- 11 files changed, 609 insertions(+), 521 deletions(-) create mode 100644 src/payment/bolt11.rs create mode 100644 src/payment/mod.rs diff --git a/README.md b/README.md index 270bf25a7..eccb85c40 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ A ready-to-go Lightning node library built using [LDK][ldk] and [BDK][bdk]. LDK Node is a self-custodial Lightning node in library form. Its central goal is to provide a small, simple, and straightforward interface that enables users to easily set up and run a Lightning node with an integrated on-chain wallet. While minimalism is at its core, LDK Node aims to be sufficiently modular and configurable to be useful for a variety of use cases. ## Getting Started -The primary abstraction of the library is the [`Node`][api_docs_node], which can be retrieved by setting up and configuring a [`Builder`][api_docs_builder] to your liking and calling one of the `build` methods. `Node` can then be controlled via commands such as `start`, `stop`, `connect_open_channel`, `send_payment`, etc. +The primary abstraction of the library is the [`Node`][api_docs_node], which can be retrieved by setting up and configuring a [`Builder`][api_docs_builder] to your liking and calling one of the `build` methods. `Node` can then be controlled via commands such as `start`, `stop`, `connect_open_channel`, `send`, etc. ```rust use ldk_node::Builder; @@ -44,7 +44,7 @@ fn main() { node.event_handled(); let invoice = Bolt11Invoice::from_str("INVOICE_STR").unwrap(); - node.send_payment(&invoice).unwrap(); + node.bolt11_payment().send(&invoice).unwrap(); node.stop().unwrap(); } diff --git a/bindings/kotlin/ldk-node-jvm/lib/src/test/kotlin/org/lightningdevkit/ldknode/LibraryTest.kt b/bindings/kotlin/ldk-node-jvm/lib/src/test/kotlin/org/lightningdevkit/ldknode/LibraryTest.kt index 39c19821d..c3fec8fb6 100644 --- a/bindings/kotlin/ldk-node-jvm/lib/src/test/kotlin/org/lightningdevkit/ldknode/LibraryTest.kt +++ b/bindings/kotlin/ldk-node-jvm/lib/src/test/kotlin/org/lightningdevkit/ldknode/LibraryTest.kt @@ -222,9 +222,9 @@ class LibraryTest { else -> return } - val invoice = node2.receivePayment(2500000u, "asdf", 9217u) + val invoice = node2.bolt11_payment().receive(2500000u, "asdf", 9217u) - node1.sendPayment(invoice) + node1.bolt11_payment().send(invoice) val paymentSuccessfulEvent = node1.waitNextEvent() println("Got event: $paymentSuccessfulEvent") diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index cb674abed..5f9d91855 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -51,6 +51,7 @@ interface Node { void event_handled(); PublicKey node_id(); sequence? listening_addresses(); + Bolt11PaymentsHandler bolt11_payment(); [Throws=NodeError] Address new_onchain_address(); [Throws=NodeError] @@ -70,25 +71,9 @@ interface Node { [Throws=NodeError] void sync_wallets(); [Throws=NodeError] - PaymentHash send_payment([ByRef]Bolt11Invoice invoice); - [Throws=NodeError] - PaymentHash send_payment_using_amount([ByRef]Bolt11Invoice invoice, u64 amount_msat); - [Throws=NodeError] PaymentHash send_spontaneous_payment(u64 amount_msat, PublicKey node_id); [Throws=NodeError] - void send_payment_probes([ByRef]Bolt11Invoice invoice); - [Throws=NodeError] void send_spontaneous_payment_probes(u64 amount_msat, PublicKey node_id); - [Throws=NodeError] - void send_payment_probes_using_amount([ByRef]Bolt11Invoice invoice, u64 amount_msat); - [Throws=NodeError] - Bolt11Invoice receive_payment(u64 amount_msat, [ByRef]string description, u32 expiry_secs); - [Throws=NodeError] - Bolt11Invoice receive_variable_amount_payment([ByRef]string description, u32 expiry_secs); - [Throws=NodeError] - Bolt11Invoice receive_payment_via_jit_channel(u64 amount_msat, [ByRef]string description, u32 expiry_secs, u64? max_lsp_fee_limit_msat); - [Throws=NodeError] - Bolt11Invoice receive_variable_amount_payment_via_jit_channel([ByRef]string description, u32 expiry_secs, u64? max_proportional_lsp_fee_limit_ppm_msat); PaymentDetails? payment([ByRef]PaymentHash payment_hash); [Throws=NodeError] void remove_payment([ByRef]PaymentHash payment_hash); @@ -102,6 +87,25 @@ interface Node { boolean is_running(); }; +interface Bolt11PaymentsHandler { + [Throws=NodeError] + PaymentHash send([ByRef]Bolt11Invoice invoice); + [Throws=NodeError] + PaymentHash send_using_amount([ByRef]Bolt11Invoice invoice, u64 amount_msat); + [Throws=NodeError] + void send_probes([ByRef]Bolt11Invoice invoice); + [Throws=NodeError] + void send_probes_using_amount([ByRef]Bolt11Invoice invoice, u64 amount_msat); + [Throws=NodeError] + Bolt11Invoice receive(u64 amount_msat, [ByRef]string description, u32 expiry_secs); + [Throws=NodeError] + Bolt11Invoice receive_variable_amount([ByRef]string description, u32 expiry_secs); + [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); +}; + [Error] enum NodeError { "AlreadyRunning", diff --git a/bindings/python/src/ldk_node/test_ldk_node.py b/bindings/python/src/ldk_node/test_ldk_node.py index 864ef7b43..468800efe 100644 --- a/bindings/python/src/ldk_node/test_ldk_node.py +++ b/bindings/python/src/ldk_node/test_ldk_node.py @@ -185,8 +185,8 @@ def test_channel_full_cycle(self): print("EVENT:", channel_ready_event_2) node_2.event_handled() - invoice = node_2.receive_payment(2500000, "asdf", 9217) - node_1.send_payment(invoice) + invoice = node_2.bolt11_payment().receive(2500000, "asdf", 9217) + node_1.bolt11_payment().send(invoice) payment_successful_event_1 = node_1.wait_next_event() assert isinstance(payment_successful_event_1, Event.PAYMENT_SUCCESSFUL) diff --git a/src/lib.rs b/src/lib.rs index e51be4210..912be5fc5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,7 +23,7 @@ //! The primary abstraction of the library is the [`Node`], which can be retrieved by setting up //! and configuring a [`Builder`] to your liking and calling [`build`]. `Node` can then be //! controlled via commands such as [`start`], [`stop`], [`connect_open_channel`], -//! [`send_payment`], etc.: +//! [`send`], etc.: //! //! ```no_run //! use ldk_node::Builder; @@ -56,7 +56,7 @@ //! node.event_handled(); //! //! let invoice = Bolt11Invoice::from_str("INVOICE_STR").unwrap(); -//! node.send_payment(&invoice).unwrap(); +//! node.bolt11_payment().send(&invoice).unwrap(); //! //! node.stop().unwrap(); //! } @@ -66,7 +66,7 @@ //! [`start`]: Node::start //! [`stop`]: Node::stop //! [`connect_open_channel`]: Node::connect_open_channel -//! [`send_payment`]: Node::send_payment +//! [`send`]: Bolt11PaymentsHandler::send //! #![cfg_attr(not(feature = "uniffi"), deny(missing_docs))] #![deny(rustdoc::broken_intra_doc_links)] @@ -88,6 +88,7 @@ pub mod io; mod liquidity; mod logger; mod message_handler; +pub mod payment; mod payment_store; mod peer_store; mod sweep; @@ -129,6 +130,7 @@ use connection::ConnectionManager; use event::{EventHandler, EventQueue}; use gossip::GossipSource; use liquidity::LiquiditySource; +use payment::Bolt11PaymentsHandler; use payment_store::PaymentStore; pub use payment_store::{LSPFeeLimits, PaymentDetails, PaymentDirection, PaymentStatus}; use peer_store::{PeerInfo, PeerStore}; @@ -155,11 +157,8 @@ use lightning_background_processor::process_events_async; use lightning_transaction_sync::EsploraSyncClient; use lightning::routing::router::{PaymentParameters, RouteParameters}; -use lightning_invoice::{payment, Bolt11Invoice, Currency}; -use bitcoin::hashes::Hash; use bitcoin::secp256k1::PublicKey; - use bitcoin::{Address, Txid}; use rand::Rng; @@ -748,6 +747,23 @@ impl Node { self.config.listening_addresses.clone() } + /// Returns a payment handler allowing to create and pay [`BOLT11`] invoices. + /// + /// [`BOLT11`]: https://github.com/lightning/bolts/blob/master/11-payment-encoding.md + pub fn bolt11_payment(&self) -> Arc { + Arc::new(Bolt11PaymentsHandler::new( + Arc::clone(&self.runtime), + Arc::clone(&self.channel_manager), + Arc::clone(&self.connection_manager), + Arc::clone(&self.keys_manager), + self.liquidity_source.clone(), + Arc::clone(&self.payment_store), + Arc::clone(&self.peer_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()?; @@ -1034,190 +1050,6 @@ impl Node { } } - /// Send a payment given an invoice. - pub fn send_payment(&self, invoice: &Bolt11Invoice) -> Result { - let rt_lock = self.runtime.read().unwrap(); - if rt_lock.is_none() { - return Err(Error::NotRunning); - } - - let (payment_hash, recipient_onion, route_params) = payment::payment_parameters_from_invoice(&invoice).map_err(|_| { - log_error!(self.logger, "Failed to send payment due to the given invoice being \"zero-amount\". Please use send_payment_using_amount instead."); - Error::InvalidInvoice - })?; - - 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: an invoice must not be paid twice."); - return Err(Error::DuplicatePayment); - } - } - - let payment_secret = Some(*invoice.payment_secret()); - let payment_id = PaymentId(invoice.payment_hash().to_byte_array()); - let retry_strategy = Retry::Timeout(LDK_PAYMENT_RETRY_TIMEOUT); - - match self.channel_manager.send_payment( - payment_hash, - recipient_onion, - payment_id, - route_params, - retry_strategy, - ) { - Ok(()) => { - let payee_pubkey = invoice.recover_payee_pub_key(); - let amt_msat = invoice.amount_milli_satoshis().unwrap(); - log_info!(self.logger, "Initiated sending {}msat to {}", amt_msat, payee_pubkey); - - let payment = PaymentDetails { - preimage: None, - hash: payment_hash, - secret: payment_secret, - amount_msat: invoice.amount_milli_satoshis(), - direction: PaymentDirection::Outbound, - status: PaymentStatus::Pending, - 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 { - preimage: None, - hash: payment_hash, - secret: payment_secret, - amount_msat: invoice.amount_milli_satoshis(), - direction: PaymentDirection::Outbound, - status: PaymentStatus::Failed, - lsp_fee_limits: None, - }; - - self.payment_store.insert(payment)?; - Err(Error::PaymentSendingFailed) - }, - } - }, - } - } - - /// Send a payment given an invoice and an amount in millisatoshi. - /// - /// This will fail if the amount given is less than the value required by the given invoice. - /// - /// This can be used to pay a so-called "zero-amount" invoice, i.e., an invoice that leaves the - /// amount paid to be determined by the user. - pub fn send_payment_using_amount( - &self, invoice: &Bolt11Invoice, amount_msat: u64, - ) -> Result { - let rt_lock = self.runtime.read().unwrap(); - if rt_lock.is_none() { - return Err(Error::NotRunning); - } - - if let Some(invoice_amount_msat) = invoice.amount_milli_satoshis() { - if amount_msat < invoice_amount_msat { - log_error!( - self.logger, - "Failed to pay as the given amount needs to be at least the invoice amount: required {}msat, gave {}msat.", invoice_amount_msat, amount_msat); - return Err(Error::InvalidAmount); - } - } - - let payment_hash = PaymentHash(invoice.payment_hash().to_byte_array()); - 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: an invoice must not be paid twice."); - return Err(Error::DuplicatePayment); - } - } - - let payment_id = PaymentId(invoice.payment_hash().to_byte_array()); - let payment_secret = invoice.payment_secret(); - let expiry_time = invoice.duration_since_epoch().saturating_add(invoice.expiry_time()); - let mut payment_params = PaymentParameters::from_node_id( - invoice.recover_payee_pub_key(), - invoice.min_final_cltv_expiry_delta() as u32, - ) - .with_expiry_time(expiry_time.as_secs()) - .with_route_hints(invoice.route_hints()) - .map_err(|_| Error::InvalidInvoice)?; - if let Some(features) = invoice.features() { - payment_params = payment_params - .with_bolt11_features(features.clone()) - .map_err(|_| Error::InvalidInvoice)?; - } - let route_params = - RouteParameters::from_payment_params_and_value(payment_params, amount_msat); - - let retry_strategy = Retry::Timeout(LDK_PAYMENT_RETRY_TIMEOUT); - let recipient_fields = RecipientOnionFields::secret_only(*payment_secret); - - match self.channel_manager.send_payment( - payment_hash, - recipient_fields, - payment_id, - route_params, - retry_strategy, - ) { - Ok(_payment_id) => { - let payee_pubkey = invoice.recover_payee_pub_key(); - log_info!( - self.logger, - "Initiated sending {} msat to {}", - amount_msat, - payee_pubkey - ); - - let payment = PaymentDetails { - hash: payment_hash, - preimage: None, - secret: Some(*payment_secret), - amount_msat: Some(amount_msat), - direction: PaymentDirection::Outbound, - status: PaymentStatus::Pending, - 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: None, - secret: Some(*payment_secret), - amount_msat: Some(amount_msat), - direction: PaymentDirection::Outbound, - status: PaymentStatus::Failed, - lsp_fee_limits: None, - }; - self.payment_store.insert(payment)?; - - Err(Error::PaymentSendingFailed) - }, - } - }, - } - } - /// Send a spontaneous, aka. "keysend", payment pub fn send_spontaneous_payment( &self, amount_msat: u64, node_id: PublicKey, @@ -1294,46 +1126,10 @@ impl Node { } } - /// Sends payment probes over all paths of a route that would be used to pay the given invoice. - /// - /// This may be used to send "pre-flight" probes, i.e., to train our scorer before conducting - /// the actual payment. Note this is only useful if there likely is sufficient time for the - /// probe to settle before sending out the actual payment, e.g., when waiting for user - /// confirmation in a wallet UI. - /// - /// Otherwise, there is a chance the probe could take up some liquidity needed to complete the - /// actual payment. Users should therefore be cautious and might avoid sending probes if - /// liquidity is scarce and/or they don't expect the probe to return before they send the - /// payment. To mitigate this issue, channels with available liquidity less than the required - /// amount times [`Config::probing_liquidity_limit_multiplier`] won't be used to send - /// pre-flight probes. - pub fn send_payment_probes(&self, invoice: &Bolt11Invoice) -> Result<(), Error> { - let rt_lock = self.runtime.read().unwrap(); - if rt_lock.is_none() { - return Err(Error::NotRunning); - } - - let (_payment_hash, _recipient_onion, route_params) = payment::payment_parameters_from_invoice(&invoice).map_err(|_| { - log_error!(self.logger, "Failed to send probes due to the given invoice being \"zero-amount\". Please use send_payment_probes_using_amount instead."); - Error::InvalidInvoice - })?; - - let liquidity_limit_multiplier = Some(self.config.probing_liquidity_limit_multiplier); - - self.channel_manager - .send_preflight_probes(route_params, liquidity_limit_multiplier) - .map_err(|e| { - log_error!(self.logger, "Failed to send payment probes: {:?}", e); - Error::ProbeSendingFailed - })?; - - Ok(()) - } - /// Sends payment probes over all paths of a route that would be used to pay the given /// amount to the given `node_id`. /// - /// See [`Self::send_payment_probes`] for more information. + /// See [`Bolt11PaymentsHandler::send_probes`] for more information. pub fn send_spontaneous_payment_probes( &self, amount_msat: u64, node_id: PublicKey, ) -> Result<(), Error> { @@ -1360,240 +1156,6 @@ impl Node { Ok(()) } - /// Sends payment probes over all paths of a route that would be used to pay the given - /// zero-value invoice using the given amount. - /// - /// This can be used to send pre-flight probes for a so-called "zero-amount" invoice, i.e., an - /// invoice that leaves the amount paid to be determined by the user. - /// - /// See [`Self::send_payment_probes`] for more information. - pub fn send_payment_probes_using_amount( - &self, invoice: &Bolt11Invoice, amount_msat: u64, - ) -> Result<(), Error> { - let rt_lock = self.runtime.read().unwrap(); - if rt_lock.is_none() { - return Err(Error::NotRunning); - } - - let (_payment_hash, _recipient_onion, route_params) = if let Some(invoice_amount_msat) = - invoice.amount_milli_satoshis() - { - if amount_msat < invoice_amount_msat { - log_error!( - self.logger, - "Failed to send probes as the given amount needs to be at least the invoice amount: required {}msat, gave {}msat.", invoice_amount_msat, amount_msat); - return Err(Error::InvalidAmount); - } - - payment::payment_parameters_from_invoice(&invoice).map_err(|_| { - log_error!(self.logger, "Failed to send probes due to the given invoice unexpectedly being \"zero-amount\"."); - Error::InvalidInvoice - })? - } else { - payment::payment_parameters_from_zero_amount_invoice(&invoice, amount_msat).map_err(|_| { - log_error!(self.logger, "Failed to send probes due to the given invoice unexpectedly being not \"zero-amount\"."); - Error::InvalidInvoice - })? - }; - - let liquidity_limit_multiplier = Some(self.config.probing_liquidity_limit_multiplier); - - self.channel_manager - .send_preflight_probes(route_params, liquidity_limit_multiplier) - .map_err(|e| { - log_error!(self.logger, "Failed to send payment probes: {:?}", e); - Error::ProbeSendingFailed - })?; - - Ok(()) - } - - /// Returns a payable invoice that can be used to request and receive a payment of the amount - /// given. - pub fn receive_payment( - &self, amount_msat: u64, description: &str, expiry_secs: u32, - ) -> Result { - self.receive_payment_inner(Some(amount_msat), description, expiry_secs) - } - - /// Returns a payable invoice that can be used to request and receive a payment for which the - /// amount is to be determined by the user, also known as a "zero-amount" invoice. - pub fn receive_variable_amount_payment( - &self, description: &str, expiry_secs: u32, - ) -> Result { - self.receive_payment_inner(None, description, expiry_secs) - } - - fn receive_payment_inner( - &self, amount_msat: Option, description: &str, expiry_secs: u32, - ) -> Result { - let currency = Currency::from(self.config.network); - let keys_manager = Arc::clone(&self.keys_manager); - let invoice = match lightning_invoice::utils::create_invoice_from_channelmanager( - &self.channel_manager, - keys_manager, - Arc::clone(&self.logger), - currency, - amount_msat, - description.to_string(), - expiry_secs, - None, - ) { - Ok(inv) => { - log_info!(self.logger, "Invoice created: {}", inv); - inv - }, - Err(e) => { - log_error!(self.logger, "Failed to create invoice: {}", e); - return Err(Error::InvoiceCreationFailed); - }, - }; - - let payment_hash = PaymentHash(invoice.payment_hash().to_byte_array()); - let payment = PaymentDetails { - hash: payment_hash, - preimage: None, - secret: Some(invoice.payment_secret().clone()), - amount_msat, - direction: PaymentDirection::Inbound, - status: PaymentStatus::Pending, - lsp_fee_limits: None, - }; - - self.payment_store.insert(payment)?; - - Ok(invoice) - } - - /// Returns a payable invoice that can be used to request a payment of the amount given and - /// receive it via a newly created just-in-time (JIT) channel. - /// - /// When the returned invoice is paid, the configured [LSPS2]-compliant LSP will open a channel - /// to us, supplying just-in-time inbound liquidity. - /// - /// If set, `max_total_lsp_fee_limit_msat` will limit how much fee we allow the LSP to take for opening the - /// channel to us. We'll use its cheapest offer otherwise. - /// - /// [LSPS2]: https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md - pub fn receive_payment_via_jit_channel( - &self, amount_msat: u64, description: &str, expiry_secs: u32, - max_total_lsp_fee_limit_msat: Option, - ) -> Result { - self.receive_payment_via_jit_channel_inner( - Some(amount_msat), - description, - expiry_secs, - max_total_lsp_fee_limit_msat, - None, - ) - } - - /// Returns a payable invoice that can be used to request a variable amount payment (also known - /// as "zero-amount" invoice) and receive it via a newly created just-in-time (JIT) channel. - /// - /// When the returned invoice is paid, the configured [LSPS2]-compliant LSP will open a channel - /// to us, supplying just-in-time inbound liquidity. - /// - /// If set, `max_proportional_lsp_fee_limit_ppm_msat` will limit how much proportional fee, in - /// parts-per-million millisatoshis, we allow the LSP to take for opening the channel to us. - /// We'll use its cheapest offer otherwise. - /// - /// [LSPS2]: https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md - pub fn receive_variable_amount_payment_via_jit_channel( - &self, description: &str, expiry_secs: u32, - max_proportional_lsp_fee_limit_ppm_msat: Option, - ) -> Result { - self.receive_payment_via_jit_channel_inner( - None, - description, - expiry_secs, - None, - max_proportional_lsp_fee_limit_ppm_msat, - ) - } - - fn receive_payment_via_jit_channel_inner( - &self, amount_msat: Option, description: &str, expiry_secs: u32, - max_total_lsp_fee_limit_msat: Option, - max_proportional_lsp_fee_limit_ppm_msat: Option, - ) -> Result { - let liquidity_source = - self.liquidity_source.as_ref().ok_or(Error::LiquiditySourceUnavailable)?; - - let (node_id, address) = liquidity_source - .get_liquidity_source_details() - .ok_or(Error::LiquiditySourceUnavailable)?; - - let rt_lock = self.runtime.read().unwrap(); - let runtime = rt_lock.as_ref().unwrap(); - - let peer_info = PeerInfo { node_id, address }; - - let con_node_id = peer_info.node_id; - let con_addr = peer_info.address.clone(); - let con_cm = Arc::clone(&self.connection_manager); - - // We need to use our main runtime here as a local runtime might not be around to poll - // connection futures going forward. - tokio::task::block_in_place(move || { - runtime.block_on(async move { - con_cm.connect_peer_if_necessary(con_node_id, con_addr).await - }) - })?; - - log_info!(self.logger, "Connected to LSP {}@{}. ", peer_info.node_id, peer_info.address); - - let liquidity_source = Arc::clone(&liquidity_source); - let (invoice, lsp_total_opening_fee, lsp_prop_opening_fee) = - tokio::task::block_in_place(move || { - runtime.block_on(async move { - if let Some(amount_msat) = amount_msat { - liquidity_source - .lsps2_receive_to_jit_channel( - amount_msat, - description, - expiry_secs, - max_total_lsp_fee_limit_msat, - ) - .await - .map(|(invoice, total_fee)| (invoice, Some(total_fee), None)) - } else { - liquidity_source - .lsps2_receive_variable_amount_to_jit_channel( - description, - expiry_secs, - max_proportional_lsp_fee_limit_ppm_msat, - ) - .await - .map(|(invoice, prop_fee)| (invoice, None, Some(prop_fee))) - } - }) - })?; - - // Register payment in payment store. - let payment_hash = PaymentHash(invoice.payment_hash().to_byte_array()); - let lsp_fee_limits = Some(LSPFeeLimits { - max_total_opening_fee_msat: lsp_total_opening_fee, - max_proportional_opening_fee_ppm_msat: lsp_prop_opening_fee, - }); - let payment = PaymentDetails { - hash: payment_hash, - preimage: None, - secret: Some(invoice.payment_secret().clone()), - amount_msat, - direction: PaymentDirection::Inbound, - status: PaymentStatus::Pending, - lsp_fee_limits, - }; - - self.payment_store.insert(payment)?; - - // Persist LSP peer to make sure we reconnect on restart. - self.peer_store.add_peer(peer_info)?; - - Ok(invoice) - } - /// 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/bolt11.rs b/src/payment/bolt11.rs new file mode 100644 index 000000000..87243aaf6 --- /dev/null +++ b/src/payment/bolt11.rs @@ -0,0 +1,517 @@ +//! Holds a payment handler allowing to create and pay [`BOLT11`] invoices. +//! +//! [`BOLT11`]: https://github.com/lightning/bolts/blob/master/11-payment-encoding.md + +use crate::config::{Config, LDK_PAYMENT_RETRY_TIMEOUT}; +use crate::connection::ConnectionManager; +use crate::error::Error; +use crate::liquidity::LiquiditySource; +use crate::logger::{log_error, log_info, FilesystemLogger, Logger}; +use crate::payment_store::{ + LSPFeeLimits, PaymentDetails, PaymentDirection, PaymentStatus, PaymentStore, +}; +use crate::peer_store::{PeerInfo, PeerStore}; +use crate::types::{ChannelManager, KeysManager}; + +use lightning::ln::channelmanager::{PaymentId, RecipientOnionFields, Retry, RetryableSendFailure}; +use lightning::ln::PaymentHash; +use lightning::routing::router::{PaymentParameters, RouteParameters}; + +use lightning_invoice::{payment, Bolt11Invoice, Currency}; + +use bitcoin::hashes::Hash; + +use std::sync::{Arc, RwLock}; + +/// A payment handler allowing to create and pay [`BOLT11`] invoices. +/// +/// Should be retrieved by calling [`Node::bolt11_payment`]. +/// +/// [`BOLT11`]: https://github.com/lightning/bolts/blob/master/11-payment-encoding.md +/// [`Node::bolt11_payment`]: crate::Node::bolt11_payment +pub struct Bolt11PaymentsHandler { + runtime: Arc>>, + channel_manager: Arc, + connection_manager: Arc>>, + keys_manager: Arc, + liquidity_source: Option>>>, + payment_store: Arc>>, + peer_store: Arc>>, + config: Arc, + logger: Arc, +} + +impl Bolt11PaymentsHandler { + pub(crate) fn new( + runtime: Arc>>, + channel_manager: Arc, + connection_manager: Arc>>, + keys_manager: Arc, + liquidity_source: Option>>>, + payment_store: Arc>>, + peer_store: Arc>>, config: Arc, + logger: Arc, + ) -> Self { + Self { + runtime, + channel_manager, + connection_manager, + keys_manager, + liquidity_source, + payment_store, + peer_store, + config, + logger, + } + } + + /// Send a payment given an invoice. + pub fn send(&self, invoice: &Bolt11Invoice) -> Result { + let rt_lock = self.runtime.read().unwrap(); + if rt_lock.is_none() { + return Err(Error::NotRunning); + } + + let (payment_hash, recipient_onion, route_params) = payment::payment_parameters_from_invoice(&invoice).map_err(|_| { + log_error!(self.logger, "Failed to send payment due to the given invoice being \"zero-amount\". Please use send_using_amount instead."); + Error::InvalidInvoice + })?; + + 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: an invoice must not be paid twice."); + return Err(Error::DuplicatePayment); + } + } + + let payment_secret = Some(*invoice.payment_secret()); + let payment_id = PaymentId(invoice.payment_hash().to_byte_array()); + let retry_strategy = Retry::Timeout(LDK_PAYMENT_RETRY_TIMEOUT); + + match self.channel_manager.send_payment( + payment_hash, + recipient_onion, + payment_id, + route_params, + retry_strategy, + ) { + Ok(()) => { + let payee_pubkey = invoice.recover_payee_pub_key(); + let amt_msat = invoice.amount_milli_satoshis().unwrap(); + log_info!(self.logger, "Initiated sending {}msat to {}", amt_msat, payee_pubkey); + + let payment = PaymentDetails { + preimage: None, + hash: payment_hash, + secret: payment_secret, + amount_msat: invoice.amount_milli_satoshis(), + direction: PaymentDirection::Outbound, + status: PaymentStatus::Pending, + 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 { + preimage: None, + hash: payment_hash, + secret: payment_secret, + amount_msat: invoice.amount_milli_satoshis(), + direction: PaymentDirection::Outbound, + status: PaymentStatus::Failed, + lsp_fee_limits: None, + }; + + self.payment_store.insert(payment)?; + Err(Error::PaymentSendingFailed) + }, + } + }, + } + } + + /// Send a payment given an invoice and an amount in millisatoshi. + /// + /// This will fail if the amount given is less than the value required by the given invoice. + /// + /// This can be used to pay a so-called "zero-amount" invoice, i.e., an invoice that leaves the + /// amount paid to be determined by the user. + pub fn send_using_amount( + &self, invoice: &Bolt11Invoice, amount_msat: u64, + ) -> Result { + let rt_lock = self.runtime.read().unwrap(); + if rt_lock.is_none() { + return Err(Error::NotRunning); + } + + if let Some(invoice_amount_msat) = invoice.amount_milli_satoshis() { + if amount_msat < invoice_amount_msat { + log_error!( + self.logger, + "Failed to pay as the given amount needs to be at least the invoice amount: required {}msat, gave {}msat.", invoice_amount_msat, amount_msat); + return Err(Error::InvalidAmount); + } + } + + let payment_hash = PaymentHash(invoice.payment_hash().to_byte_array()); + 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: an invoice must not be paid twice."); + return Err(Error::DuplicatePayment); + } + } + + let payment_id = PaymentId(invoice.payment_hash().to_byte_array()); + let payment_secret = invoice.payment_secret(); + let expiry_time = invoice.duration_since_epoch().saturating_add(invoice.expiry_time()); + let mut payment_params = PaymentParameters::from_node_id( + invoice.recover_payee_pub_key(), + invoice.min_final_cltv_expiry_delta() as u32, + ) + .with_expiry_time(expiry_time.as_secs()) + .with_route_hints(invoice.route_hints()) + .map_err(|_| Error::InvalidInvoice)?; + if let Some(features) = invoice.features() { + payment_params = payment_params + .with_bolt11_features(features.clone()) + .map_err(|_| Error::InvalidInvoice)?; + } + let route_params = + RouteParameters::from_payment_params_and_value(payment_params, amount_msat); + + let retry_strategy = Retry::Timeout(LDK_PAYMENT_RETRY_TIMEOUT); + let recipient_fields = RecipientOnionFields::secret_only(*payment_secret); + + match self.channel_manager.send_payment( + payment_hash, + recipient_fields, + payment_id, + route_params, + retry_strategy, + ) { + Ok(_payment_id) => { + let payee_pubkey = invoice.recover_payee_pub_key(); + log_info!( + self.logger, + "Initiated sending {} msat to {}", + amount_msat, + payee_pubkey + ); + + let payment = PaymentDetails { + hash: payment_hash, + preimage: None, + secret: Some(*payment_secret), + amount_msat: Some(amount_msat), + direction: PaymentDirection::Outbound, + status: PaymentStatus::Pending, + 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: None, + secret: Some(*payment_secret), + amount_msat: Some(amount_msat), + direction: PaymentDirection::Outbound, + status: PaymentStatus::Failed, + lsp_fee_limits: None, + }; + self.payment_store.insert(payment)?; + + Err(Error::PaymentSendingFailed) + }, + } + }, + } + } + + /// Returns a payable invoice that can be used to request and receive a payment of the amount + /// given. + pub fn receive( + &self, amount_msat: u64, description: &str, expiry_secs: u32, + ) -> Result { + self.receive_inner(Some(amount_msat), description, expiry_secs) + } + + /// Returns a payable invoice that can be used to request and receive a payment for which the + /// amount is to be determined by the user, also known as a "zero-amount" invoice. + pub fn receive_variable_amount( + &self, description: &str, expiry_secs: u32, + ) -> Result { + self.receive_inner(None, description, expiry_secs) + } + + fn receive_inner( + &self, amount_msat: Option, description: &str, expiry_secs: u32, + ) -> Result { + let currency = Currency::from(self.config.network); + let keys_manager = Arc::clone(&self.keys_manager); + let invoice = match lightning_invoice::utils::create_invoice_from_channelmanager( + &self.channel_manager, + keys_manager, + Arc::clone(&self.logger), + currency, + amount_msat, + description.to_string(), + expiry_secs, + None, + ) { + Ok(inv) => { + log_info!(self.logger, "Invoice created: {}", inv); + inv + }, + Err(e) => { + log_error!(self.logger, "Failed to create invoice: {}", e); + return Err(Error::InvoiceCreationFailed); + }, + }; + + let payment_hash = PaymentHash(invoice.payment_hash().to_byte_array()); + let payment = PaymentDetails { + hash: payment_hash, + preimage: None, + secret: Some(invoice.payment_secret().clone()), + amount_msat, + direction: PaymentDirection::Inbound, + status: PaymentStatus::Pending, + lsp_fee_limits: None, + }; + + self.payment_store.insert(payment)?; + + Ok(invoice) + } + + /// Returns a payable invoice that can be used to request a payment of the amount given and + /// receive it via a newly created just-in-time (JIT) channel. + /// + /// When the returned invoice is paid, the configured [LSPS2]-compliant LSP will open a channel + /// to us, supplying just-in-time inbound liquidity. + /// + /// If set, `max_total_lsp_fee_limit_msat` will limit how much fee we allow the LSP to take for opening the + /// channel to us. We'll use its cheapest offer otherwise. + /// + /// [LSPS2]: https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md + pub fn receive_via_jit_channel( + &self, amount_msat: u64, description: &str, expiry_secs: u32, + max_total_lsp_fee_limit_msat: Option, + ) -> Result { + self.receive_via_jit_channel_inner( + Some(amount_msat), + description, + expiry_secs, + max_total_lsp_fee_limit_msat, + None, + ) + } + + /// Returns a payable invoice that can be used to request a variable amount payment (also known + /// as "zero-amount" invoice) and receive it via a newly created just-in-time (JIT) channel. + /// + /// When the returned invoice is paid, the configured [LSPS2]-compliant LSP will open a channel + /// to us, supplying just-in-time inbound liquidity. + /// + /// If set, `max_proportional_lsp_fee_limit_ppm_msat` will limit how much proportional fee, in + /// parts-per-million millisatoshis, we allow the LSP to take for opening the channel to us. + /// We'll use its cheapest offer otherwise. + /// + /// [LSPS2]: https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md + pub fn receive_variable_amount_via_jit_channel( + &self, description: &str, expiry_secs: u32, + max_proportional_lsp_fee_limit_ppm_msat: Option, + ) -> Result { + self.receive_via_jit_channel_inner( + None, + description, + expiry_secs, + None, + max_proportional_lsp_fee_limit_ppm_msat, + ) + } + + fn receive_via_jit_channel_inner( + &self, amount_msat: Option, description: &str, expiry_secs: u32, + max_total_lsp_fee_limit_msat: Option, + max_proportional_lsp_fee_limit_ppm_msat: Option, + ) -> Result { + let liquidity_source = + self.liquidity_source.as_ref().ok_or(Error::LiquiditySourceUnavailable)?; + + let (node_id, address) = liquidity_source + .get_liquidity_source_details() + .ok_or(Error::LiquiditySourceUnavailable)?; + + let rt_lock = self.runtime.read().unwrap(); + let runtime = rt_lock.as_ref().unwrap(); + + let peer_info = PeerInfo { node_id, address }; + + let con_node_id = peer_info.node_id; + let con_addr = peer_info.address.clone(); + let con_cm = Arc::clone(&self.connection_manager); + + // We need to use our main runtime here as a local runtime might not be around to poll + // connection futures going forward. + tokio::task::block_in_place(move || { + runtime.block_on(async move { + con_cm.connect_peer_if_necessary(con_node_id, con_addr).await + }) + })?; + + log_info!(self.logger, "Connected to LSP {}@{}. ", peer_info.node_id, peer_info.address); + + let liquidity_source = Arc::clone(&liquidity_source); + let (invoice, lsp_total_opening_fee, lsp_prop_opening_fee) = + tokio::task::block_in_place(move || { + runtime.block_on(async move { + if let Some(amount_msat) = amount_msat { + liquidity_source + .lsps2_receive_to_jit_channel( + amount_msat, + description, + expiry_secs, + max_total_lsp_fee_limit_msat, + ) + .await + .map(|(invoice, total_fee)| (invoice, Some(total_fee), None)) + } else { + liquidity_source + .lsps2_receive_variable_amount_to_jit_channel( + description, + expiry_secs, + max_proportional_lsp_fee_limit_ppm_msat, + ) + .await + .map(|(invoice, prop_fee)| (invoice, None, Some(prop_fee))) + } + }) + })?; + + // Register payment in payment store. + let payment_hash = PaymentHash(invoice.payment_hash().to_byte_array()); + let lsp_fee_limits = Some(LSPFeeLimits { + max_total_opening_fee_msat: lsp_total_opening_fee, + max_proportional_opening_fee_ppm_msat: lsp_prop_opening_fee, + }); + let payment = PaymentDetails { + hash: payment_hash, + preimage: None, + secret: Some(invoice.payment_secret().clone()), + amount_msat, + direction: PaymentDirection::Inbound, + status: PaymentStatus::Pending, + lsp_fee_limits, + }; + + self.payment_store.insert(payment)?; + + // Persist LSP peer to make sure we reconnect on restart. + self.peer_store.add_peer(peer_info)?; + + Ok(invoice) + } + + /// Sends payment probes over all paths of a route that would be used to pay the given invoice. + /// + /// This may be used to send "pre-flight" probes, i.e., to train our scorer before conducting + /// the actual payment. Note this is only useful if there likely is sufficient time for the + /// probe to settle before sending out the actual payment, e.g., when waiting for user + /// confirmation in a wallet UI. + /// + /// Otherwise, there is a chance the probe could take up some liquidity needed to complete the + /// actual payment. Users should therefore be cautious and might avoid sending probes if + /// liquidity is scarce and/or they don't expect the probe to return before they send the + /// payment. To mitigate this issue, channels with available liquidity less than the required + /// amount times [`Config::probing_liquidity_limit_multiplier`] won't be used to send + /// pre-flight probes. + pub fn send_probes(&self, invoice: &Bolt11Invoice) -> Result<(), Error> { + let rt_lock = self.runtime.read().unwrap(); + if rt_lock.is_none() { + return Err(Error::NotRunning); + } + + let (_payment_hash, _recipient_onion, route_params) = payment::payment_parameters_from_invoice(&invoice).map_err(|_| { + log_error!(self.logger, "Failed to send probes due to the given invoice being \"zero-amount\". Please use send_probes_using_amount instead."); + Error::InvalidInvoice + })?; + + let liquidity_limit_multiplier = Some(self.config.probing_liquidity_limit_multiplier); + + self.channel_manager + .send_preflight_probes(route_params, liquidity_limit_multiplier) + .map_err(|e| { + log_error!(self.logger, "Failed to send payment probes: {:?}", e); + Error::ProbeSendingFailed + })?; + + Ok(()) + } + + /// Sends payment probes over all paths of a route that would be used to pay the given + /// zero-value invoice using the given amount. + /// + /// This can be used to send pre-flight probes for a so-called "zero-amount" invoice, i.e., an + /// invoice that leaves the amount paid to be determined by the user. + /// + /// See [`Self::send_probes`] for more information. + pub fn send_probes_using_amount( + &self, invoice: &Bolt11Invoice, amount_msat: u64, + ) -> Result<(), Error> { + let rt_lock = self.runtime.read().unwrap(); + if rt_lock.is_none() { + return Err(Error::NotRunning); + } + + let (_payment_hash, _recipient_onion, route_params) = if let Some(invoice_amount_msat) = + invoice.amount_milli_satoshis() + { + if amount_msat < invoice_amount_msat { + log_error!( + self.logger, + "Failed to send probes as the given amount needs to be at least the invoice amount: required {}msat, gave {}msat.", invoice_amount_msat, amount_msat); + return Err(Error::InvalidAmount); + } + + payment::payment_parameters_from_invoice(&invoice).map_err(|_| { + log_error!(self.logger, "Failed to send probes due to the given invoice unexpectedly being \"zero-amount\"."); + Error::InvalidInvoice + })? + } else { + payment::payment_parameters_from_zero_amount_invoice(&invoice, amount_msat).map_err(|_| { + log_error!(self.logger, "Failed to send probes due to the given invoice unexpectedly being not \"zero-amount\"."); + Error::InvalidInvoice + })? + }; + + let liquidity_limit_multiplier = Some(self.config.probing_liquidity_limit_multiplier); + + self.channel_manager + .send_preflight_probes(route_params, liquidity_limit_multiplier) + .map_err(|e| { + log_error!(self.logger, "Failed to send payment probes: {:?}", e); + Error::ProbeSendingFailed + })?; + + Ok(()) + } +} diff --git a/src/payment/mod.rs b/src/payment/mod.rs new file mode 100644 index 000000000..d2864a70a --- /dev/null +++ b/src/payment/mod.rs @@ -0,0 +1,5 @@ +//! Handlers for different types of payments. + +mod bolt11; + +pub use bolt11::Bolt11PaymentsHandler; diff --git a/src/uniffi_types.rs b/src/uniffi_types.rs index 0cef5d682..959c6e9ed 100644 --- a/src/uniffi_types.rs +++ b/src/uniffi_types.rs @@ -3,6 +3,8 @@ pub use lightning::ln::ChannelId; pub use lightning::ln::PaymentSecret; pub use lightning::util::string::UntrustedString; +pub use lightning_invoice::Bolt11Invoice; + pub use bitcoin::{BlockHash, Network, OutPoint}; pub use bip39::Mnemonic; @@ -18,7 +20,7 @@ use bitcoin::hashes::Hash; use bitcoin::secp256k1::PublicKey; use bitcoin::{Address, Txid}; use lightning::ln::{PaymentHash, PaymentPreimage}; -use lightning_invoice::{Bolt11Invoice, SignedRawBolt11Invoice}; +use lightning_invoice::SignedRawBolt11Invoice; use std::convert::TryInto; use std::str::FromStr; diff --git a/tests/common.rs b/tests/common.rs index 9b1064b51..c138f7431 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -377,13 +377,13 @@ pub(crate) fn do_channel_full_cycle( let user_channel_id = expect_channel_ready_event!(node_b, node_a.node_id()); - println!("\nB receive_payment"); + println!("\nB receive"); let invoice_amount_1_msat = 2500_000; - let invoice = node_b.receive_payment(invoice_amount_1_msat, &"asdf", 9217).unwrap(); + let invoice = node_b.bolt11_payment().receive(invoice_amount_1_msat, &"asdf", 9217).unwrap(); - println!("\nA send_payment"); - let payment_hash = node_a.send_payment(&invoice).unwrap(); - assert_eq!(node_a.send_payment(&invoice), Err(NodeError::DuplicatePayment)); + println!("\nA send"); + let payment_hash = node_a.bolt11_payment().send(&invoice).unwrap(); + assert_eq!(node_a.bolt11_payment().send(&invoice), Err(NodeError::DuplicatePayment)); assert_eq!(node_a.list_payments().first().unwrap().hash, payment_hash); @@ -413,7 +413,7 @@ pub(crate) fn do_channel_full_cycle( assert_eq!(node_b.payment(&payment_hash).unwrap().amount_msat, Some(invoice_amount_1_msat)); // Assert we fail duplicate outbound payments and check the status hasn't changed. - assert_eq!(Err(NodeError::DuplicatePayment), node_a.send_payment(&invoice)); + assert_eq!(Err(NodeError::DuplicatePayment), node_a.bolt11_payment().send(&invoice)); assert_eq!(node_a.payment(&payment_hash).unwrap().status, PaymentStatus::Succeeded); assert_eq!(node_a.payment(&payment_hash).unwrap().direction, PaymentDirection::Outbound); assert_eq!(node_a.payment(&payment_hash).unwrap().amount_msat, Some(invoice_amount_1_msat)); @@ -423,20 +423,21 @@ pub(crate) fn do_channel_full_cycle( // Test under-/overpayment let invoice_amount_2_msat = 2500_000; - let invoice = node_b.receive_payment(invoice_amount_2_msat, &"asdf", 9217).unwrap(); + let invoice = node_b.bolt11_payment().receive(invoice_amount_2_msat, &"asdf", 9217).unwrap(); let underpaid_amount = invoice_amount_2_msat - 1; assert_eq!( Err(NodeError::InvalidAmount), - node_a.send_payment_using_amount(&invoice, underpaid_amount) + node_a.bolt11_payment().send_using_amount(&invoice, underpaid_amount) ); - println!("\nB overpaid receive_payment"); - let invoice = node_b.receive_payment(invoice_amount_2_msat, &"asdf", 9217).unwrap(); + println!("\nB overpaid receive"); + let invoice = node_b.bolt11_payment().receive(invoice_amount_2_msat, &"asdf", 9217).unwrap(); let overpaid_amount_msat = invoice_amount_2_msat + 100; - println!("\nA overpaid send_payment"); - let payment_hash = node_a.send_payment_using_amount(&invoice, overpaid_amount_msat).unwrap(); + println!("\nA overpaid send"); + let payment_hash = + node_a.bolt11_payment().send_using_amount(&invoice, overpaid_amount_msat).unwrap(); expect_event!(node_a, PaymentSuccessful); let received_amount = match node_b.wait_next_event() { ref e @ Event::PaymentReceived { amount_msat, .. } => { @@ -458,12 +459,18 @@ pub(crate) fn do_channel_full_cycle( // Test "zero-amount" invoice payment println!("\nB receive_variable_amount_payment"); - let variable_amount_invoice = node_b.receive_variable_amount_payment(&"asdf", 9217).unwrap(); + let variable_amount_invoice = + node_b.bolt11_payment().receive_variable_amount(&"asdf", 9217).unwrap(); let determined_amount_msat = 2345_678; - assert_eq!(Err(NodeError::InvalidInvoice), node_a.send_payment(&variable_amount_invoice)); - println!("\nA send_payment_using_amount"); - let payment_hash = - node_a.send_payment_using_amount(&variable_amount_invoice, determined_amount_msat).unwrap(); + assert_eq!( + Err(NodeError::InvalidInvoice), + node_a.bolt11_payment().send(&variable_amount_invoice) + ); + println!("\nA send_using_amount"); + let payment_hash = node_a + .bolt11_payment() + .send_using_amount(&variable_amount_invoice, determined_amount_msat) + .unwrap(); expect_event!(node_a, PaymentSuccessful); let received_amount = match node_b.wait_next_event() { @@ -487,7 +494,7 @@ pub(crate) fn do_channel_full_cycle( // Test spontaneous/keysend payments println!("\nA send_spontaneous_payment"); let keysend_amount_msat = 2500_000; - let keysend_payment_hash = + let keysend_hash = node_a.send_spontaneous_payment(keysend_amount_msat, node_b.node_id()).unwrap(); expect_event!(node_a, PaymentSuccessful); let received_keysend_amount = match node_b.wait_next_event() { @@ -501,21 +508,12 @@ pub(crate) fn do_channel_full_cycle( }, }; assert_eq!(received_keysend_amount, keysend_amount_msat); - assert_eq!(node_a.payment(&keysend_payment_hash).unwrap().status, PaymentStatus::Succeeded); - assert_eq!( - node_a.payment(&keysend_payment_hash).unwrap().direction, - PaymentDirection::Outbound - ); - assert_eq!( - node_a.payment(&keysend_payment_hash).unwrap().amount_msat, - Some(keysend_amount_msat) - ); - assert_eq!(node_b.payment(&keysend_payment_hash).unwrap().status, PaymentStatus::Succeeded); - assert_eq!(node_b.payment(&keysend_payment_hash).unwrap().direction, PaymentDirection::Inbound); - assert_eq!( - node_b.payment(&keysend_payment_hash).unwrap().amount_msat, - Some(keysend_amount_msat) - ); + assert_eq!(node_a.payment(&keysend_hash).unwrap().status, PaymentStatus::Succeeded); + assert_eq!(node_a.payment(&keysend_hash).unwrap().direction, PaymentDirection::Outbound); + assert_eq!(node_a.payment(&keysend_hash).unwrap().amount_msat, Some(keysend_amount_msat)); + assert_eq!(node_b.payment(&keysend_hash).unwrap().status, PaymentStatus::Succeeded); + assert_eq!(node_b.payment(&keysend_hash).unwrap().direction, PaymentDirection::Inbound); + assert_eq!(node_b.payment(&keysend_hash).unwrap().amount_msat, Some(keysend_amount_msat)); println!("\nB close_channel"); node_b.close_channel(&user_channel_id, node_a.node_id()).unwrap(); diff --git a/tests/integration_tests_cln.rs b/tests/integration_tests_cln.rs index a5c11ad1b..0405b136c 100644 --- a/tests/integration_tests_cln.rs +++ b/tests/integration_tests_cln.rs @@ -97,7 +97,7 @@ fn test_cln() { cln_client.invoice(Some(2_500_000), &rand_label, &rand_label, None, None, None).unwrap(); let parsed_invoice = Bolt11Invoice::from_str(&cln_invoice.bolt11).unwrap(); - node.send_payment(&parsed_invoice).unwrap(); + node.bolt11_payment().send(&parsed_invoice).unwrap(); common::expect_event!(node, PaymentSuccessful); let cln_listed_invoices = cln_client.listinvoices(Some(&rand_label), None, None, None).unwrap().invoices; @@ -106,7 +106,7 @@ fn test_cln() { // Send a payment to LDK let rand_label: String = (0..7).map(|_| rng.sample(Alphanumeric) as char).collect(); - let ldk_invoice = node.receive_payment(2_500_000, &rand_label, 3600).unwrap(); + let ldk_invoice = node.bolt11_payment().receive(2_500_000, &rand_label, 3600).unwrap(); cln_client.pay(&ldk_invoice.to_string(), Default::default()).unwrap(); common::expect_event!(node, PaymentReceived); diff --git a/tests/integration_tests_rust.rs b/tests/integration_tests_rust.rs index 8a7fb7e18..ed854e50a 100644 --- a/tests/integration_tests_rust.rs +++ b/tests/integration_tests_rust.rs @@ -132,8 +132,8 @@ fn multi_hop_sending() { // Sleep a bit for gossip to propagate. std::thread::sleep(std::time::Duration::from_secs(1)); - let invoice = nodes[4].receive_payment(2_500_000, &"asdf", 9217).unwrap(); - nodes[0].send_payment(&invoice).unwrap(); + let invoice = nodes[4].bolt11_payment().receive(2_500_000, &"asdf", 9217).unwrap(); + nodes[0].bolt11_payment().send(&invoice).unwrap(); expect_event!(nodes[4], PaymentReceived); expect_event!(nodes[0], PaymentSuccessful);