diff --git a/Cargo.toml b/Cargo.toml index 96a9eea53..5c0b2a94d 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -92,6 +92,8 @@ log = { version = "0.4.22", default-features = false, features = ["std"]} vss-client = "0.3" prost = { version = "0.11.6", default-features = false} +bitcoin-payment-instructions = { version = "0.5" } +lightning-dns-resolver = "0.2.0" [target.'cfg(windows)'.dependencies] winapi = { version = "0.3", features = ["winbase"] } diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index 076d7fc9b..7fcf7a40e 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -13,6 +13,7 @@ dictionary Config { u64 probing_liquidity_limit_multiplier; AnchorChannelsConfig? anchor_channels_config; SendingParameters? sending_parameters; + boolean is_hrn_resolver; }; dictionary AnchorChannelsConfig { @@ -127,7 +128,7 @@ interface Node { Bolt12Payment bolt12_payment(); SpontaneousPayment spontaneous_payment(); OnchainPayment onchain_payment(); - UnifiedQrPayment unified_qr_payment(); + UnifiedPayment unified_payment(); LSPS1Liquidity lsps1_liquidity(); [Throws=NodeError] void connect(PublicKey node_id, SocketAddress address, boolean persist); @@ -243,11 +244,11 @@ interface FeeRate { u64 to_sat_per_vb_ceil(); }; -interface UnifiedQrPayment { +interface UnifiedPayment { [Throws=NodeError] string receive(u64 amount_sats, [ByRef]string message, u32 expiry_sec); - [Throws=NodeError] - QrPaymentResult send([ByRef]string uri_str); + [Throws=NodeError, Async] + UnifiedPaymentResult send([ByRef]string uri_str, u64? amount_msat); }; interface LSPS1Liquidity { @@ -311,6 +312,7 @@ enum NodeError { "InsufficientFunds", "LiquiditySourceUnavailable", "LiquidityFeeTooHigh", + "HrnResolverNotConfigured", }; dictionary NodeStatus { @@ -347,6 +349,7 @@ enum BuildError { "WalletSetupFailed", "LoggerSetupFailed", "NetworkMismatch", + "DNSResolverSetupFailed", }; [Trait] @@ -418,7 +421,7 @@ interface PaymentKind { }; [Enum] -interface QrPaymentResult { +interface UnifiedPaymentResult { Onchain(Txid txid); Bolt11(PaymentId payment_id); Bolt12(PaymentId payment_id); diff --git a/src/builder.rs b/src/builder.rs index e160d1f6e..848ecefdc 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -31,8 +31,8 @@ use crate::peer_store::PeerStore; use crate::runtime::Runtime; use crate::tx_broadcaster::TransactionBroadcaster; use crate::types::{ - ChainMonitor, ChannelManager, DynStore, GossipSync, Graph, KeysManager, MessageRouter, - OnionMessenger, PaymentStore, PeerManager, + ChainMonitor, ChannelManager, DomainResolver, DynStore, GossipSync, Graph, HRNResolver, + KeysManager, MessageRouter, OnionMessenger, PaymentStore, PeerManager, }; use crate::wallet::persist::KVStoreWalletPersister; use crate::wallet::Wallet; @@ -43,6 +43,7 @@ use lightning::io::Cursor; use lightning::ln::channelmanager::{self, ChainParameters, ChannelManagerReadArgs}; use lightning::ln::msgs::{RoutingMessageHandler, SocketAddress}; use lightning::ln::peer_handler::{IgnoringMessageHandler, MessageHandler}; +use lightning::onion_message::dns_resolution::DNSResolverMessageHandler; use lightning::routing::gossip::NodeAlias; use lightning::routing::router::DefaultRouter; use lightning::routing::scoring::{ @@ -59,6 +60,8 @@ use lightning::util::sweep::OutputSweeper; use lightning_persister::fs_store::FilesystemStore; +use lightning_dns_resolver::OMDomainResolver; + use bdk_wallet::template::Bip84; use bdk_wallet::KeychainKind; use bdk_wallet::Wallet as BdkWallet; @@ -80,6 +83,8 @@ use std::sync::{Arc, Mutex, Once, RwLock}; use std::time::SystemTime; use vss_client::headers::{FixedHeaders, LnurlAuthToJwtProvider, VssHeaderProvider}; +use bitcoin_payment_instructions::onion_message_resolver::LDKOnionMessageDNSSECHrnResolver; + const VSS_HARDENED_CHILD_INDEX: u32 = 877; const VSS_LNURL_AUTH_HARDENED_CHILD_INDEX: u32 = 138; const LSPS_HARDENED_CHILD_INDEX: u32 = 577; @@ -191,6 +196,8 @@ pub enum BuildError { LoggerSetupFailed, /// The given network does not match the node's previously configured network. NetworkMismatch, + /// An attempt to setup a DNS Resolver failed. + DNSResolverSetupFailed, } impl fmt::Display for BuildError { @@ -219,12 +226,20 @@ impl fmt::Display for BuildError { Self::NetworkMismatch => { write!(f, "Given network does not match the node's previously configured network.") }, + Self::DNSResolverSetupFailed => { + write!(f, "An attempt to setup a DNS resolver has failed.") + }, } } } impl std::error::Error for BuildError {} +enum Resolver { + HRN(Arc), + DNS(Arc), +} + /// A builder for an [`Node`] instance, allowing to set some configuration and module choices from /// the getgo. /// @@ -1454,6 +1469,23 @@ fn build_with_store_internal( })?; } + let resolver = if config.is_hrn_resolver { + Resolver::DNS(Arc::new(OMDomainResolver::ignoring_incoming_proofs( + "8.8.8.8:53".parse().map_err(|_| BuildError::DNSResolverSetupFailed)?, + ))) + } else { + Resolver::HRN(Arc::new(LDKOnionMessageDNSSECHrnResolver::new(Arc::clone(&network_graph)))) + }; + + let om_resolver = match resolver { + Resolver::DNS(ref dns_resolver) => { + Arc::clone(dns_resolver) as Arc + }, + Resolver::HRN(ref hrn_resolver) => { + Arc::clone(hrn_resolver) as Arc + }, + }; + // Initialize the PeerManager let onion_messenger: Arc = Arc::new(OnionMessenger::new( Arc::clone(&keys_manager), @@ -1463,7 +1495,7 @@ fn build_with_store_internal( message_router, Arc::clone(&channel_manager), IgnoringMessageHandler {}, - IgnoringMessageHandler {}, + Arc::clone(&om_resolver), IgnoringMessageHandler {}, )); let ephemeral_bytes: [u8; 32] = keys_manager.get_secure_random_bytes(); @@ -1589,6 +1621,18 @@ fn build_with_store_internal( Arc::clone(&keys_manager), )); + let peer_manager_clone = Arc::clone(&peer_manager); + + let hrn_resolver = match resolver { + Resolver::DNS(_) => None, + Resolver::HRN(ref hrn_resolver) => { + hrn_resolver.register_post_queue_action(Box::new(move || { + peer_manager_clone.process_events(); + })); + Some(hrn_resolver) + }, + }; + liquidity_source.as_ref().map(|l| l.set_peer_manager(Arc::clone(&peer_manager))); gossip_source.set_gossip_verifier( @@ -1696,6 +1740,7 @@ fn build_with_store_internal( is_running, is_listening, node_metrics, + hrn_resolver: hrn_resolver.cloned(), }) } diff --git a/src/config.rs b/src/config.rs index 02df8bbc7..4378c98fb 100644 --- a/src/config.rs +++ b/src/config.rs @@ -115,6 +115,7 @@ pub const WALLET_KEYS_SEED_LEN: usize = 64; /// | `log_level` | Debug | /// | `anchor_channels_config` | Some(..) | /// | `sending_parameters` | None | +/// | `is_hrn_resolver` | false | /// /// See [`AnchorChannelsConfig`] and [`SendingParameters`] for more information regarding their /// respective default values. @@ -179,6 +180,8 @@ pub struct Config { /// **Note:** If unset, default parameters will be used, and you will be able to override the /// parameters on a per-payment basis in the corresponding method calls. pub sending_parameters: Option, + /// This allows us to use our node as a DNS resolver for 3rd party HRN resolutions. + pub is_hrn_resolver: bool, } impl Default for Config { @@ -193,6 +196,7 @@ impl Default for Config { anchor_channels_config: Some(AnchorChannelsConfig::default()), sending_parameters: None, node_alias: None, + is_hrn_resolver: false, } } } diff --git a/src/error.rs b/src/error.rs index 2cb71186d..d0d1fa52c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -120,6 +120,8 @@ pub enum Error { LiquiditySourceUnavailable, /// The given operation failed due to the LSP's required opening fee being too high. LiquidityFeeTooHigh, + /// A HRN resolver was not configured + HrnResolverNotConfigured, } impl fmt::Display for Error { @@ -193,6 +195,9 @@ impl fmt::Display for Error { Self::LiquidityFeeTooHigh => { write!(f, "The given operation failed due to the LSP's required opening fee being too high.") }, + Self::HrnResolverNotConfigured => { + write!(f, "A HRN resolver was not configured.") + }, } } } diff --git a/src/ffi/types.rs b/src/ffi/types.rs index 984e4da8f..91858d48f 100644 --- a/src/ffi/types.rs +++ b/src/ffi/types.rs @@ -20,7 +20,7 @@ pub use crate::logger::{LogLevel, LogRecord, LogWriter}; pub use crate::payment::store::{ ConfirmationStatus, LSPFeeLimits, PaymentDirection, PaymentKind, PaymentStatus, }; -pub use crate::payment::{MaxTotalRoutingFeeLimit, QrPaymentResult, SendingParameters}; +pub use crate::payment::{MaxTotalRoutingFeeLimit, SendingParameters, UnifiedPaymentResult}; pub use lightning::chain::channelmonitor::BalanceSource; pub use lightning::events::{ClosureReason, PaymentFailureReason}; diff --git a/src/lib.rs b/src/lib.rs index da86fce73..fa69277cf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -139,13 +139,14 @@ use io::utils::write_node_metrics; use liquidity::{LSPS1Liquidity, LiquiditySource}; use payment::{ Bolt11Payment, Bolt12Payment, OnchainPayment, PaymentDetails, SpontaneousPayment, - UnifiedQrPayment, + UnifiedPayment, }; use peer_store::{PeerInfo, PeerStore}; use runtime::Runtime; use types::{ Broadcaster, BumpTransactionEventHandler, ChainMonitor, ChannelManager, DynStore, Graph, - KeysManager, OnionMessenger, PaymentStore, PeerManager, Router, Scorer, Sweeper, Wallet, + HRNResolver, KeysManager, OnionMessenger, PaymentStore, PeerManager, Router, Scorer, Sweeper, + Wallet, }; pub use types::{ChannelDetails, CustomTlvRecord, PeerDetails, UserChannelId}; @@ -205,6 +206,7 @@ pub struct Node { is_running: Arc>, is_listening: Arc, node_metrics: Arc>, + hrn_resolver: Option>, } impl Node { @@ -466,6 +468,8 @@ impl Node { if let Some(node_alias) = node_alias.as_ref() { bcast_pm.broadcast_node_announcement([0; 3], node_alias.0, addresses); + println!("broadcasted announncement"); + let unix_time_secs_opt = SystemTime::now().duration_since(UNIX_EPOCH).ok().map(|d| d.as_secs()); { @@ -885,34 +889,42 @@ impl Node { /// Returns a payment handler allowing to create [BIP 21] URIs with an on-chain, [BOLT 11], /// and [BOLT 12] payment options. /// + /// This handler allows you to send payments to these URIs as well as [BIP 353] HRNs. + /// /// [BOLT 11]: https://github.com/lightning/bolts/blob/master/11-payment-encoding.md /// [BOLT 12]: https://github.com/lightning/bolts/blob/master/12-offer-encoding.md /// [BIP 21]: https://github.com/bitcoin/bips/blob/master/bip-0021.mediawiki + /// [BIP 353]: https://github.com/bitcoin/bips/blob/master/bip-0353.mediawiki #[cfg(not(feature = "uniffi"))] - pub fn unified_qr_payment(&self) -> UnifiedQrPayment { - UnifiedQrPayment::new( + pub fn unified_payment(&self) -> UnifiedPayment { + UnifiedPayment::new( self.onchain_payment().into(), self.bolt11_payment().into(), self.bolt12_payment().into(), Arc::clone(&self.config), Arc::clone(&self.logger), + Arc::new(self.hrn_resolver.clone()), ) } /// Returns a payment handler allowing to create [BIP 21] URIs with an on-chain, [BOLT 11], /// and [BOLT 12] payment options. /// + /// This handler allows you to send payments to these URIs as well as [BIP 353] HRNs. + /// /// [BOLT 11]: https://github.com/lightning/bolts/blob/master/11-payment-encoding.md /// [BOLT 12]: https://github.com/lightning/bolts/blob/master/12-offer-encoding.md /// [BIP 21]: https://github.com/bitcoin/bips/blob/master/bip-0021.mediawiki + /// [BIP 353]: https://github.com/bitcoin/bips/blob/master/bip-0353.mediawiki #[cfg(feature = "uniffi")] - pub fn unified_qr_payment(&self) -> Arc { - Arc::new(UnifiedQrPayment::new( + pub fn unified_payment(&self) -> Arc { + Arc::new(UnifiedPayment::new( self.onchain_payment(), self.bolt11_payment(), self.bolt12_payment(), Arc::clone(&self.config), Arc::clone(&self.logger), + Arc::new(self.hrn_resolver.clone()), )) } diff --git a/src/payment/mod.rs b/src/payment/mod.rs index b031e37fd..0019120ce 100644 --- a/src/payment/mod.rs +++ b/src/payment/mod.rs @@ -12,7 +12,7 @@ mod bolt12; mod onchain; mod spontaneous; pub(crate) mod store; -mod unified_qr; +mod unified; pub use bolt11::Bolt11Payment; pub use bolt12::Bolt12Payment; @@ -21,7 +21,7 @@ pub use spontaneous::SpontaneousPayment; pub use store::{ ConfirmationStatus, LSPFeeLimits, PaymentDetails, PaymentDirection, PaymentKind, PaymentStatus, }; -pub use unified_qr::{QrPaymentResult, UnifiedQrPayment}; +pub use unified::{UnifiedPayment, UnifiedPaymentResult}; /// Represents information used to send a payment. #[derive(Clone, Debug, PartialEq)] diff --git a/src/payment/unified_qr.rs b/src/payment/unified.rs similarity index 78% rename from src/payment/unified_qr.rs rename to src/payment/unified.rs index af5ee1c7b..24e271140 100644 --- a/src/payment/unified_qr.rs +++ b/src/payment/unified.rs @@ -5,16 +5,20 @@ // http://opensource.org/licenses/MIT>, at your option. You may not use this file except in // accordance with one or both of these licenses. -//! Holds a payment handler allowing to create [BIP 21] URIs with an on-chain, [BOLT 11], and [BOLT 12] payment +//! Holds a payment handler allowing to create [BIP 21] URIs with on-chain, [BOLT 11], and [BOLT 12] payment //! options. //! +//! Also allows to send payments using these URIs as well as [BIP 353] HRNs. +//! //! [BIP 21]: https://github.com/bitcoin/bips/blob/master/bip-0021.mediawiki +//! [BIP 353]: https://github.com/bitcoin/bips/blob/master/bip-0353.mediawiki //! [BOLT 11]: https://github.com/lightning/bolts/blob/master/11-payment-encoding.md //! [BOLT 12]: https://github.com/lightning/bolts/blob/master/12-offer-encoding.md use crate::error::Error; use crate::ffi::maybe_wrap; use crate::logger::{log_error, LdkLogger, Logger}; use crate::payment::{Bolt11Payment, Bolt12Payment, OnchainPayment}; +use crate::types::HRNResolver; use crate::Config; use lightning::ln::channelmanager::PaymentId; @@ -23,8 +27,11 @@ use lightning_invoice::{Bolt11Invoice, Bolt11InvoiceDescription, Description}; use bip21::de::ParamKind; use bip21::{DeserializationError, DeserializeParams, Param, SerializeParams}; -use bitcoin::address::{NetworkChecked, NetworkUnchecked}; +use bitcoin::address::NetworkChecked; use bitcoin::{Amount, Txid}; +use bitcoin_payment_instructions::{ + amount::Amount as BPIAmount, PaymentInstructions, PaymentMethod, +}; use std::sync::Arc; use std::vec::IntoIter; @@ -40,26 +47,31 @@ struct Extras { /// A payment handler allowing to create [BIP 21] URIs with an on-chain, [BOLT 11], and [BOLT 12] payment /// option. /// -/// Should be retrieved by calling [`Node::unified_qr_payment`] +/// Should be retrieved by calling [`Node::unified_payment`] +/// +/// This handler allows you to send payments to these URIs as well as [BIP 353] HRNs. /// /// [BIP 21]: https://github.com/bitcoin/bips/blob/master/bip-0021.mediawiki +/// [BIP 353]: https://github.com/bitcoin/bips/blob/master/bip-0353.mediawiki /// [BOLT 11]: https://github.com/lightning/bolts/blob/master/11-payment-encoding.md /// [BOLT 12]: https://github.com/lightning/bolts/blob/master/12-offer-encoding.md -/// [`Node::unified_qr_payment`]: crate::Node::unified_qr_payment -pub struct UnifiedQrPayment { +/// [`Node::unified_payment`]: crate::Node::unified_payment +pub struct UnifiedPayment { onchain_payment: Arc, bolt11_invoice: Arc, bolt12_payment: Arc, config: Arc, logger: Arc, + hrn_resolver: Arc>>, } -impl UnifiedQrPayment { +impl UnifiedPayment { pub(crate) fn new( onchain_payment: Arc, bolt11_invoice: Arc, bolt12_payment: Arc, config: Arc, logger: Arc, + hrn_resolver: Arc>>, ) -> Self { - Self { onchain_payment, bolt11_invoice, bolt12_payment, config, logger } + Self { onchain_payment, bolt11_invoice, bolt12_payment, config, logger, hrn_resolver } } /// Generates a URI with an on-chain address, [BOLT 11] invoice and [BOLT 12] offer. @@ -129,54 +141,118 @@ impl UnifiedQrPayment { Ok(format_uri(uri)) } - /// Sends a payment given a [BIP 21] URI. + /// Sends a payment given a [BIP 21] URI or [BIP 353] HRN. /// /// This method parses the provided URI string and attempts to send the payment. If the URI /// has an offer and or invoice, it will try to pay the offer first followed by the invoice. /// If they both fail, the on-chain payment will be paid. /// - /// Returns a `QrPaymentResult` indicating the outcome of the payment. If an error + /// Returns a `UnifiedPaymentResult` indicating the outcome of the payment. If an error /// occurs, an `Error` is returned detailing the issue encountered. /// /// [BIP 21]: https://github.com/bitcoin/bips/blob/master/bip-0021.mediawiki - pub fn send(&self, uri_str: &str) -> Result { - let uri: bip21::Uri = - uri_str.parse().map_err(|_| Error::InvalidUri)?; - - let uri_network_checked = - uri.clone().require_network(self.config.network).map_err(|_| Error::InvalidNetwork)?; - - if let Some(offer) = uri_network_checked.extras.bolt12_offer { - let offer = maybe_wrap(offer); - match self.bolt12_payment.send(&offer, None, None) { - Ok(payment_id) => return Ok(QrPaymentResult::Bolt12 { payment_id }), - Err(e) => log_error!(self.logger, "Failed to send BOLT12 offer: {:?}. This is part of a unified QR code payment. Falling back to the BOLT11 invoice.", e), + /// [BIP 353]: https://github.com/bitcoin/bips/blob/master/bip-0353.mediawiki + pub async fn send( + &self, uri_str: &str, amount_msat: Option, + ) -> Result { + let resolver = self.hrn_resolver.as_ref().clone().ok_or_else(|| { + log_error!(self.logger, "No HRN resolver configured. Cannot resolve HRNs."); + Error::HrnResolverNotConfigured + })?; + + println!("Parsing instructions..."); + + let instructions = + PaymentInstructions::parse(uri_str, self.config.network, resolver.as_ref(), false) + .await + .map_err(|e| { + log_error!(self.logger, "Failed to parse payment instructions: {:?}", e); + println!("Failed to parse payment instructions: {:?}", e); + Error::UriParameterParsingFailed + })?; + + println!("Sending..."); + + let resolved = match instructions { + PaymentInstructions::ConfigurableAmount(instr) => { + let amount = amount_msat.ok_or_else(|| { + log_error!(self.logger, "No amount specified. Aborting the payment."); + Error::InvalidAmount + })?; + + let amt = BPIAmount::from_milli_sats(amount).map_err(|e| { + log_error!(self.logger, "Error while converting amount : {:?}", e); + Error::InvalidAmount + })?; + + instr.set_amount(amt, resolver.as_ref()).await.map_err(|e| { + log_error!(self.logger, "Failed to set amount: {:?}", e); + Error::InvalidAmount + })? + }, + PaymentInstructions::FixedAmount(instr) => { + if let Some(user_amount) = amount_msat { + if instr.max_amount().map_or(false, |amt| user_amount < amt.milli_sats()) { + log_error!(self.logger, "Amount specified is less than the amount in the parsed URI. Aborting the payment."); + return Err(Error::InvalidAmount); + } + } + instr + }, + }; + + if let Some(PaymentMethod::LightningBolt12(offer)) = + resolved.methods().iter().find(|m| matches!(m, PaymentMethod::LightningBolt12(_))) + { + let offer = maybe_wrap(offer.clone()); + let payment_result = if let Some(amount_msat) = amount_msat { + self.bolt12_payment.send_using_amount(&offer, amount_msat, None, None) + } else { + self.bolt12_payment.send(&offer, None, None) } - } + .map_err(|e| { + log_error!(self.logger, "Failed to send BOLT12 offer: {:?}. This is part of a unified payment. Falling back to the BOLT11 invoice.", e); + e + }); - if let Some(invoice) = uri_network_checked.extras.bolt11_invoice { - let invoice = maybe_wrap(invoice); - match self.bolt11_invoice.send(&invoice, None) { - Ok(payment_id) => return Ok(QrPaymentResult::Bolt11 { payment_id }), - Err(e) => log_error!(self.logger, "Failed to send BOLT11 invoice: {:?}. This is part of a unified QR code payment. Falling back to the on-chain transaction.", e), + if let Ok(payment_id) = payment_result { + return Ok(UnifiedPaymentResult::Bolt12 { payment_id }); } } - let amount = match uri_network_checked.amount { - Some(amount) => amount, - None => { - log_error!(self.logger, "No amount specified in the URI. Aborting the payment."); - return Err(Error::InvalidAmount); - }, - }; - - let txid = self.onchain_payment.send_to_address( - &uri_network_checked.address, - amount.to_sat(), - None, - )?; + if let Some(PaymentMethod::LightningBolt11(invoice)) = + resolved.methods().iter().find(|m| matches!(m, PaymentMethod::LightningBolt11(_))) + { + let invoice = maybe_wrap(invoice.clone()); + let payment_result = self.bolt11_invoice.send(&invoice, None) + .map_err(|e| { + log_error!(self.logger, "Failed to send BOLT11 invoice: {:?}. This is part of a unified payment. Falling back to the on-chain transaction.", e); + e + }); + + if let Ok(payment_id) = payment_result { + return Ok(UnifiedPaymentResult::Bolt11 { payment_id }); + } + } - Ok(QrPaymentResult::Onchain { txid }) + if let Some(PaymentMethod::OnChain(address)) = + resolved.methods().iter().find(|m| matches!(m, PaymentMethod::OnChain(_))) + { + let amount = resolved.onchain_payment_amount().ok_or_else(|| { + log_error!(self.logger, "No amount specified. Aborting the payment."); + Error::InvalidAmount + })?; + + let amt_sats = amount.sats().map_err(|_| { + log_error!(self.logger, "Amount in sats returned an error. Aborting the payment."); + Error::InvalidAmount + })?; + + let txid = self.onchain_payment.send_to_address(&address, amt_sats, None)?; + return Ok(UnifiedPaymentResult::Onchain { txid }); + } + log_error!(self.logger, "Payable methods not found in URI"); + Err(Error::PaymentSendingFailed) } } @@ -189,7 +265,7 @@ impl UnifiedQrPayment { /// [`PaymentId`]: lightning::ln::channelmanager::PaymentId /// [`Txid`]: bitcoin::hash_types::Txid #[derive(Debug)] -pub enum QrPaymentResult { +pub enum UnifiedPaymentResult { /// An on-chain payment. Onchain { /// The transaction ID (txid) of the on-chain payment. @@ -303,9 +379,8 @@ impl DeserializationError for Extras { #[cfg(test)] mod tests { - use super::*; - use crate::payment::unified_qr::Extras; - use bitcoin::{Address, Network}; + use super::{Amount, Bolt11Invoice, Extras, Offer}; + use bitcoin::{address::NetworkUnchecked, Address, Network}; use std::str::FromStr; #[test] diff --git a/src/types.rs b/src/types.rs index 3103ead3f..99c99a1e1 100644 --- a/src/types.rs +++ b/src/types.rs @@ -21,6 +21,7 @@ use lightning::ln::msgs::RoutingMessageHandler; use lightning::ln::msgs::SocketAddress; use lightning::ln::peer_handler::IgnoringMessageHandler; use lightning::ln::types::ChannelId; +use lightning::onion_message::dns_resolution::DNSResolverMessageHandler; use lightning::routing::gossip; use lightning::routing::router::DefaultRouter; use lightning::routing::scoring::{ProbabilisticScorer, ProbabilisticScoringFeeParameters}; @@ -33,9 +34,13 @@ use lightning_block_sync::gossip::{GossipVerifier, UtxoSource}; use lightning_net_tokio::SocketDescriptor; +use lightning_dns_resolver::OMDomainResolver; + use bitcoin::secp256k1::PublicKey; use bitcoin::OutPoint; +use bitcoin_payment_instructions::onion_message_resolver::LDKOnionMessageDNSSECHrnResolver; + use std::sync::{Arc, Mutex}; pub(crate) type DynStore = dyn KVStore + Sync + Send; @@ -117,10 +122,13 @@ pub(crate) type OnionMessenger = lightning::onion_message::messenger::OnionMesse Arc, Arc, IgnoringMessageHandler, - IgnoringMessageHandler, + Arc, IgnoringMessageHandler, >; +pub(crate) type HRNResolver = LDKOnionMessageDNSSECHrnResolver, Arc>; +pub(crate) type DomainResolver = OMDomainResolver; + pub(crate) type MessageRouter = lightning::onion_message::messenger::DefaultMessageRouter< Arc, Arc, diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 780e9bbf4..87e140eab 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -284,7 +284,7 @@ pub(crate) use setup_builder; pub(crate) fn setup_two_nodes( chain_source: &TestChainSource, allow_0conf: bool, anchor_channels: bool, - anchors_trusted_no_reserve: bool, + anchors_trusted_no_reserve: bool, second_node_is_hrn_resolver: bool, ) -> (TestNode, TestNode) { println!("== Node A =="); let config_a = random_config(anchor_channels); @@ -292,6 +292,9 @@ pub(crate) fn setup_two_nodes( println!("\n== Node B =="); let mut config_b = random_config(anchor_channels); + if second_node_is_hrn_resolver { + config_b.node_config.is_hrn_resolver = true; + } if allow_0conf { config_b.node_config.trusted_peers_0conf.push(node_a.node_id()); } diff --git a/tests/integration_tests_rust.rs b/tests/integration_tests_rust.rs index 0932116ef..52fa43d79 100644 --- a/tests/integration_tests_rust.rs +++ b/tests/integration_tests_rust.rs @@ -22,7 +22,7 @@ use ldk_node::config::EsploraSyncConfig; use ldk_node::liquidity::LSPS2ServiceConfig; use ldk_node::payment::{ ConfirmationStatus, PaymentDetails, PaymentDirection, PaymentKind, PaymentStatus, - QrPaymentResult, SendingParameters, + SendingParameters, UnifiedPaymentResult, }; use ldk_node::{Builder, Event, NodeError}; @@ -47,7 +47,7 @@ use std::sync::Arc; fn channel_full_cycle() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let chain_source = TestChainSource::Esplora(&electrsd); - let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false); + let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false, false); do_channel_full_cycle(node_a, node_b, &bitcoind.client, &electrsd.client, false, true, false); } @@ -55,7 +55,7 @@ fn channel_full_cycle() { fn channel_full_cycle_electrum() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let chain_source = TestChainSource::Electrum(&electrsd); - let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false); + let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false, false); do_channel_full_cycle(node_a, node_b, &bitcoind.client, &electrsd.client, false, true, false); } @@ -63,7 +63,7 @@ fn channel_full_cycle_electrum() { fn channel_full_cycle_bitcoind_rpc_sync() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let chain_source = TestChainSource::BitcoindRpcSync(&bitcoind); - let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false); + let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false, false); do_channel_full_cycle(node_a, node_b, &bitcoind.client, &electrsd.client, false, true, false); } @@ -71,7 +71,7 @@ fn channel_full_cycle_bitcoind_rpc_sync() { fn channel_full_cycle_bitcoind_rest_sync() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let chain_source = TestChainSource::BitcoindRestSync(&bitcoind); - let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false); + let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false, false); do_channel_full_cycle(node_a, node_b, &bitcoind.client, &electrsd.client, false, true, false); } @@ -79,7 +79,7 @@ fn channel_full_cycle_bitcoind_rest_sync() { fn channel_full_cycle_force_close() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let chain_source = TestChainSource::Esplora(&electrsd); - let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false); + let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false, false); do_channel_full_cycle(node_a, node_b, &bitcoind.client, &electrsd.client, false, true, true); } @@ -87,7 +87,7 @@ fn channel_full_cycle_force_close() { fn channel_full_cycle_force_close_trusted_no_reserve() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let chain_source = TestChainSource::Esplora(&electrsd); - let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, true); + let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, true, false); do_channel_full_cycle(node_a, node_b, &bitcoind.client, &electrsd.client, false, true, true); } @@ -95,7 +95,7 @@ fn channel_full_cycle_force_close_trusted_no_reserve() { fn channel_full_cycle_0conf() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let chain_source = TestChainSource::Esplora(&electrsd); - let (node_a, node_b) = setup_two_nodes(&chain_source, true, true, false); + let (node_a, node_b) = setup_two_nodes(&chain_source, true, true, false, false); do_channel_full_cycle(node_a, node_b, &bitcoind.client, &electrsd.client, true, true, false) } @@ -103,7 +103,7 @@ fn channel_full_cycle_0conf() { fn channel_full_cycle_legacy_staticremotekey() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let chain_source = TestChainSource::Esplora(&electrsd); - let (node_a, node_b) = setup_two_nodes(&chain_source, false, false, false); + let (node_a, node_b) = setup_two_nodes(&chain_source, false, false, false, false); do_channel_full_cycle(node_a, node_b, &bitcoind.client, &electrsd.client, false, false, false); } @@ -111,7 +111,7 @@ fn channel_full_cycle_legacy_staticremotekey() { fn channel_open_fails_when_funds_insufficient() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let chain_source = TestChainSource::Esplora(&electrsd); - let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false); + let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false, false); let addr_a = node_a.onchain_payment().new_address().unwrap(); let addr_b = node_b.onchain_payment().new_address().unwrap(); @@ -312,7 +312,7 @@ fn start_stop_reinit() { fn onchain_send_receive() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let chain_source = TestChainSource::Esplora(&electrsd); - let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false); + let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false, false); let addr_a = node_a.onchain_payment().new_address().unwrap(); let addr_b = node_b.onchain_payment().new_address().unwrap(); @@ -512,7 +512,7 @@ fn onchain_send_receive() { fn onchain_send_all_retains_reserve() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let chain_source = TestChainSource::Esplora(&electrsd); - let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false); + let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false, false); // Setup nodes let addr_a = node_a.onchain_payment().new_address().unwrap(); @@ -829,7 +829,7 @@ fn connection_restart_behavior() { fn do_connection_restart_behavior(persist: bool) { let (_bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let chain_source = TestChainSource::Esplora(&electrsd); - let (node_a, node_b) = setup_two_nodes(&chain_source, false, false, false); + let (node_a, node_b) = setup_two_nodes(&chain_source, false, false, false, false); let node_id_a = node_a.node_id(); let node_id_b = node_b.node_id(); @@ -881,7 +881,7 @@ fn do_connection_restart_behavior(persist: bool) { fn concurrent_connections_succeed() { let (_bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let chain_source = TestChainSource::Esplora(&electrsd); - let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false); + let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false, false); let node_a = Arc::new(node_a); let node_b = Arc::new(node_b); @@ -912,7 +912,7 @@ fn concurrent_connections_succeed() { fn simple_bolt12_send_receive() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let chain_source = TestChainSource::Esplora(&electrsd); - let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false); + let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false, false); let address_a = node_a.onchain_payment().new_address().unwrap(); let premine_amount_sat = 5_000_000; @@ -1225,7 +1225,7 @@ fn generate_bip21_uri() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let chain_source = TestChainSource::Esplora(&electrsd); - let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false); + let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false, false); let address_a = node_a.onchain_payment().new_address().unwrap(); let premined_sats = 5_000_000; @@ -1235,15 +1235,15 @@ fn generate_bip21_uri() { // Test 1: Verify URI generation (on-chain + BOLT11) works // even before any channels are opened. This checks the graceful fallback behavior. - let initial_uqr_payment = node_b - .unified_qr_payment() + let initial_uni_payment = node_b + .unified_payment() .receive(expected_amount_sats, "asdf", expiry_sec) .expect("Failed to generate URI"); - println!("Initial URI (no channels): {}", initial_uqr_payment); + println!("Initial URI (no channels): {}", initial_uni_payment); - assert!(initial_uqr_payment.contains("bitcoin:")); - assert!(initial_uqr_payment.contains("lightning=")); - assert!(!initial_uqr_payment.contains("lno=")); // BOLT12 requires channels + assert!(initial_uni_payment.contains("bitcoin:")); + assert!(initial_uni_payment.contains("lightning=")); + assert!(!initial_uni_payment.contains("lno=")); // BOLT12 requires channels premine_and_distribute_funds( &bitcoind.client, @@ -1263,23 +1263,23 @@ fn generate_bip21_uri() { expect_channel_ready_event!(node_b, node_a.node_id()); // Test 2: Verify URI generation (on-chain + BOLT11 + BOLT12) works after channels are established. - let uqr_payment = node_b - .unified_qr_payment() + let uni_payment = node_b + .unified_payment() .receive(expected_amount_sats, "asdf", expiry_sec) .expect("Failed to generate URI"); - println!("Generated URI: {}", uqr_payment); - assert!(uqr_payment.contains("bitcoin:")); - assert!(uqr_payment.contains("lightning=")); - assert!(uqr_payment.contains("lno=")); + println!("Generated URI: {}", uni_payment); + assert!(uni_payment.contains("bitcoin:")); + assert!(uni_payment.contains("lightning=")); + assert!(uni_payment.contains("lno=")); } -#[test] -fn unified_qr_send_receive() { +#[tokio::test(flavor = "multi_thread")] +async fn unified_send_receive_qr_uri() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let chain_source = TestChainSource::Esplora(&electrsd); - let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false); + let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false, false); let address_a = node_a.onchain_payment().new_address().unwrap(); let premined_sats = 5_000_000; @@ -1312,18 +1312,18 @@ fn unified_qr_send_receive() { let expected_amount_sats = 100_000; let expiry_sec = 4_000; - let uqr_payment = node_b.unified_qr_payment().receive(expected_amount_sats, "asdf", expiry_sec); - let uri_str = uqr_payment.clone().unwrap(); - let offer_payment_id: PaymentId = match node_a.unified_qr_payment().send(&uri_str) { - Ok(QrPaymentResult::Bolt12 { payment_id }) => { + let uni_payment = node_b.unified_payment().receive(expected_amount_sats, "asdf", expiry_sec); + let uri_str = uni_payment.clone().unwrap(); + let offer_payment_id: PaymentId = match node_a.unified_payment().send(&uri_str, None).await { + Ok(UnifiedPaymentResult::Bolt12 { payment_id }) => { println!("\nBolt12 payment sent successfully with PaymentID: {:?}", payment_id); payment_id }, - Ok(QrPaymentResult::Bolt11 { payment_id: _ }) => { + Ok(UnifiedPaymentResult::Bolt11 { payment_id: _ }) => { panic!("Expected Bolt12 payment but got Bolt11"); }, - Ok(QrPaymentResult::Onchain { txid: _ }) => { - panic!("Expected Bolt12 payment but get On-chain transaction"); + Ok(UnifiedPaymentResult::Onchain { txid: _ }) => { + panic!("Expected Bolt12 payment but got On-chain transaction"); }, Err(e) => { panic!("Expected Bolt12 payment but got error: {:?}", e); @@ -1335,15 +1335,15 @@ fn unified_qr_send_receive() { // Cut off the BOLT12 part to fallback to BOLT11. let uri_str_without_offer = uri_str.split("&lno=").next().unwrap(); let invoice_payment_id: PaymentId = - match node_a.unified_qr_payment().send(uri_str_without_offer) { - Ok(QrPaymentResult::Bolt12 { payment_id: _ }) => { + match node_a.unified_payment().send(uri_str_without_offer, None).await { + Ok(UnifiedPaymentResult::Bolt12 { payment_id: _ }) => { panic!("Expected Bolt11 payment but got Bolt12"); }, - Ok(QrPaymentResult::Bolt11 { payment_id }) => { + Ok(UnifiedPaymentResult::Bolt11 { payment_id }) => { println!("\nBolt11 payment sent successfully with PaymentID: {:?}", payment_id); payment_id }, - Ok(QrPaymentResult::Onchain { txid: _ }) => { + Ok(UnifiedPaymentResult::Onchain { txid: _ }) => { panic!("Expected Bolt11 payment but got on-chain transaction"); }, Err(e) => { @@ -1353,19 +1353,19 @@ fn unified_qr_send_receive() { expect_payment_successful_event!(node_a, Some(invoice_payment_id), None); let expect_onchain_amount_sats = 800_000; - let onchain_uqr_payment = - node_b.unified_qr_payment().receive(expect_onchain_amount_sats, "asdf", 4_000).unwrap(); + let onchain_uni_payment = + node_b.unified_payment().receive(expect_onchain_amount_sats, "asdf", 4_000).unwrap(); // Cut off any lightning part to fallback to on-chain only. - let uri_str_without_lightning = onchain_uqr_payment.split("&lightning=").next().unwrap(); - let txid = match node_a.unified_qr_payment().send(&uri_str_without_lightning) { - Ok(QrPaymentResult::Bolt12 { payment_id: _ }) => { + let uri_str_without_lightning = onchain_uni_payment.split("&lightning=").next().unwrap(); + let txid = match node_a.unified_payment().send(&uri_str_without_lightning, None).await { + Ok(UnifiedPaymentResult::Bolt12 { payment_id: _ }) => { panic!("Expected on-chain payment but got Bolt12") }, - Ok(QrPaymentResult::Bolt11 { payment_id: _ }) => { + Ok(UnifiedPaymentResult::Bolt11 { payment_id: _ }) => { panic!("Expected on-chain payment but got Bolt11"); }, - Ok(QrPaymentResult::Onchain { txid }) => { + Ok(UnifiedPaymentResult::Onchain { txid }) => { println!("\nOn-chain transaction successful with Txid: {}", txid); txid }, @@ -1384,6 +1384,66 @@ fn unified_qr_send_receive() { assert_eq!(node_b.list_balances().total_lightning_balance_sats, 200_000); } +#[tokio::test(flavor = "multi_thread")] +async fn unified_send_to_hrn() { + let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); + let chain_source = TestChainSource::Esplora(&electrsd); + + let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false, true); + + let address_a = node_a.onchain_payment().new_address().unwrap(); + let premined_sats = 5_000_000; + + premine_and_distribute_funds( + &bitcoind.client, + &electrsd.client, + vec![address_a], + Amount::from_sat(premined_sats), + ); + + node_a.sync_wallets().unwrap(); + open_channel(&node_a, &node_b, 4_000_000, true, &electrsd); + generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6); + + node_a.sync_wallets().unwrap(); + node_b.sync_wallets().unwrap(); + + expect_channel_ready_event!(node_a, node_b.node_id()); + expect_channel_ready_event!(node_b, node_a.node_id()); + + // Sleep until we broadcast a node announcement. + while node_b.status().latest_node_announcement_broadcast_timestamp.is_none() { + std::thread::sleep(std::time::Duration::from_millis(10)); + } + + println!("node_b has broadcasted node announcement!"); + + //let offer = node_b.bolt12_payment().receive(1000000, "test offer", None, None).unwrap(); + //println!("offer! {:?}", offer.to_string()); + + // Sleep one more sec to make sure the node announcement propagates. + std::thread::sleep(std::time::Duration::from_secs(1)); + + let hrn = "chuks@peepswire.com"; + let offer_payment_id: PaymentId = match node_a.unified_payment().send(&hrn, None).await { + Ok(UnifiedPaymentResult::Bolt12 { payment_id }) => { + println!("\nBolt12 payment sent successfully with PaymentID: {:?}", payment_id); + payment_id + }, + Ok(UnifiedPaymentResult::Bolt11 { payment_id: _ }) => { + panic!("Expected Bolt12 payment but got Bolt11"); + }, + Ok(UnifiedPaymentResult::Onchain { txid: _ }) => { + panic!("Expected Bolt12 payment but got On-chain transaction"); + }, + Err(e) => { + panic!("Expected Bolt12 payment but got error: {:?}", e); + }, + }; + + expect_payment_successful_event!(node_a, Some(offer_payment_id), None); +} + #[test] fn lsps2_client_service_integration() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); @@ -1627,7 +1687,7 @@ fn facade_logging() { fn spontaneous_send_with_custom_preimage() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let chain_source = TestChainSource::Esplora(&electrsd); - let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false); + let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false, false); let address_a = node_a.onchain_payment().new_address().unwrap(); let premine_sat = 1_000_000;