From 972afcb5138332d86ccbf764a391c3544c12dc96 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Mon, 22 May 2023 11:07:57 +0200 Subject: [PATCH] Update to LDK 0.0.116 --- Cargo.toml | 14 ++-- bindings/ldk_node.udl | 34 +++++---- src/builder.rs | 24 ++++--- src/event.rs | 18 ++++- src/io/utils.rs | 6 +- src/lib.rs | 52 ++++++++------ src/test/functional_tests.rs | 6 +- src/types.rs | 131 +++++++++++++++++++++++++++++++++-- src/uniffi_types.rs | 8 +-- src/wallet.rs | 83 +++++++++++++--------- 10 files changed, 275 insertions(+), 101 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 936a33c51..75ee56d81 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,13 +32,13 @@ panic = 'abort' # Abort on panic default = [] [dependencies] -lightning = { version = "0.0.115", features = ["max_level_trace", "std"] } -lightning-invoice = { version = "0.23" } -lightning-net-tokio = { version = "0.0.115" } -lightning-persister = { version = "0.0.115" } -lightning-background-processor = { version = "0.0.115", features = ["futures"] } -lightning-rapid-gossip-sync = { version = "0.0.115" } -lightning-transaction-sync = { version = "0.0.115", features = ["esplora-async-https"] } +lightning = { version = "0.0.116", features = ["max_level_trace", "std"] } +lightning-invoice = { version = "0.24.0" } +lightning-net-tokio = { version = "0.0.116" } +lightning-persister = { version = "0.0.116" } +lightning-background-processor = { version = "0.0.116", features = ["futures"] } +lightning-rapid-gossip-sync = { version = "0.0.116" } +lightning-transaction-sync = { version = "0.0.116", features = ["esplora-async-https"] } #lightning = { git = "https://github.com/lightningdevkit/rust-lightning", branch="main", features = ["max_level_trace", "std"] } #lightning-invoice = { git = "https://github.com/lightningdevkit/rust-lightning", branch="main" } diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index 75a6374bf..6bbdd95d7 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -62,23 +62,23 @@ interface LDKNode { [Throws=NodeError] void close_channel([ByRef]ChannelId channel_id, PublicKey counterparty_node_id); [Throws=NodeError] - void update_channel_config([ByRef]ChannelId channel_id, PublicKey counterparty_node_id, [ByRef]ChannelConfig channel_config); + void update_channel_config([ByRef]ChannelId channel_id, PublicKey counterparty_node_id, ChannelConfig channel_config); [Throws=NodeError] void sync_wallets(); [Throws=NodeError] - PaymentHash send_payment([ByRef]Invoice invoice); + PaymentHash send_payment([ByRef]Bolt11Invoice invoice); [Throws=NodeError] - PaymentHash send_payment_using_amount([ByRef]Invoice invoice, u64 amount_msat); + 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_probe([ByRef]Invoice invoice); + void send_payment_probe([ByRef]Bolt11Invoice invoice); [Throws=NodeError] void send_spontaneous_payment_probe(u64 amount_msat, PublicKey node_id); [Throws=NodeError] - Invoice receive_payment(u64 amount_msat, [ByRef]string description, u32 expiry_secs); + Bolt11Invoice receive_payment(u64 amount_msat, [ByRef]string description, u32 expiry_secs); [Throws=NodeError] - Invoice receive_variable_amount_payment([ByRef]string description, u32 expiry_secs); + Bolt11Invoice receive_variable_amount_payment([ByRef]string description, u32 expiry_secs); PaymentDetails? payment([ByRef]PaymentHash payment_hash); [Throws=NodeError] boolean remove_payment([ByRef]PaymentHash payment_hash); @@ -204,12 +204,20 @@ dictionary PeerDetails { boolean is_connected; }; -dictionary ChannelConfig { - u32 forwarding_fee_proportional_millionths; - u32 forwarding_fee_base_msat; - u16 cltv_expiry_delta; - u64 max_dust_htlc_exposure_msat; - u64 force_close_avoidance_max_fee_satoshis; +interface ChannelConfig { + constructor(); + u32 forwarding_fee_proportional_millionths(); + void set_forwarding_fee_proportional_millionths(u32 value); + u32 forwarding_fee_base_msat(); + void set_forwarding_fee_base_msat(u32 fee_msat); + u16 cltv_expiry_delta(); + void set_cltv_expiry_delta(u16 value); + u64 force_close_avoidance_max_fee_satoshis(); + void set_force_close_avoidance_max_fee_satoshis(u64 value_sat); + boolean accept_underpaying_htlcs(); + void set_accept_underpaying_htlcs(boolean value); + void set_max_dust_htlc_exposure_from_fixed_limit(u64 limit_msat); + void set_max_dust_htlc_exposure_from_fee_rate_multiplier(u64 multiplier); }; enum LogLevel { @@ -234,7 +242,7 @@ typedef string PublicKey; typedef string Address; [Custom] -typedef string Invoice; +typedef string Bolt11Invoice; [Custom] typedef string PaymentHash; diff --git a/src/builder.rs b/src/builder.rs index bc5575ddf..0314810c2 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -8,8 +8,8 @@ use crate::logger::{log_error, FilesystemLogger, Logger}; use crate::payment_store::PaymentStore; use crate::peer_store::PeerStore; use crate::types::{ - ChainMonitor, ChannelManager, GossipSync, KeysManager, NetAddress, NetworkGraph, - OnionMessenger, PeerManager, + ChainMonitor, ChannelManager, FakeMessageRouter, GossipSync, KeysManager, NetAddress, + NetworkGraph, OnionMessenger, PeerManager, }; use crate::wallet::Wallet; use crate::LogLevel; @@ -18,13 +18,15 @@ use crate::{ WALLET_KEYS_SEED_LEN, }; -use lightning::chain::keysinterface::EntropySource; use lightning::chain::{chainmonitor, BestBlock, Watch}; use lightning::ln::channelmanager::{self, ChainParameters, ChannelManagerReadArgs}; use lightning::ln::msgs::RoutingMessageHandler; use lightning::ln::peer_handler::{IgnoringMessageHandler, MessageHandler}; use lightning::routing::router::DefaultRouter; -use lightning::routing::scoring::{ProbabilisticScorer, ProbabilisticScoringParameters}; +use lightning::routing::scoring::{ + ProbabilisticScorer, ProbabilisticScoringDecayParameters, ProbabilisticScoringFeeParameters, +}; +use lightning::sign::EntropySource; use lightning::util::config::UserConfig; use lightning::util::ser::ReadableArgs; @@ -505,7 +507,7 @@ fn build_with_store_internal( Ok(scorer) => Arc::new(Mutex::new(scorer)), Err(e) => { if e.kind() == std::io::ErrorKind::NotFound { - let params = ProbabilisticScoringParameters::default(); + let params = ProbabilisticScoringDecayParameters::default(); Arc::new(Mutex::new(ProbabilisticScorer::new( params, Arc::clone(&network_graph), @@ -517,11 +519,13 @@ fn build_with_store_internal( } }; + let scoring_fee_params = ProbabilisticScoringFeeParameters::default(); let router = Arc::new(DefaultRouter::new( Arc::clone(&network_graph), Arc::clone(&logger), keys_manager.get_secure_random_bytes(), Arc::clone(&scorer), + scoring_fee_params, )); // Read ChannelMonitor state from store @@ -594,6 +598,7 @@ fn build_with_store_internal( Arc::clone(&keys_manager), user_config, chain_params, + cur_time.as_secs() as u32, ) } }; @@ -611,14 +616,12 @@ fn build_with_store_internal( Arc::clone(&keys_manager), Arc::clone(&keys_manager), Arc::clone(&logger), + Arc::new(FakeMessageRouter {}), + IgnoringMessageHandler {}, IgnoringMessageHandler {}, )); let ephemeral_bytes: [u8; 32] = keys_manager.get_secure_random_bytes(); - let cur_time = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .map_err(|_| BuildError::InvalidSystemTime)?; - // Initialize the GossipSource // Use the configured gossip source, if the user set one, otherwise default to P2PNetwork. let gossip_source_config = gossip_source_config.unwrap_or(&GossipSourceConfig::P2PNetwork); @@ -658,12 +661,14 @@ fn build_with_store_internal( route_handler: Arc::clone(&p2p_gossip_sync) as Arc, onion_message_handler: onion_messenger, + custom_message_handler: IgnoringMessageHandler {}, }, GossipSync::Rapid(_) => MessageHandler { chan_handler: Arc::clone(&channel_manager), route_handler: Arc::new(IgnoringMessageHandler {}) as Arc, onion_message_handler: onion_messenger, + custom_message_handler: IgnoringMessageHandler {}, }, GossipSync::None => { unreachable!("We must always have a gossip sync!"); @@ -675,7 +680,6 @@ fn build_with_store_internal( cur_time.as_secs().try_into().map_err(|_| BuildError::InvalidSystemTime)?, &ephemeral_bytes, Arc::clone(&logger), - IgnoringMessageHandler {}, Arc::clone(&keys_manager), )); diff --git a/src/event.rs b/src/event.rs index 6cb8c028a..31a115da5 100644 --- a/src/event.rs +++ b/src/event.rs @@ -20,7 +20,7 @@ use lightning::util::errors::APIError; use lightning::util::ser::{Readable, ReadableArgs, Writeable, Writer}; use bitcoin::secp256k1::{PublicKey, Secp256k1}; -use bitcoin::OutPoint; +use bitcoin::{LockTime, OutPoint, PackedLockTime}; use rand::{thread_rng, Rng}; use std::collections::VecDeque; use std::ops::Deref; @@ -270,11 +270,16 @@ where // channel. let confirmation_target = ConfirmationTarget::Normal; + // We set nLockTime to the current height to discourage fee sniping. + let cur_height = self.channel_manager.current_best_block().height(); + let locktime = LockTime::from_height(cur_height).unwrap_or(LockTime::ZERO); + // Sign the final funding transaction and broadcast it. match self.wallet.create_funding_transaction( output_script, channel_value_satoshis, confirmation_target, + locktime, ) { Ok(final_tx) => { // Give the funding transaction back to LDK for opening the channel. @@ -329,6 +334,7 @@ where via_user_channel_id: _, claim_deadline: _, onion_fields: _, + counterparty_skimmed_fee_msat: _, } => { if let Some(info) = self.payment_store.get(&payment_hash) { if info.status == PaymentStatus::Succeeded { @@ -556,15 +562,22 @@ where let output_descriptors = &outputs.iter().collect::>(); let tx_feerate = self.wallet.get_est_sat_per_1000_weight(ConfirmationTarget::Normal); + + // We set nLockTime to the current height to discourage fee sniping. + let cur_height = self.channel_manager.current_best_block().height(); + let locktime: PackedLockTime = + LockTime::from_height(cur_height).map_or(PackedLockTime::ZERO, |l| l.into()); let res = self.keys_manager.spend_spendable_outputs( output_descriptors, Vec::new(), destination_address.script_pubkey(), tx_feerate, + Some(locktime), &Secp256k1::new(), ); + match res { - Ok(Some(spending_tx)) => self.wallet.broadcast_transaction(&spending_tx), + Ok(Some(spending_tx)) => self.wallet.broadcast_transactions(&[&spending_tx]), Ok(None) => { log_debug!(self.logger, "Omitted spending static outputs: {:?}", outputs); } @@ -747,6 +760,7 @@ where } LdkEvent::DiscardFunding { .. } => {} LdkEvent::HTLCIntercepted { .. } => {} + LdkEvent::BumpTransaction(_) => {} } } } diff --git a/src/io/utils.rs b/src/io/utils.rs index 91166e645..9bace5d5a 100644 --- a/src/io/utils.rs +++ b/src/io/utils.rs @@ -6,9 +6,9 @@ use crate::peer_store::PeerStore; use crate::{Error, EventQueue, PaymentDetails}; use lightning::chain::channelmonitor::ChannelMonitor; -use lightning::chain::keysinterface::{EntropySource, SignerProvider}; use lightning::routing::gossip::NetworkGraph; -use lightning::routing::scoring::{ProbabilisticScorer, ProbabilisticScoringParameters}; +use lightning::routing::scoring::{ProbabilisticScorer, ProbabilisticScoringDecayParameters}; +use lightning::sign::{EntropySource, SignerProvider}; use lightning::util::logger::Logger; use lightning::util::ser::{Readable, ReadableArgs, Writeable}; @@ -161,7 +161,7 @@ pub(crate) fn read_scorer< where L::Target: Logger, { - let params = ProbabilisticScoringParameters::default(); + let params = ProbabilisticScoringDecayParameters::default(); let mut reader = kv_store.read(SCORER_PERSISTENCE_NAMESPACE, SCORER_PERSISTENCE_KEY)?; let args = (params, network_graph, logger.clone()); ProbabilisticScorer::read(&mut reader, args).map_err(|e| { diff --git a/src/lib.rs b/src/lib.rs index 96d642829..b06f0a2ae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,7 +27,7 @@ //! //! ```no_run //! use ldk_node::{Builder, NetAddress}; -//! use ldk_node::lightning_invoice::Invoice; +//! use ldk_node::lightning_invoice::Bolt11Invoice; //! use ldk_node::bitcoin::secp256k1::PublicKey; //! use ldk_node::bitcoin::Network; //! use std::str::FromStr; @@ -54,7 +54,7 @@ //! println!("EVENT: {:?}", event); //! node.event_handled(); //! -//! let invoice = Invoice::from_str("INVOICE_STR").unwrap(); +//! let invoice = Bolt11Invoice::from_str("INVOICE_STR").unwrap(); //! node.send_payment(&invoice).unwrap(); //! //! node.stop().unwrap(); @@ -99,6 +99,7 @@ pub use error::Error as NodeError; use error::Error; pub use event::Event; +pub use types::ChannelConfig; pub use types::NetAddress; pub use io::utils::generate_entropy_mnemonic; @@ -124,12 +125,12 @@ use wallet::Wallet; use logger::{log_debug, log_error, log_info, log_trace, FilesystemLogger, Logger}; -use lightning::chain::keysinterface::EntropySource; use lightning::chain::Confirm; use lightning::ln::channelmanager::{self, PaymentId, RecipientOnionFields, Retry}; use lightning::ln::{PaymentHash, PaymentPreimage}; +use lightning::sign::EntropySource; -use lightning::util::config::{ChannelConfig, ChannelHandshakeConfig, UserConfig}; +use lightning::util::config::{ChannelHandshakeConfig, UserConfig}; pub use lightning::util::logger::Level as LogLevel; use lightning_background_processor::process_events_async; @@ -137,7 +138,7 @@ use lightning_background_processor::process_events_async; use lightning_transaction_sync::EsploraSyncClient; use lightning::routing::router::{PaymentParameters, RouteParameters, Router as LdkRouter}; -use lightning_invoice::{payment, Currency, Invoice}; +use lightning_invoice::{payment, Bolt11Invoice, Currency}; use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::hashes::Hash; @@ -889,7 +890,7 @@ impl Node { /// Returns a temporary channel id. pub fn connect_open_channel( &self, node_id: PublicKey, address: NetAddress, channel_amount_sats: u64, - push_to_counterparty_msat: Option, channel_config: Option, + push_to_counterparty_msat: Option, channel_config: Option>, announce_channel: bool, ) -> Result<(), Error> { let rt_lock = self.runtime.read().unwrap(); @@ -919,13 +920,14 @@ impl Node { }) })?; + let channel_config = (*(channel_config.unwrap_or_default())).clone().into(); let user_config = UserConfig { channel_handshake_limits: Default::default(), channel_handshake_config: ChannelHandshakeConfig { announced_channel: announce_channel, ..Default::default() }, - channel_config: channel_config.unwrap_or_default(), + channel_config, ..Default::default() }; @@ -1029,15 +1031,19 @@ impl Node { /// Update the config for a previously opened channel. pub fn update_channel_config( &self, channel_id: &ChannelId, counterparty_node_id: PublicKey, - channel_config: &ChannelConfig, + channel_config: Arc, ) -> Result<(), Error> { self.channel_manager - .update_channel_config(&counterparty_node_id, &[channel_id.0], channel_config) + .update_channel_config( + &counterparty_node_id, + &[channel_id.0], + &(*channel_config).clone().into(), + ) .map_err(|_| Error::ChannelConfigUpdateFailed) } /// Send a payment given an invoice. - pub fn send_payment(&self, invoice: &Invoice) -> Result { + pub fn send_payment(&self, invoice: &Bolt11Invoice) -> Result { let rt_lock = self.runtime.read().unwrap(); if rt_lock.is_none() { return Err(Error::NotRunning); @@ -1113,7 +1119,7 @@ impl Node { /// 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: &Invoice, amount_msat: u64, + &self, invoice: &Bolt11Invoice, amount_msat: u64, ) -> Result { let rt_lock = self.runtime.read().unwrap(); if rt_lock.is_none() { @@ -1147,9 +1153,12 @@ impl Node { invoice.min_final_cltv_expiry_delta() as u32, ) .with_expiry_time(expiry_time.as_secs()) - .with_route_hints(invoice.route_hints()); + .with_route_hints(invoice.route_hints()) + .map_err(|_| Error::InvalidInvoice)?; if let Some(features) = invoice.features() { - payment_params = payment_params.with_features(features.clone()); + payment_params = payment_params + .with_bolt11_features(features.clone()) + .map_err(|_| Error::InvalidInvoice)?; } let route_params = RouteParameters { payment_params, final_value_msat: amount_msat }; @@ -1294,7 +1303,7 @@ impl Node { /// 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. - pub fn send_payment_probe(&self, invoice: &Invoice) -> Result<(), Error> { + pub fn send_payment_probe(&self, invoice: &Bolt11Invoice) -> Result<(), Error> { let rt_lock = self.runtime.read().unwrap(); if rt_lock.is_none() { return Err(Error::NotRunning); @@ -1313,9 +1322,12 @@ impl Node { invoice.min_final_cltv_expiry_delta() as u32, ) .with_expiry_time(expiry_time.as_secs()) - .with_route_hints(invoice.route_hints()); + .with_route_hints(invoice.route_hints()) + .map_err(|_| Error::InvalidInvoice)?; if let Some(features) = invoice.features() { - payment_params = payment_params.with_features(features.clone()); + payment_params = payment_params + .with_bolt11_features(features.clone()) + .map_err(|_| Error::InvalidInvoice)?; } let route_params = RouteParameters { payment_params, final_value_msat: amount_msat }; @@ -1356,7 +1368,7 @@ impl Node { &payer, &route_params, Some(&first_hops.iter().collect::>()), - &inflight_htlcs, + inflight_htlcs, ) .map_err(|e| { log_error!(self.logger, "Failed to find path for payment probe: {:?}", e); @@ -1385,7 +1397,7 @@ impl Node { /// given. pub fn receive_payment( &self, amount_msat: u64, description: &str, expiry_secs: u32, - ) -> Result { + ) -> Result { self.receive_payment_inner(Some(amount_msat), description, expiry_secs) } @@ -1393,13 +1405,13 @@ impl Node { /// 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 { + ) -> Result { self.receive_payment_inner(None, description, expiry_secs) } fn receive_payment_inner( &self, amount_msat: Option, description: &str, expiry_secs: u32, - ) -> Result { + ) -> 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( diff --git a/src/test/functional_tests.rs b/src/test/functional_tests.rs index c59a0d33f..cbb847e5d 100644 --- a/src/test/functional_tests.rs +++ b/src/test/functional_tests.rs @@ -137,7 +137,7 @@ fn do_channel_full_cycle( }; println!("\nB receive_payment"); - let invoice_amount_1_msat = 1000000; + let invoice_amount_1_msat = 2500_000; let invoice = node_b.receive_payment(invoice_amount_1_msat, &"asdf", 9217).unwrap(); println!("\nA send_payment"); @@ -181,7 +181,7 @@ fn do_channel_full_cycle( assert_eq!(node_b.payment(&payment_hash).unwrap().amount_msat, Some(invoice_amount_1_msat)); // Test under-/overpayment - let invoice_amount_2_msat = 1000_000; + let invoice_amount_2_msat = 2500_000; let invoice = node_b.receive_payment(invoice_amount_2_msat, &"asdf", 9217).unwrap(); let underpaid_amount = invoice_amount_2_msat - 1; @@ -214,7 +214,7 @@ fn do_channel_full_cycle( // Test "zero-amount" invoice payment let variable_amount_invoice = node_b.receive_variable_amount_payment(&"asdf", 9217).unwrap(); - let determined_amount_msat = 1234_567; + let determined_amount_msat = 2345_678; assert_eq!(Err(Error::InvalidInvoice), node_a.send_payment(&variable_amount_invoice)); let payment_hash = node_a.send_payment_using_amount(&variable_amount_invoice, determined_amount_msat).unwrap(); diff --git a/src/types.rs b/src/types.rs index b7c38b351..b031dec69 100644 --- a/src/types.rs +++ b/src/types.rs @@ -2,14 +2,16 @@ use crate::logger::FilesystemLogger; use crate::wallet::{Wallet, WalletKeysManager}; use lightning::chain::chainmonitor; -use lightning::chain::keysinterface::InMemorySigner; use lightning::ln::channelmanager::ChannelDetails as LdkChannelDetails; use lightning::ln::msgs::NetAddress as LdkNetAddress; use lightning::ln::msgs::RoutingMessageHandler; use lightning::ln::peer_handler::IgnoringMessageHandler; use lightning::routing::gossip; use lightning::routing::router::DefaultRouter; -use lightning::routing::scoring::ProbabilisticScorer; +use lightning::routing::scoring::{ProbabilisticScorer, ProbabilisticScoringFeeParameters}; +use lightning::sign::InMemorySigner; +use lightning::util::config::ChannelConfig as LdkChannelConfig; +use lightning::util::config::MaxDustHTLCExposure as LdkMaxDustHTLCExposure; use lightning::util::ser::{Hostname, Readable, Writeable, Writer}; use lightning_net_tokio::SocketDescriptor; use lightning_transaction_sync::EsploraSyncClient; @@ -21,7 +23,7 @@ use std::convert::TryFrom; use std::fmt::Display; use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs}; use std::str::FromStr; -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, Mutex, RwLock}; pub(crate) type ChainMonitor = chainmonitor::ChainMonitor< InMemorySigner, @@ -56,8 +58,13 @@ pub(crate) type ChannelManager = lightning::ln::channelmanager::ChannelManage pub(crate) type KeysManager = WalletKeysManager>; -pub(crate) type Router = - DefaultRouter, Arc, Arc>>; +pub(crate) type Router = DefaultRouter< + Arc, + Arc, + Arc>, + ProbabilisticScoringFeeParameters, + Scorer, +>; pub(crate) type Scorer = ProbabilisticScorer, Arc>; pub(crate) type NetworkGraph = gossip::NetworkGraph>; @@ -84,9 +91,22 @@ pub(crate) type OnionMessenger = lightning::onion_message::OnionMessenger< Arc>>, Arc>>, Arc, + Arc, + IgnoringMessageHandler, IgnoringMessageHandler, >; +pub(crate) struct FakeMessageRouter {} + +impl lightning::onion_message::MessageRouter for FakeMessageRouter { + fn find_path( + &self, _sender: PublicKey, _peers: Vec, + _destination: lightning::onion_message::Destination, + ) -> Result { + unimplemented!() + } +} + /// The global identifier of a channel. /// /// Note that this will start out to be a temporary ID until channel funding negotiation is @@ -375,3 +395,104 @@ impl Readable for NetAddress { Ok(Self(addr)) } } + +/// Options which apply on a per-channel basis. +/// +/// See documentation of [`LdkChannelConfig`] for details. +#[derive(Debug)] +pub struct ChannelConfig { + inner: RwLock, +} + +impl Clone for ChannelConfig { + fn clone(&self) -> Self { + self.inner.read().unwrap().clone().into() + } +} + +impl ChannelConfig { + /// Constructs a new `ChannelConfig`. + pub fn new() -> Self { + Self::default() + } + + /// Returns the set `forwarding_fee_proportional_millionths`. + pub fn forwarding_fee_proportional_millionths(&self) -> u32 { + self.inner.read().unwrap().forwarding_fee_proportional_millionths + } + + /// Sets the `forwarding_fee_proportional_millionths`. + pub fn set_forwarding_fee_proportional_millionths(&self, value: u32) { + self.inner.write().unwrap().forwarding_fee_proportional_millionths = value; + } + + /// Returns the set `forwarding_fee_base_msat`. + pub fn forwarding_fee_base_msat(&self) -> u32 { + self.inner.read().unwrap().forwarding_fee_base_msat + } + + /// Sets the `forwarding_fee_base_msat`. + pub fn set_forwarding_fee_base_msat(&self, fee_msat: u32) { + self.inner.write().unwrap().forwarding_fee_base_msat = fee_msat; + } + + /// Returns the set `cltv_expiry_delta`. + pub fn cltv_expiry_delta(&self) -> u16 { + self.inner.read().unwrap().cltv_expiry_delta + } + + /// Sets the `cltv_expiry_delta`. + pub fn set_cltv_expiry_delta(&self, value: u16) { + self.inner.write().unwrap().cltv_expiry_delta = value; + } + + /// Returns the set `force_close_avoidance_max_fee_satoshis`. + pub fn force_close_avoidance_max_fee_satoshis(&self) -> u64 { + self.inner.read().unwrap().force_close_avoidance_max_fee_satoshis + } + + /// Sets the `force_close_avoidance_max_fee_satoshis`. + pub fn set_force_close_avoidance_max_fee_satoshis(&self, value_sat: u64) { + self.inner.write().unwrap().force_close_avoidance_max_fee_satoshis = value_sat; + } + + /// Returns the set `accept_underpaying_htlcs`. + pub fn accept_underpaying_htlcs(&self) -> bool { + self.inner.read().unwrap().accept_underpaying_htlcs + } + + /// Sets the `accept_underpaying_htlcs`. + pub fn set_accept_underpaying_htlcs(&self, value: bool) { + self.inner.write().unwrap().accept_underpaying_htlcs = value; + } + + /// Sets the `max_dust_htlc_exposure` from a fixed limit. + pub fn set_max_dust_htlc_exposure_from_fixed_limit(&self, limit_msat: u64) { + self.inner.write().unwrap().max_dust_htlc_exposure = + LdkMaxDustHTLCExposure::FixedLimitMsat(limit_msat); + } + + /// Sets the `max_dust_htlc_exposure` from a fee rate multiplier. + pub fn set_max_dust_htlc_exposure_from_fee_rate_multiplier(&self, multiplier: u64) { + self.inner.write().unwrap().max_dust_htlc_exposure = + LdkMaxDustHTLCExposure::FeeRateMultiplier(multiplier); + } +} + +impl From for ChannelConfig { + fn from(value: LdkChannelConfig) -> Self { + Self { inner: RwLock::new(value) } + } +} + +impl From for LdkChannelConfig { + fn from(value: ChannelConfig) -> Self { + *value.inner.read().unwrap() + } +} + +impl Default for ChannelConfig { + fn default() -> Self { + LdkChannelConfig::default().into() + } +} diff --git a/src/uniffi_types.rs b/src/uniffi_types.rs index b6e9101f6..20c8872bb 100644 --- a/src/uniffi_types.rs +++ b/src/uniffi_types.rs @@ -10,7 +10,7 @@ use bitcoin::hashes::Hash; use bitcoin::secp256k1::PublicKey; use bitcoin::{Address, Txid}; use lightning::ln::{PaymentHash, PaymentPreimage, PaymentSecret}; -use lightning_invoice::{Invoice, SignedRawInvoice}; +use lightning_invoice::{Bolt11Invoice, SignedRawBolt11Invoice}; use bip39::Mnemonic; @@ -53,12 +53,12 @@ impl UniffiCustomTypeConverter for Address { } } -impl UniffiCustomTypeConverter for Invoice { +impl UniffiCustomTypeConverter for Bolt11Invoice { type Builtin = String; fn into_custom(val: Self::Builtin) -> uniffi::Result { - if let Ok(signed) = val.parse::() { - if let Ok(invoice) = Invoice::from_signed(signed) { + if let Ok(signed) = val.parse::() { + if let Ok(invoice) = Bolt11Invoice::from_signed(signed) { return Ok(invoice); } } diff --git a/src/wallet.rs b/src/wallet.rs index 8d1310798..de4fd38c9 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -6,12 +6,12 @@ use lightning::chain::chaininterface::{ BroadcasterInterface, ConfirmationTarget, FeeEstimator, FEERATE_FLOOR_SATS_PER_KW, }; -use lightning::chain::keysinterface::{ +use lightning::ln::msgs::{DecodeError, UnsignedGossipMessage}; +use lightning::ln::script::ShutdownScript; +use lightning::sign::{ EntropySource, InMemorySigner, KeyMaterial, KeysManager, NodeSigner, Recipient, SignerProvider, SpendableOutputDescriptor, }; -use lightning::ln::msgs::{DecodeError, UnsignedGossipMessage}; -use lightning::ln::script::ShutdownScript; use lightning::util::message_signing; @@ -24,7 +24,7 @@ use bitcoin::bech32::u5; use bitcoin::secp256k1::ecdh::SharedSecret; use bitcoin::secp256k1::ecdsa::{RecoverableSignature, Signature}; use bitcoin::secp256k1::{PublicKey, Scalar, Secp256k1, Signing}; -use bitcoin::{Script, Transaction, TxOut, Txid}; +use bitcoin::{LockTime, PackedLockTime, Script, Transaction, TxOut, Txid}; use std::collections::HashMap; use std::ops::Deref; @@ -116,12 +116,14 @@ where let mut locked_fee_rate_cache = self.fee_rate_cache.write().unwrap(); let confirmation_targets = vec![ + ConfirmationTarget::MempoolMinimum, ConfirmationTarget::Background, ConfirmationTarget::Normal, ConfirmationTarget::HighPriority, ]; for target in confirmation_targets { let num_blocks = match target { + ConfirmationTarget::MempoolMinimum => 1008, ConfirmationTarget::Background => 12, ConfirmationTarget::Normal => 6, ConfirmationTarget::HighPriority => 3, @@ -154,13 +156,18 @@ where pub(crate) fn create_funding_transaction( &self, output_script: Script, value_sats: u64, confirmation_target: ConfirmationTarget, + locktime: LockTime, ) -> Result { let fee_rate = self.estimate_fee_rate(confirmation_target); let locked_wallet = self.inner.lock().unwrap(); let mut tx_builder = locked_wallet.build_tx(); - tx_builder.add_recipient(output_script, value_sats).fee_rate(fee_rate).enable_rbf(); + tx_builder + .add_recipient(output_script, value_sats) + .fee_rate(fee_rate) + .nlocktime(locktime) + .enable_rbf(); let mut psbt = match tx_builder.finish() { Ok((psbt, _)) => { @@ -249,7 +256,7 @@ where psbt.extract_tx() }; - self.broadcast_transaction(&tx); + self.broadcast_transactions(&[&tx]); let txid = tx.txid(); @@ -277,7 +284,8 @@ where let locked_fee_rate_cache = self.fee_rate_cache.read().unwrap(); let fallback_sats_kwu = match confirmation_target { - ConfirmationTarget::Background => FEERATE_FLOOR_SATS_PER_KW, + ConfirmationTarget::MempoolMinimum => FEERATE_FLOOR_SATS_PER_KW, + ConfirmationTarget::Background => 500, ConfirmationTarget::Normal => 2000, ConfirmationTarget::HighPriority => 5000, }; @@ -305,25 +313,36 @@ where D: BatchDatabase, L::Target: Logger, { - fn broadcast_transaction(&self, tx: &Transaction) { + fn broadcast_transactions(&self, txs: &[&Transaction]) { let locked_runtime = self.runtime.read().unwrap(); if locked_runtime.as_ref().is_none() { log_error!(self.logger, "Failed to broadcast transaction: No runtime."); return; } - let res = tokio::task::block_in_place(move || { - locked_runtime - .as_ref() - .unwrap() - .block_on(async move { self.blockchain.broadcast(tx).await }) + let errors = tokio::task::block_in_place(move || { + locked_runtime.as_ref().unwrap().block_on(async move { + let mut handles = Vec::new(); + let mut errors = Vec::new(); + + for tx in txs { + handles.push((tx.txid(), self.blockchain.broadcast(tx))); + } + + for handle in handles { + match handle.1.await { + Ok(_) => {} + Err(e) => { + errors.push((e, handle.0)); + } + } + } + errors + }) }); - match res { - Ok(_) => {} - Err(err) => { - log_error!(self.logger, "Failed to broadcast transaction: {}", err); - } + for (e, txid) in errors { + log_error!(self.logger, "Failed to broadcast transaction {}: {}", txid, e); } } } @@ -361,7 +380,7 @@ where pub fn spend_spendable_outputs( &self, descriptors: &[&SpendableOutputDescriptor], outputs: Vec, change_destination_script: Script, feerate_sat_per_1000_weight: u32, - secp_ctx: &Secp256k1, + locktime: Option, secp_ctx: &Secp256k1, ) -> Result, ()> { let only_non_static = &descriptors .iter() @@ -377,6 +396,7 @@ where outputs, change_destination_script, feerate_sat_per_1000_weight, + locktime, secp_ctx, ) .map(Some) @@ -455,28 +475,23 @@ where self.inner.read_chan_signer(reader) } - fn get_destination_script(&self) -> Script { - let address = self.wallet.get_new_address().unwrap_or_else(|e| { + fn get_destination_script(&self) -> Result { + let address = self.wallet.get_new_address().map_err(|e| { log_error!(self.logger, "Failed to retrieve new address from wallet: {}", e); - panic!("Failed to retrieve new address from wallet"); - }); - address.script_pubkey() + })?; + Ok(address.script_pubkey()) } - fn get_shutdown_scriptpubkey(&self) -> ShutdownScript { - let address = self.wallet.get_new_address().unwrap_or_else(|e| { + fn get_shutdown_scriptpubkey(&self) -> Result { + let address = self.wallet.get_new_address().map_err(|e| { log_error!(self.logger, "Failed to retrieve new address from wallet: {}", e); - panic!("Failed to retrieve new address from wallet"); - }); + })?; match address.payload { bitcoin::util::address::Payload::WitnessProgram { version, program } => { - return ShutdownScript::new_witness_program(version, &program).unwrap_or_else( - |e| { - log_error!(self.logger, "Invalid shutdown script: {:?}", e); - panic!("Invalid shutdown script."); - }, - ); + ShutdownScript::new_witness_program(version, &program).map_err(|e| { + log_error!(self.logger, "Invalid shutdown script: {:?}", e); + }) } _ => { log_error!(