From: Matt Corallo <649246+TheBlueMatt@users.noreply.github.com> Date: Thu, 28 Apr 2022 21:14:19 +0000 (+0000) Subject: Merge pull request #1425 from valentinewallace/2021-04-wumbo X-Git-Tag: v0.0.107~50 X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=commitdiff_plain;h=f53d13bcb8220b3ce39e51a4d20beb23b3930d1f;hp=5cfe19ef02cc9746ba664aeaa90921691a75dcd6;p=rust-lightning Merge pull request #1425 from valentinewallace/2021-04-wumbo Wumbo! --- diff --git a/fuzz/src/router.rs b/fuzz/src/router.rs index bff177b1..27c5ee2b 100644 --- a/fuzz/src/router.rs +++ b/fuzz/src/router.rs @@ -29,6 +29,7 @@ use bitcoin::blockdata::constants::genesis_block; use utils::test_logger; +use std::convert::TryInto; use std::collections::HashSet; use std::sync::Arc; use std::sync::atomic::{AtomicUsize, Ordering}; @@ -205,7 +206,8 @@ pub fn do_test(data: &[u8], out: Out) { count => { for _ in 0..count { scid += 1; - let rnid = node_pks.iter().skip(slice_to_be16(get_slice!(2))as usize % node_pks.len()).next().unwrap(); + let rnid = node_pks.iter().skip(u16::from_be_bytes(get_slice!(2).try_into().unwrap()) as usize % node_pks.len()).next().unwrap(); + let capacity = u64::from_be_bytes(get_slice!(8).try_into().unwrap()); first_hops_vec.push(ChannelDetails { channel_id: [0; 32], counterparty: ChannelCounterparty { @@ -213,12 +215,14 @@ pub fn do_test(data: &[u8], out: Out) { features: InitFeatures::known(), unspendable_punishment_reserve: 0, forwarding_info: None, + outbound_htlc_minimum_msat: None, + outbound_htlc_maximum_msat: None, }, funding_txo: Some(OutPoint { txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(), index: 0 }), channel_type: None, short_channel_id: Some(scid), inbound_scid_alias: None, - channel_value_satoshis: slice_to_be64(get_slice!(8)), + channel_value_satoshis: capacity, user_channel_id: 0, inbound_capacity_msat: 0, unspendable_punishment_reserve: None, confirmations_required: None, @@ -226,7 +230,10 @@ pub fn do_test(data: &[u8], out: Out) { is_outbound: true, is_funding_locked: true, is_usable: true, is_public: true, balance_msat: 0, - outbound_capacity_msat: 0, + outbound_capacity_msat: capacity.saturating_mul(1000), + next_outbound_htlc_limit_msat: capacity.saturating_mul(1000), + inbound_htlc_minimum_msat: None, + inbound_htlc_maximum_msat: None, }); } Some(&first_hops_vec[..]) diff --git a/lightning-background-processor/Cargo.toml b/lightning-background-processor/Cargo.toml index bd6d54d8..16ec763f 100644 --- a/lightning-background-processor/Cargo.toml +++ b/lightning-background-processor/Cargo.toml @@ -16,8 +16,8 @@ rustdoc-args = ["--cfg", "docsrs"] [dependencies] bitcoin = "0.27" lightning = { version = "0.0.106", path = "../lightning", features = ["std"] } -lightning-persister = { version = "0.0.106", path = "../lightning-persister" } [dev-dependencies] lightning = { version = "0.0.106", path = "../lightning", features = ["_test_utils"] } lightning-invoice = { version = "0.14.0", path = "../lightning-invoice" } +lightning-persister = { version = "0.0.106", path = "../lightning-persister" } diff --git a/lightning-background-processor/src/lib.rs b/lightning-background-processor/src/lib.rs index 73f420c9..e23737c0 100644 --- a/lightning-background-processor/src/lib.rs +++ b/lightning-background-processor/src/lib.rs @@ -20,6 +20,7 @@ use lightning::ln::peer_handler::{CustomMessageHandler, PeerManager, SocketDescr use lightning::routing::network_graph::{NetworkGraph, NetGraphMsgHandler}; use lightning::util::events::{Event, EventHandler, EventsProvider}; use lightning::util::logger::Logger; +use lightning::util::persist::Persister; use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; use std::thread; @@ -80,22 +81,6 @@ const FIRST_NETWORK_PRUNE_TIMER: u64 = 60; #[cfg(test)] const FIRST_NETWORK_PRUNE_TIMER: u64 = 1; -/// Trait that handles persisting a [`ChannelManager`] and [`NetworkGraph`] to disk. -pub trait Persister -where - M::Target: 'static + chain::Watch, - T::Target: 'static + BroadcasterInterface, - K::Target: 'static + KeysInterface, - F::Target: 'static + FeeEstimator, - L::Target: 'static + Logger, -{ - /// Persist the given [`ChannelManager`] to disk, returning an error if persistence failed - /// (which will cause the [`BackgroundProcessor`] which called this method to exit). - fn persist_manager(&self, channel_manager: &ChannelManager) -> Result<(), std::io::Error>; - - /// Persist the given [`NetworkGraph`] to disk, returning an error if persistence failed. - fn persist_graph(&self, network_graph: &NetworkGraph) -> Result<(), std::io::Error>; -} /// Decorates an [`EventHandler`] with common functionality provided by standard [`EventHandler`]s. struct DecoratingEventHandler< @@ -138,12 +123,12 @@ impl BackgroundProcessor { /// /// [`Persister::persist_manager`] is responsible for writing out the [`ChannelManager`] to disk, and/or /// uploading to one or more backup services. See [`ChannelManager::write`] for writing out a - /// [`ChannelManager`]. See [`FilesystemPersister::persist_manager`] for Rust-Lightning's + /// [`ChannelManager`]. See the `lightning-persister` crate for LDK's /// provided implementation. /// /// [`Persister::persist_graph`] is responsible for writing out the [`NetworkGraph`] to disk. See - /// [`NetworkGraph::write`] for writing out a [`NetworkGraph`]. See [`FilesystemPersister::persist_network_graph`] - /// for Rust-Lightning's provided implementation. + /// [`NetworkGraph::write`] for writing out a [`NetworkGraph`]. See the `lightning-persister` crate + /// for LDK's provided implementation. /// /// Typically, users should either implement [`Persister::persist_manager`] to never return an /// error or call [`join`] and handle any error that may arise. For the latter case, @@ -161,8 +146,8 @@ impl BackgroundProcessor { /// [`stop`]: Self::stop /// [`ChannelManager`]: lightning::ln::channelmanager::ChannelManager /// [`ChannelManager::write`]: lightning::ln::channelmanager::ChannelManager#impl-Writeable - /// [`FilesystemPersister::persist_manager`]: lightning_persister::FilesystemPersister::persist_manager - /// [`FilesystemPersister::persist_network_graph`]: lightning_persister::FilesystemPersister::persist_network_graph + /// [`Persister::persist_manager`]: lightning::util::persist::Persister::persist_manager + /// [`Persister::persist_graph`]: lightning::util::persist::Persister::persist_graph /// [`NetworkGraph`]: lightning::routing::network_graph::NetworkGraph /// [`NetworkGraph::write`]: lightning::routing::network_graph::NetworkGraph#impl-Writeable pub fn start< @@ -180,7 +165,7 @@ impl BackgroundProcessor { CMH: 'static + Deref + Send + Sync, RMH: 'static + Deref + Send + Sync, EH: 'static + EventHandler + Send, - PS: 'static + Send + Persister, + PS: 'static + Deref + Send, M: 'static + Deref> + Send + Sync, CM: 'static + Deref> + Send + Sync, NG: 'static + Deref> + Send + Sync, @@ -202,6 +187,7 @@ impl BackgroundProcessor { CMH::Target: 'static + ChannelMessageHandler, RMH::Target: 'static + RoutingMessageHandler, UMH::Target: 'static + CustomMessageHandler, + PS::Target: 'static + Persister { let stop_thread = Arc::new(AtomicBool::new(false)); let stop_thread_clone = stop_thread.clone(); @@ -217,10 +203,22 @@ impl BackgroundProcessor { let mut have_pruned = false; loop { - peer_manager.process_events(); // Note that this may block on ChannelManager's locking channel_manager.process_pending_events(&event_handler); chain_monitor.process_pending_events(&event_handler); + // Note that the PeerManager::process_events may block on ChannelManager's locks, + // hence it comes last here. When the ChannelManager finishes whatever it's doing, + // we want to ensure we get into `persist_manager` as quickly as we can, especially + // without running the normal event processing above and handing events to users. + // + // Specifically, on an *extremely* slow machine, we may see ChannelManager start + // processing a message effectively at any point during this loop. In order to + // minimize the time between such processing completing and persisting the updated + // ChannelManager, we want to minimize methods blocking on a ChannelManager + // generally, and as a fallback place such blocking only immediately before + // persistence. + peer_manager.process_events(); + // We wait up to 100ms, but track how long it takes to detect being put to sleep, // see `await_start`'s use below. let await_start = Instant::now(); @@ -349,10 +347,9 @@ mod tests { use bitcoin::blockdata::constants::genesis_block; use bitcoin::blockdata::transaction::{Transaction, TxOut}; use bitcoin::network::constants::Network; - use lightning::chain::chaininterface::{BroadcasterInterface, FeeEstimator}; - use lightning::chain::{BestBlock, Confirm, chainmonitor, self}; + use lightning::chain::{BestBlock, Confirm, chainmonitor}; use lightning::chain::channelmonitor::ANTI_REORG_DELAY; - use lightning::chain::keysinterface::{InMemorySigner, Recipient, KeysInterface, KeysManager, Sign}; + use lightning::chain::keysinterface::{InMemorySigner, Recipient, KeysInterface, KeysManager}; use lightning::chain::transaction::OutPoint; use lightning::get_event_msg; use lightning::ln::channelmanager::{BREAKDOWN_TIMEOUT, ChainParameters, ChannelManager, SimpleArcChannelManager}; @@ -362,14 +359,13 @@ mod tests { use lightning::routing::network_graph::{NetworkGraph, NetGraphMsgHandler}; use lightning::util::config::UserConfig; use lightning::util::events::{Event, MessageSendEventsProvider, MessageSendEvent}; - use lightning::util::logger::Logger; use lightning::util::ser::Writeable; use lightning::util::test_utils; + use lightning::util::persist::KVStorePersister; use lightning_invoice::payment::{InvoicePayer, RetryAttempts}; use lightning_invoice::utils::DefaultRouter; use lightning_persister::FilesystemPersister; use std::fs; - use std::ops::Deref; use std::path::PathBuf; use std::sync::{Arc, Mutex}; use std::time::Duration; @@ -412,14 +408,15 @@ mod tests { } struct Persister { - data_dir: String, graph_error: Option<(std::io::ErrorKind, &'static str)>, - manager_error: Option<(std::io::ErrorKind, &'static str)> + manager_error: Option<(std::io::ErrorKind, &'static str)>, + filesystem_persister: FilesystemPersister, } impl Persister { fn new(data_dir: String) -> Self { - Self { data_dir, graph_error: None, manager_error: None } + let filesystem_persister = FilesystemPersister::new(data_dir.clone()); + Self { graph_error: None, manager_error: None, filesystem_persister } } fn with_graph_error(self, error: std::io::ErrorKind, message: &'static str) -> Self { @@ -431,25 +428,21 @@ mod tests { } } - impl super::Persister for Persister where - M::Target: 'static + chain::Watch, - T::Target: 'static + BroadcasterInterface, - K::Target: 'static + KeysInterface, - F::Target: 'static + FeeEstimator, - L::Target: 'static + Logger, - { - fn persist_manager(&self, channel_manager: &ChannelManager) -> Result<(), std::io::Error> { - match self.manager_error { - None => FilesystemPersister::persist_manager(self.data_dir.clone(), channel_manager), - Some((error, message)) => Err(std::io::Error::new(error, message)), + impl KVStorePersister for Persister { + fn persist(&self, key: &str, object: &W) -> std::io::Result<()> { + if key == "manager" { + if let Some((error, message)) = self.manager_error { + return Err(std::io::Error::new(error, message)) + } } - } - fn persist_graph(&self, network_graph: &NetworkGraph) -> Result<(), std::io::Error> { - match self.graph_error { - None => FilesystemPersister::persist_network_graph(self.data_dir.clone(), network_graph), - Some((error, message)) => Err(std::io::Error::new(error, message)), + if key == "network_graph" { + if let Some((error, message)) = self.graph_error { + return Err(std::io::Error::new(error, message)) + } } + + self.filesystem_persister.persist(key, object) } } @@ -576,7 +569,7 @@ mod tests { // Initiate the background processors to watch each node. let data_dir = nodes[0].persister.get_data_dir(); - let persister = Persister::new(data_dir); + let persister = Arc::new(Persister::new(data_dir)); let event_handler = |_: &_| {}; let bg_processor = BackgroundProcessor::start(persister, event_handler, nodes[0].chain_monitor.clone(), nodes[0].node.clone(), nodes[0].net_graph_msg_handler.clone(), nodes[0].peer_manager.clone(), nodes[0].logger.clone()); @@ -637,7 +630,7 @@ mod tests { // `FRESHNESS_TIMER`. let nodes = create_nodes(1, "test_timer_tick_called".to_string()); let data_dir = nodes[0].persister.get_data_dir(); - let persister = Persister::new(data_dir); + let persister = Arc::new(Persister::new(data_dir)); let event_handler = |_: &_| {}; let bg_processor = BackgroundProcessor::start(persister, event_handler, nodes[0].chain_monitor.clone(), nodes[0].node.clone(), nodes[0].net_graph_msg_handler.clone(), nodes[0].peer_manager.clone(), nodes[0].logger.clone()); loop { @@ -660,7 +653,7 @@ mod tests { open_channel!(nodes[0], nodes[1], 100000); let data_dir = nodes[0].persister.get_data_dir(); - let persister = Persister::new(data_dir).with_manager_error(std::io::ErrorKind::Other, "test"); + let persister = Arc::new(Persister::new(data_dir).with_manager_error(std::io::ErrorKind::Other, "test")); let event_handler = |_: &_| {}; let bg_processor = BackgroundProcessor::start(persister, event_handler, nodes[0].chain_monitor.clone(), nodes[0].node.clone(), nodes[0].net_graph_msg_handler.clone(), nodes[0].peer_manager.clone(), nodes[0].logger.clone()); match bg_processor.join() { @@ -677,7 +670,7 @@ mod tests { // Test that if we encounter an error during network graph persistence, an error gets returned. let nodes = create_nodes(2, "test_persist_network_graph_error".to_string()); let data_dir = nodes[0].persister.get_data_dir(); - let persister = Persister::new(data_dir).with_graph_error(std::io::ErrorKind::Other, "test"); + let persister = Arc::new(Persister::new(data_dir).with_graph_error(std::io::ErrorKind::Other, "test")); let event_handler = |_: &_| {}; let bg_processor = BackgroundProcessor::start(persister, event_handler, nodes[0].chain_monitor.clone(), nodes[0].node.clone(), nodes[0].net_graph_msg_handler.clone(), nodes[0].peer_manager.clone(), nodes[0].logger.clone()); @@ -695,7 +688,7 @@ mod tests { let mut nodes = create_nodes(2, "test_background_event_handling".to_string()); let channel_value = 100000; let data_dir = nodes[0].persister.get_data_dir(); - let persister = Persister::new(data_dir.clone()); + let persister = Arc::new(Persister::new(data_dir.clone())); // Set up a background event handler for FundingGenerationReady events. let (sender, receiver) = std::sync::mpsc::sync_channel(1); @@ -726,7 +719,8 @@ mod tests { // Set up a background event handler for SpendableOutputs events. let (sender, receiver) = std::sync::mpsc::sync_channel(1); let event_handler = move |event: &Event| sender.send(event.clone()).unwrap(); - let bg_processor = BackgroundProcessor::start(Persister::new(data_dir), event_handler, nodes[0].chain_monitor.clone(), nodes[0].node.clone(), nodes[0].net_graph_msg_handler.clone(), nodes[0].peer_manager.clone(), nodes[0].logger.clone()); + let persister = Arc::new(Persister::new(data_dir)); + let bg_processor = BackgroundProcessor::start(persister, event_handler, nodes[0].chain_monitor.clone(), nodes[0].node.clone(), nodes[0].net_graph_msg_handler.clone(), nodes[0].peer_manager.clone(), nodes[0].logger.clone()); // Force close the channel and check that the SpendableOutputs event was handled. nodes[0].node.force_close_channel(&nodes[0].node.list_channels()[0].channel_id).unwrap(); @@ -752,7 +746,7 @@ mod tests { // Initiate the background processors to watch each node. let data_dir = nodes[0].persister.get_data_dir(); - let persister = Persister::new(data_dir); + let persister = Arc::new(Persister::new(data_dir)); let scorer = Arc::new(Mutex::new(test_utils::TestScorer::with_penalty(0))); let router = DefaultRouter::new(Arc::clone(&nodes[0].network_graph), Arc::clone(&nodes[0].logger), random_seed_bytes); let invoice_payer = Arc::new(InvoicePayer::new(Arc::clone(&nodes[0].node), router, scorer, Arc::clone(&nodes[0].logger), |_: &_| {}, RetryAttempts(2))); diff --git a/lightning-block-sync/src/init.rs b/lightning-block-sync/src/init.rs index f5d839d2..b3f745bd 100644 --- a/lightning-block-sync/src/init.rs +++ b/lightning-block-sync/src/init.rs @@ -4,7 +4,7 @@ use crate::{BlockSource, BlockSourceResult, Cache, ChainNotifier}; use crate::poll::{ChainPoller, Validate, ValidatedBlockHeader}; -use bitcoin::blockdata::block::{Block, BlockHeader}; +use bitcoin::blockdata::block::BlockHeader; use bitcoin::hash_types::BlockHash; use bitcoin::network::constants::Network; @@ -203,7 +203,7 @@ impl<'a, C: Cache> Cache for ReadOnlyCache<'a, C> { struct DynamicChainListener<'a, L: chain::Listen + ?Sized>(&'a L); impl<'a, L: chain::Listen + ?Sized> chain::Listen for DynamicChainListener<'a, L> { - fn block_connected(&self, _block: &Block, _height: u32) { + fn filtered_block_connected(&self, _header: &BlockHeader, _txdata: &chain::transaction::TransactionData, _height: u32) { unreachable!() } @@ -216,10 +216,10 @@ impl<'a, L: chain::Listen + ?Sized> chain::Listen for DynamicChainListener<'a, L struct ChainListenerSet<'a, L: chain::Listen + ?Sized>(Vec<(u32, &'a L)>); impl<'a, L: chain::Listen + ?Sized> chain::Listen for ChainListenerSet<'a, L> { - fn block_connected(&self, block: &Block, height: u32) { + fn filtered_block_connected(&self, header: &BlockHeader, txdata: &chain::transaction::TransactionData, height: u32) { for (starting_height, chain_listener) in self.0.iter() { if height > *starting_height { - chain_listener.block_connected(block, height); + chain_listener.filtered_block_connected(header, txdata, height); } } } diff --git a/lightning-block-sync/src/test_utils.rs b/lightning-block-sync/src/test_utils.rs index fe57c0c6..c101d4bd 100644 --- a/lightning-block-sync/src/test_utils.rs +++ b/lightning-block-sync/src/test_utils.rs @@ -166,7 +166,7 @@ impl BlockSource for Blockchain { pub struct NullChainListener; impl chain::Listen for NullChainListener { - fn block_connected(&self, _block: &Block, _height: u32) {} + fn filtered_block_connected(&self, _header: &BlockHeader, _txdata: &chain::transaction::TransactionData, _height: u32) {} fn block_disconnected(&self, _header: &BlockHeader, _height: u32) {} } @@ -195,13 +195,13 @@ impl MockChainListener { } impl chain::Listen for MockChainListener { - fn block_connected(&self, block: &Block, height: u32) { + fn filtered_block_connected(&self, header: &BlockHeader, _txdata: &chain::transaction::TransactionData, height: u32) { match self.expected_blocks_connected.borrow_mut().pop_front() { None => { - panic!("Unexpected block connected: {:?}", block.block_hash()); + panic!("Unexpected block connected: {:?}", header.block_hash()); }, Some(expected_block) => { - assert_eq!(block.block_hash(), expected_block.header.block_hash()); + assert_eq!(header.block_hash(), expected_block.header.block_hash()); assert_eq!(height, expected_block.height); }, } diff --git a/lightning-invoice/src/utils.rs b/lightning-invoice/src/utils.rs index f6affc4d..a2edc43a 100644 --- a/lightning-invoice/src/utils.rs +++ b/lightning-invoice/src/utils.rs @@ -13,6 +13,7 @@ use lightning::ln::{PaymentHash, PaymentPreimage, PaymentSecret}; use lightning::ln::channelmanager::{ChannelDetails, ChannelManager, PaymentId, PaymentSendFailure, MIN_FINAL_CLTV_EXPIRY}; #[cfg(feature = "std")] use lightning::ln::channelmanager::{PhantomRouteHints, MIN_CLTV_EXPIRY_DELTA}; +use lightning::ln::inbound_payment::{create, create_from_hash, ExpandedKey}; use lightning::ln::msgs::LightningError; use lightning::routing::scoring::Score; use lightning::routing::network_graph::{NetworkGraph, RoutingFees}; @@ -38,11 +39,12 @@ use sync::Mutex; /// may be too long for QR code scanning. To fix this, `PhantomRouteHints::channels` may be pared /// down /// -/// `payment_hash` and `payment_secret` can come from [`ChannelManager::create_inbound_payment`] or -/// [`ChannelManager::create_inbound_payment_for_hash`]. These values can be retrieved from any -/// participating node. Alternatively, [`inbound_payment::create`] or -/// [`inbound_payment::create_from_hash`] may be used to retrieve these values without a -/// `ChannelManager`. +/// `payment_hash` can be specified if you have a specific need for a custom payment hash (see the difference +/// between [`ChannelManager::create_inbound_payment`] and [`ChannelManager::create_inbound_payment_for_hash`]). +/// If `None` is provided for `payment_hash`, then one will be created. +/// +/// `invoice_expiry_delta_secs` describes the number of seconds that the invoice is valid for +/// in excess of the current time. /// /// Note that the provided `keys_manager`'s `KeysInterface` implementation must support phantom /// invoices in its `sign_invoice` implementation ([`PhantomKeysManager`] satisfies this @@ -50,17 +52,17 @@ use sync::Mutex; /// /// [`PhantomKeysManager`]: lightning::chain::keysinterface::PhantomKeysManager /// [`ChannelManager::get_phantom_route_hints`]: lightning::ln::channelmanager::ChannelManager::get_phantom_route_hints -/// [`inbound_payment::create`]: lightning::ln::inbound_payment::create -/// [`inbound_payment::create_from_hash`]: lightning::ln::inbound_payment::create_from_hash +/// [`ChannelManager::create_inbound_payment`]: lightning::ln::channelmanager::ChannelManager::create_inbound_payment +/// [`ChannelManager::create_inbound_payment_for_hash`]: lightning::ln::channelmanager::ChannelManager::create_inbound_payment_for_hash /// [`PhantomRouteHints::channels`]: lightning::ln::channelmanager::PhantomRouteHints::channels pub fn create_phantom_invoice( - amt_msat: Option, description: String, payment_hash: PaymentHash, payment_secret: PaymentSecret, + amt_msat: Option, payment_hash: Option, description: String, invoice_expiry_delta_secs: u32, phantom_route_hints: Vec, keys_manager: K, network: Currency, ) -> Result> where K::Target: KeysInterface { let description = Description::new(description).map_err(SignOrCreationError::CreationError)?; let description = InvoiceDescription::Direct(&description,); _create_phantom_invoice::( - amt_msat, description, payment_hash, payment_secret, phantom_route_hints, keys_manager, network, + amt_msat, payment_hash, description, invoice_expiry_delta_secs, phantom_route_hints, keys_manager, network, ) } @@ -80,11 +82,12 @@ pub fn create_phantom_invoice( /// /// `description_hash` is a SHA-256 hash of the description text /// -/// `payment_hash` and `payment_secret` can come from [`ChannelManager::create_inbound_payment`] or -/// [`ChannelManager::create_inbound_payment_for_hash`]. These values can be retrieved from any -/// participating node. Alternatively, [`inbound_payment::create`] or -/// [`inbound_payment::create_from_hash`] may be used to retrieve these values without a -/// `ChannelManager`. +/// `payment_hash` can be specified if you have a specific need for a custom payment hash (see the difference +/// between [`ChannelManager::create_inbound_payment`] and [`ChannelManager::create_inbound_payment_for_hash`]). +/// If `None` is provided for `payment_hash`, then one will be created. +/// +/// `invoice_expiry_delta_secs` describes the number of seconds that the invoice is valid for +/// in excess of the current time. /// /// Note that the provided `keys_manager`'s `KeysInterface` implementation must support phantom /// invoices in its `sign_invoice` implementation ([`PhantomKeysManager`] satisfies this @@ -92,30 +95,28 @@ pub fn create_phantom_invoice( /// /// [`PhantomKeysManager`]: lightning::chain::keysinterface::PhantomKeysManager /// [`ChannelManager::get_phantom_route_hints`]: lightning::ln::channelmanager::ChannelManager::get_phantom_route_hints -/// [`inbound_payment::create`]: lightning::ln::inbound_payment::create -/// [`inbound_payment::create_from_hash`]: lightning::ln::inbound_payment::create_from_hash +/// [`ChannelManager::create_inbound_payment`]: lightning::ln::channelmanager::ChannelManager::create_inbound_payment +/// [`ChannelManager::create_inbound_payment_for_hash`]: lightning::ln::channelmanager::ChannelManager::create_inbound_payment_for_hash /// [`PhantomRouteHints::channels`]: lightning::ln::channelmanager::PhantomRouteHints::channels pub fn create_phantom_invoice_with_description_hash( - amt_msat: Option, description_hash: Sha256, payment_hash: PaymentHash, - payment_secret: PaymentSecret, phantom_route_hints: Vec, - keys_manager: K, network: Currency, + amt_msat: Option, payment_hash: Option, invoice_expiry_delta_secs: u32, + description_hash: Sha256, phantom_route_hints: Vec, keys_manager: K, network: Currency, ) -> Result> where K::Target: KeysInterface { - _create_phantom_invoice::( - amt_msat, - InvoiceDescription::Hash(&description_hash), - payment_hash, payment_secret, phantom_route_hints, keys_manager, network, + amt_msat, payment_hash, InvoiceDescription::Hash(&description_hash), + invoice_expiry_delta_secs, phantom_route_hints, keys_manager, network, ) } #[cfg(feature = "std")] fn _create_phantom_invoice( - amt_msat: Option, description: InvoiceDescription, payment_hash: PaymentHash, - payment_secret: PaymentSecret, phantom_route_hints: Vec, - keys_manager: K, network: Currency, + amt_msat: Option, payment_hash: Option, description: InvoiceDescription, + invoice_expiry_delta_secs: u32, phantom_route_hints: Vec, keys_manager: K, network: Currency, ) -> Result> where K::Target: KeysInterface { + use std::time::{SystemTime, UNIX_EPOCH}; + if phantom_route_hints.len() == 0 { return Err(SignOrCreationError::CreationError( CreationError::MissingRouteHints, @@ -128,6 +129,35 @@ fn _create_phantom_invoice( InvoiceDescription::Hash(hash) => InvoiceBuilder::new(network).description_hash(hash.0), }; + // If we ever see performance here being too slow then we should probably take this ExpandedKey as a parameter instead. + let keys = ExpandedKey::new(&keys_manager.get_inbound_payment_key_material()); + let (payment_hash, payment_secret) = if let Some(payment_hash) = payment_hash { + let payment_secret = create_from_hash( + &keys, + amt_msat, + payment_hash, + invoice_expiry_delta_secs, + SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time must be > 1970") + .as_secs(), + ) + .map_err(|_| SignOrCreationError::CreationError(CreationError::InvalidAmount))?; + (payment_hash, payment_secret) + } else { + create( + &keys, + amt_msat, + invoice_expiry_delta_secs, + &keys_manager, + SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time must be > 1970") + .as_secs(), + ) + .map_err(|_| SignOrCreationError::CreationError(CreationError::InvalidAmount))? + }; + let mut invoice = invoice .current_timestamp() .payment_hash(Hash::from_slice(&payment_hash.0).unwrap()) @@ -382,8 +412,8 @@ fn filter_channels(channels: Vec, min_inbound_capacity_msat: Opt proportional_millionths: forwarding_info.fee_proportional_millionths, }, cltv_expiry_delta: forwarding_info.cltv_expiry_delta, - htlc_minimum_msat: None, - htlc_maximum_msat: None,}]) + htlc_minimum_msat: channel.inbound_htlc_minimum_msat, + htlc_maximum_msat: channel.inbound_htlc_maximum_msat,}]) }; // If all channels are private, return the route hint for the highest inbound capacity channel // per counterparty node. If channels with an higher inbound capacity than the @@ -505,10 +535,13 @@ mod test { // Invoice SCIDs should always use inbound SCID aliases over the real channel ID, if one is // available. + let chan = &nodes[1].node.list_usable_channels()[0]; assert_eq!(invoice.route_hints().len(), 1); assert_eq!(invoice.route_hints()[0].0.len(), 1); - assert_eq!(invoice.route_hints()[0].0[0].short_channel_id, - nodes[1].node.list_usable_channels()[0].inbound_scid_alias.unwrap()); + assert_eq!(invoice.route_hints()[0].0[0].short_channel_id, chan.inbound_scid_alias.unwrap()); + + assert_eq!(invoice.route_hints()[0].0[0].htlc_minimum_msat, chan.inbound_htlc_minimum_msat); + assert_eq!(invoice.route_hints()[0].0[0].htlc_maximum_msat, chan.inbound_htlc_maximum_msat); let payment_params = PaymentParameters::from_node_id(invoice.recover_payee_pub_key()) .with_features(invoice.features().unwrap().clone()) @@ -755,23 +788,25 @@ mod test { nodes[2].node.handle_channel_update(&nodes[0].node.get_our_node_id(), &chan_0_2.0); let payment_amt = 10_000; - let (payment_preimage, payment_hash, payment_secret) = { - if user_generated_pmt_hash { - let payment_preimage = PaymentPreimage([1; 32]); - let payment_hash = PaymentHash(Sha256::hash(&payment_preimage.0[..]).into_inner()); - let payment_secret = nodes[1].node.create_inbound_payment_for_hash(payment_hash, Some(payment_amt), 3600).unwrap(); - (payment_preimage, payment_hash, payment_secret) - } else { - let (payment_hash, payment_secret) = nodes[1].node.create_inbound_payment(Some(payment_amt), 3600).unwrap(); - let payment_preimage = nodes[1].node.get_payment_preimage(payment_hash, payment_secret).unwrap(); - (payment_preimage, payment_hash, payment_secret) - } - }; let route_hints = vec![ nodes[1].node.get_phantom_route_hints(), nodes[2].node.get_phantom_route_hints(), ]; - let invoice = ::utils::create_phantom_invoice::(Some(payment_amt), "test".to_string(), payment_hash, payment_secret, route_hints, &nodes[1].keys_manager, Currency::BitcoinTestnet).unwrap(); + + let user_payment_preimage = PaymentPreimage([1; 32]); + let payment_hash = if user_generated_pmt_hash { + Some(PaymentHash(Sha256::hash(&user_payment_preimage.0[..]).into_inner())) + } else { + None + }; + + let invoice = ::utils::create_phantom_invoice::(Some(payment_amt), payment_hash, "test".to_string(), 3600, route_hints, &nodes[1].keys_manager, Currency::BitcoinTestnet).unwrap(); + let (payment_hash, payment_secret) = (PaymentHash(invoice.payment_hash().into_inner()), *invoice.payment_secret()); + let payment_preimage = if user_generated_pmt_hash { + user_payment_preimage + } else { + nodes[1].node.get_payment_preimage(payment_hash, payment_secret).unwrap() + }; assert_eq!(invoice.min_final_cltv_expiry(), MIN_FINAL_CLTV_EXPIRY as u64); assert_eq!(invoice.description(), InvoiceDescription::Direct(&Description("test".to_string()))); @@ -847,6 +882,40 @@ mod test { } } + #[test] + #[cfg(feature = "std")] + fn test_multi_node_hints_has_htlc_min_max_values() { + let mut chanmon_cfgs = create_chanmon_cfgs(3); + let seed_1 = [42 as u8; 32]; + let seed_2 = [43 as u8; 32]; + let cross_node_seed = [44 as u8; 32]; + chanmon_cfgs[1].keys_manager.backing = PhantomKeysManager::new(&seed_1, 43, 44, &cross_node_seed); + chanmon_cfgs[2].keys_manager.backing = PhantomKeysManager::new(&seed_2, 43, 44, &cross_node_seed); + let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]); + let nodes = create_network(3, &node_cfgs, &node_chanmgrs); + + create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 100000, 10001, InitFeatures::known(), InitFeatures::known()); + create_unannounced_chan_between_nodes_with_value(&nodes, 0, 2, 100000, 10001, InitFeatures::known(), InitFeatures::known()); + + let payment_amt = 20_000; + let (payment_hash, _payment_secret) = nodes[1].node.create_inbound_payment(Some(payment_amt), 3600).unwrap(); + let route_hints = vec![ + nodes[1].node.get_phantom_route_hints(), + nodes[2].node.get_phantom_route_hints(), + ]; + + let invoice = ::utils::create_phantom_invoice::(Some(payment_amt), Some(payment_hash), "test".to_string(), 3600, route_hints, &nodes[1].keys_manager, Currency::BitcoinTestnet).unwrap(); + + let chan_0_1 = &nodes[1].node.list_usable_channels()[0]; + assert_eq!(invoice.route_hints()[0].0[0].htlc_minimum_msat, chan_0_1.inbound_htlc_minimum_msat); + assert_eq!(invoice.route_hints()[0].0[0].htlc_maximum_msat, chan_0_1.inbound_htlc_maximum_msat); + + let chan_0_2 = &nodes[2].node.list_usable_channels()[0]; + assert_eq!(invoice.route_hints()[1].0[0].htlc_minimum_msat, chan_0_2.inbound_htlc_minimum_msat); + assert_eq!(invoice.route_hints()[1].0[0].htlc_maximum_msat, chan_0_2.inbound_htlc_maximum_msat); + } + #[test] #[cfg(feature = "std")] fn create_phantom_invoice_with_description_hash() { @@ -856,14 +925,13 @@ mod test { let nodes = create_network(3, &node_cfgs, &node_chanmgrs); let payment_amt = 20_000; - let (payment_hash, payment_secret) = nodes[1].node.create_inbound_payment(Some(payment_amt), 3600).unwrap(); let route_hints = vec![ nodes[1].node.get_phantom_route_hints(), nodes[2].node.get_phantom_route_hints(), ]; let description_hash = crate::Sha256(Hash::hash("Description hash phantom invoice".as_bytes())); - let invoice = ::utils::create_phantom_invoice_with_description_hash::(Some(payment_amt), description_hash, payment_hash, payment_secret, route_hints, &nodes[1].keys_manager, Currency::BitcoinTestnet).unwrap(); + let invoice = ::utils::create_phantom_invoice_with_description_hash::(Some(payment_amt), None, 3600, description_hash, route_hints, &nodes[1].keys_manager, Currency::BitcoinTestnet).unwrap(); assert_eq!(invoice.amount_pico_btc(), Some(200_000)); assert_eq!(invoice.min_final_cltv_expiry(), MIN_FINAL_CLTV_EXPIRY as u64); @@ -1166,7 +1234,6 @@ mod test { mut chan_ids_to_match: HashSet, nodes_contains_public_channels: bool ){ - let (payment_hash, payment_secret) = invoice_node.node.create_inbound_payment(invoice_amt, 3600).unwrap(); let phantom_route_hints = network_multi_nodes.iter() .map(|node| node.node.get_phantom_route_hints()) .collect::>(); @@ -1174,7 +1241,7 @@ mod test { .map(|route_hint| route_hint.phantom_scid) .collect::>(); - let invoice = ::utils::create_phantom_invoice::(invoice_amt, "test".to_string(), payment_hash, payment_secret, phantom_route_hints, &invoice_node.keys_manager, Currency::BitcoinTestnet).unwrap(); + let invoice = ::utils::create_phantom_invoice::(invoice_amt, None, "test".to_string(), 3600, phantom_route_hints, &invoice_node.keys_manager, Currency::BitcoinTestnet).unwrap(); let invoice_hints = invoice.private_routes(); diff --git a/lightning-persister/src/lib.rs b/lightning-persister/src/lib.rs index 45006212..c23baf8a 100644 --- a/lightning-persister/src/lib.rs +++ b/lightning-persister/src/lib.rs @@ -15,20 +15,13 @@ extern crate bitcoin; extern crate libc; use bitcoin::hash_types::{BlockHash, Txid}; -use bitcoin::hashes::hex::{FromHex, ToHex}; -use lightning::routing::network_graph::NetworkGraph; -use crate::util::DiskWriteable; -use lightning::chain; -use lightning::chain::chaininterface::{BroadcasterInterface, FeeEstimator}; -use lightning::chain::channelmonitor::{ChannelMonitor, ChannelMonitorUpdate}; -use lightning::chain::chainmonitor; +use bitcoin::hashes::hex::FromHex; +use lightning::chain::channelmonitor::ChannelMonitor; use lightning::chain::keysinterface::{Sign, KeysInterface}; -use lightning::chain::transaction::OutPoint; -use lightning::ln::channelmanager::ChannelManager; -use lightning::util::logger::Logger; use lightning::util::ser::{ReadableArgs, Writeable}; +use lightning::util::persist::KVStorePersister; use std::fs; -use std::io::{Cursor, Error, Write}; +use std::io::Cursor; use std::ops::Deref; use std::path::{Path, PathBuf}; @@ -48,31 +41,6 @@ pub struct FilesystemPersister { path_to_channel_data: String, } -impl DiskWriteable for ChannelMonitor { - fn write_to_file(&self, writer: &mut W) -> Result<(), Error> { - self.write(writer) - } -} - -impl DiskWriteable for ChannelManager -where - M::Target: chain::Watch, - T::Target: BroadcasterInterface, - K::Target: KeysInterface, - F::Target: FeeEstimator, - L::Target: Logger, -{ - fn write_to_file(&self, writer: &mut W) -> Result<(), std::io::Error> { - self.write(writer) - } -} - -impl DiskWriteable for NetworkGraph { - fn write_to_file(&self, writer: &mut W) -> Result<(), std::io::Error> { - self.write(writer) - } -} - impl FilesystemPersister { /// Initialize a new FilesystemPersister and set the path to the individual channels' /// files. @@ -87,43 +55,14 @@ impl FilesystemPersister { self.path_to_channel_data.clone() } - pub(crate) fn path_to_monitor_data(&self) -> PathBuf { - let mut path = PathBuf::from(self.path_to_channel_data.clone()); - path.push("monitors"); - path - } - - /// Writes the provided `ChannelManager` to the path provided at `FilesystemPersister` - /// initialization, within a file called "manager". - pub fn persist_manager( - data_dir: String, - manager: &ChannelManager - ) -> Result<(), std::io::Error> - where - M::Target: chain::Watch, - T::Target: BroadcasterInterface, - K::Target: KeysInterface, - F::Target: FeeEstimator, - L::Target: Logger, - { - let path = PathBuf::from(data_dir); - util::write_to_file(path, "manager".to_string(), manager) - } - - /// Write the provided `NetworkGraph` to the path provided at `FilesystemPersister` - /// initialization, within a file called "network_graph" - pub fn persist_network_graph(data_dir: String, network_graph: &NetworkGraph) -> Result<(), std::io::Error> { - let path = PathBuf::from(data_dir); - util::write_to_file(path, "network_graph".to_string(), network_graph) - } - /// Read `ChannelMonitor`s from disk. pub fn read_channelmonitors ( &self, keys_manager: K ) -> Result)>, std::io::Error> where K::Target: KeysInterface + Sized, { - let path = self.path_to_monitor_data(); + let mut path = PathBuf::from(&self.path_to_channel_data); + path.push("monitors"); if !Path::new(&path).exists() { return Ok(Vec::new()); } @@ -180,22 +119,11 @@ impl FilesystemPersister { } } -impl chainmonitor::Persist for FilesystemPersister { - // TODO: We really need a way for the persister to inform the user that its time to crash/shut - // down once these start returning failure. - // A PermanentFailure implies we need to shut down since we're force-closing channels without - // even broadcasting! - - fn persist_new_channel(&self, funding_txo: OutPoint, monitor: &ChannelMonitor, _update_id: chainmonitor::MonitorUpdateId) -> Result<(), chain::ChannelMonitorUpdateErr> { - let filename = format!("{}_{}", funding_txo.txid.to_hex(), funding_txo.index); - util::write_to_file(self.path_to_monitor_data(), filename, monitor) - .map_err(|_| chain::ChannelMonitorUpdateErr::PermanentFailure) - } - - fn update_persisted_channel(&self, funding_txo: OutPoint, _update: &Option, monitor: &ChannelMonitor, _update_id: chainmonitor::MonitorUpdateId) -> Result<(), chain::ChannelMonitorUpdateErr> { - let filename = format!("{}_{}", funding_txo.txid.to_hex(), funding_txo.index); - util::write_to_file(self.path_to_monitor_data(), filename, monitor) - .map_err(|_| chain::ChannelMonitorUpdateErr::PermanentFailure) +impl KVStorePersister for FilesystemPersister { + fn persist(&self, key: &str, object: &W) -> std::io::Result<()> { + let mut dest_file = PathBuf::from(self.path_to_channel_data.clone()); + dest_file.push(key); + util::write_to_file(dest_file, object) } } diff --git a/lightning-persister/src/util.rs b/lightning-persister/src/util.rs index f2629679..4adbb33e 100644 --- a/lightning-persister/src/util.rs +++ b/lightning-persister/src/util.rs @@ -2,27 +2,20 @@ extern crate winapi; use std::fs; -use std::path::{Path, PathBuf}; -use std::io::{BufWriter, Write}; +use std::path::PathBuf; +use std::io::BufWriter; #[cfg(not(target_os = "windows"))] use std::os::unix::io::AsRawFd; +use lightning::util::ser::Writeable; + #[cfg(target_os = "windows")] use { std::ffi::OsStr, std::os::windows::ffi::OsStrExt }; -pub(crate) trait DiskWriteable { - fn write_to_file(&self, writer: &mut W) -> Result<(), std::io::Error>; -} - -pub(crate) fn get_full_filepath(mut filepath: PathBuf, filename: String) -> String { - filepath.push(filename); - filepath.to_str().unwrap().to_string() -} - #[cfg(target_os = "windows")] macro_rules! call { ($e: expr) => ( @@ -40,45 +33,43 @@ fn path_to_windows_str>(path: T) -> Vec(path: PathBuf, filename: String, data: &D) -> std::io::Result<()> { - fs::create_dir_all(path.clone())?; +pub(crate) fn write_to_file(dest_file: PathBuf, data: &W) -> std::io::Result<()> { + let mut tmp_file = dest_file.clone(); + tmp_file.set_extension("tmp"); + + let parent_directory = dest_file.parent().unwrap(); + fs::create_dir_all(parent_directory)?; // Do a crazy dance with lots of fsync()s to be overly cautious here... // We never want to end up in a state where we've lost the old data, or end up using the // old data on power loss after we've returned. // The way to atomically write a file on Unix platforms is: // open(tmpname), write(tmpfile), fsync(tmpfile), close(tmpfile), rename(), fsync(dir) - let filename_with_path = get_full_filepath(path, filename); - let tmp_filename = format!("{}.tmp", filename_with_path.clone()); - { // Note that going by rust-lang/rust@d602a6b, on MacOS it is only safe to use // rust stdlib 1.36 or higher. - let mut buf = BufWriter::new(fs::File::create(&tmp_filename)?); - data.write_to_file(&mut buf)?; + let mut buf = BufWriter::new(fs::File::create(&tmp_file)?); + data.write(&mut buf)?; buf.into_inner()?.sync_all()?; } // Fsync the parent directory on Unix. #[cfg(not(target_os = "windows"))] { - fs::rename(&tmp_filename, &filename_with_path)?; - let path = Path::new(&filename_with_path).parent().unwrap(); - let dir_file = fs::OpenOptions::new().read(true).open(path)?; + fs::rename(&tmp_file, &dest_file)?; + let dir_file = fs::OpenOptions::new().read(true).open(parent_directory)?; unsafe { libc::fsync(dir_file.as_raw_fd()); } } #[cfg(target_os = "windows")] { - let src = PathBuf::from(tmp_filename.clone()); - let dst = PathBuf::from(filename_with_path.clone()); - if Path::new(&filename_with_path.clone()).exists() { + if dest_file.exists() { unsafe {winapi::um::winbase::ReplaceFileW( - path_to_windows_str(dst).as_ptr(), path_to_windows_str(src).as_ptr(), std::ptr::null(), + path_to_windows_str(dest_file).as_ptr(), path_to_windows_str(tmp_file).as_ptr(), std::ptr::null(), winapi::um::winbase::REPLACEFILE_IGNORE_MERGE_ERRORS, std::ptr::null_mut() as *mut winapi::ctypes::c_void, std::ptr::null_mut() as *mut winapi::ctypes::c_void )}; } else { call!(unsafe {winapi::um::winbase::MoveFileExW( - path_to_windows_str(src).as_ptr(), path_to_windows_str(dst).as_ptr(), + path_to_windows_str(tmp_file).as_ptr(), path_to_windows_str(dest_file).as_ptr(), winapi::um::winbase::MOVEFILE_WRITE_THROUGH | winapi::um::winbase::MOVEFILE_REPLACE_EXISTING )}); } @@ -88,15 +79,16 @@ pub(crate) fn write_to_file(path: PathBuf, filename: String, d #[cfg(test)] mod tests { - use super::{DiskWriteable, get_full_filepath, write_to_file}; + use lightning::util::ser::{Writer, Writeable}; + + use super::{write_to_file}; use std::fs; use std::io; - use std::io::Write; use std::path::PathBuf; struct TestWriteable{} - impl DiskWriteable for TestWriteable { - fn write_to_file(&self, writer: &mut W) -> Result<(), io::Error> { + impl Writeable for TestWriteable { + fn write(&self, writer: &mut W) -> Result<(), std::io::Error> { writer.write_all(&[42; 1]) } } @@ -114,7 +106,9 @@ mod tests { let mut perms = fs::metadata(path.to_string()).unwrap().permissions(); perms.set_readonly(true); fs::set_permissions(path.to_string(), perms).unwrap(); - match write_to_file(PathBuf::from(path.to_string()), filename, &test_writeable) { + let mut dest_file = PathBuf::from(path); + dest_file.push(filename); + match write_to_file(dest_file, &test_writeable) { Err(e) => assert_eq!(e.kind(), io::ErrorKind::PermissionDenied), _ => panic!("Unexpected error message") } @@ -132,10 +126,12 @@ mod tests { fn test_rename_failure() { let test_writeable = TestWriteable{}; let filename = "test_rename_failure_filename"; - let path = PathBuf::from("test_rename_failure_dir"); + let path = "test_rename_failure_dir"; + let mut dest_file = PathBuf::from(path); + dest_file.push(filename); // Create the channel data file and make it a directory. - fs::create_dir_all(get_full_filepath(path.clone(), filename.to_string())).unwrap(); - match write_to_file(path.clone(), filename.to_string(), &test_writeable) { + fs::create_dir_all(dest_file.clone()).unwrap(); + match write_to_file(dest_file, &test_writeable) { Err(e) => assert_eq!(e.raw_os_error(), Some(libc::EISDIR)), _ => panic!("Unexpected Ok(())") } @@ -145,16 +141,18 @@ mod tests { #[test] fn test_diskwriteable_failure() { struct FailingWriteable {} - impl DiskWriteable for FailingWriteable { - fn write_to_file(&self, _writer: &mut W) -> Result<(), std::io::Error> { + impl Writeable for FailingWriteable { + fn write(&self, _writer: &mut W) -> Result<(), std::io::Error> { Err(std::io::Error::new(std::io::ErrorKind::Other, "expected failure")) } } let filename = "test_diskwriteable_failure"; - let path = PathBuf::from("test_diskwriteable_failure_dir"); + let path = "test_diskwriteable_failure_dir"; let test_writeable = FailingWriteable{}; - match write_to_file(path.clone(), filename.to_string(), &test_writeable) { + let mut dest_file = PathBuf::from(path); + dest_file.push(filename); + match write_to_file(dest_file, &test_writeable) { Err(e) => { assert_eq!(e.kind(), std::io::ErrorKind::Other); assert_eq!(e.get_ref().unwrap().to_string(), "expected failure"); @@ -171,12 +169,13 @@ mod tests { fn test_tmp_file_creation_failure() { let test_writeable = TestWriteable{}; let filename = "test_tmp_file_creation_failure_filename".to_string(); - let path = PathBuf::from("test_tmp_file_creation_failure_dir"); - - // Create the tmp file and make it a directory. - let tmp_path = get_full_filepath(path.clone(), format!("{}.tmp", filename.clone())); - fs::create_dir_all(tmp_path).unwrap(); - match write_to_file(path, filename, &test_writeable) { + let path = "test_tmp_file_creation_failure_dir"; + let mut dest_file = PathBuf::from(path); + dest_file.push(filename); + let mut tmp_file = dest_file.clone(); + tmp_file.set_extension("tmp"); + fs::create_dir_all(tmp_file).unwrap(); + match write_to_file(dest_file, &test_writeable) { Err(e) => { #[cfg(not(target_os = "windows"))] assert_eq!(e.raw_os_error(), Some(libc::EISDIR)); diff --git a/lightning/src/chain/chainmonitor.rs b/lightning/src/chain/chainmonitor.rs index 19095fa2..aae260e7 100644 --- a/lightning/src/chain/chainmonitor.rs +++ b/lightning/src/chain/chainmonitor.rs @@ -23,7 +23,7 @@ //! events. The remote server would make use of [`ChainMonitor`] for block processing and for //! servicing [`ChannelMonitor`] updates from the client. -use bitcoin::blockdata::block::{Block, BlockHeader}; +use bitcoin::blockdata::block::BlockHeader; use bitcoin::hash_types::Txid; use chain; @@ -501,9 +501,7 @@ where L::Target: Logger, P::Target: Persist, { - fn block_connected(&self, block: &Block, height: u32) { - let header = &block.header; - let txdata: Vec<_> = block.txdata.iter().enumerate().collect(); + fn filtered_block_connected(&self, header: &BlockHeader, txdata: &TransactionData, height: u32) { log_debug!(self.logger, "New best block {} at height {} provided via block_connected", header.block_hash(), height); self.process_chain_data(header, Some(height), &txdata, |monitor, txdata| { monitor.block_connected( diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index fb2cbadd..681d895f 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -20,7 +20,7 @@ //! security-domain-separated system design, you should consider having multiple paths for //! ChannelMonitors to get out of the HSM and onto monitoring devices. -use bitcoin::blockdata::block::{Block, BlockHeader}; +use bitcoin::blockdata::block::BlockHeader; use bitcoin::blockdata::transaction::{TxOut,Transaction}; use bitcoin::blockdata::script::{Script, Builder}; use bitcoin::blockdata::opcodes; @@ -3007,9 +3007,8 @@ where F::Target: FeeEstimator, L::Target: Logger, { - fn block_connected(&self, block: &Block, height: u32) { - let txdata: Vec<_> = block.txdata.iter().enumerate().collect(); - self.0.block_connected(&block.header, &txdata, height, &*self.1, &*self.2, &*self.3); + fn filtered_block_connected(&self, header: &BlockHeader, txdata: &TransactionData, height: u32) { + self.0.block_connected(header, txdata, height, &*self.1, &*self.2, &*self.3); } fn block_disconnected(&self, header: &BlockHeader, height: u32) { diff --git a/lightning/src/chain/mod.rs b/lightning/src/chain/mod.rs index 25e5a97d..24eb09e7 100644 --- a/lightning/src/chain/mod.rs +++ b/lightning/src/chain/mod.rs @@ -87,9 +87,20 @@ pub trait Access { /// sourcing chain data using a block-oriented API should prefer this interface over [`Confirm`]. /// Such clients fetch the entire header chain whereas clients using [`Confirm`] only fetch headers /// when needed. +/// +/// By using [`Listen::filtered_block_connected`] this interface supports clients fetching the +/// entire header chain and only blocks with matching transaction data using BIP 157 filters or +/// other similar filtering. pub trait Listen { + /// Notifies the listener that a block was added at the given height, with the transaction data + /// possibly filtered. + fn filtered_block_connected(&self, header: &BlockHeader, txdata: &TransactionData, height: u32); + /// Notifies the listener that a block was added at the given height. - fn block_connected(&self, block: &Block, height: u32); + fn block_connected(&self, block: &Block, height: u32) { + let txdata: Vec<_> = block.txdata.iter().enumerate().collect(); + self.filtered_block_connected(&block.header, &txdata, height); + } /// Notifies the listener that a block was removed at the given height. fn block_disconnected(&self, header: &BlockHeader, height: u32); @@ -355,8 +366,8 @@ pub struct WatchedOutput { } impl Listen for core::ops::Deref { - fn block_connected(&self, block: &Block, height: u32) { - (**self).block_connected(block, height); + fn filtered_block_connected(&self, header: &BlockHeader, txdata: &TransactionData, height: u32) { + (**self).filtered_block_connected(header, txdata, height); } fn block_disconnected(&self, header: &BlockHeader, height: u32) { @@ -369,9 +380,9 @@ where T::Target: Listen, U::Target: Listen, { - fn block_connected(&self, block: &Block, height: u32) { - self.0.block_connected(block, height); - self.1.block_connected(block, height); + fn filtered_block_connected(&self, header: &BlockHeader, txdata: &TransactionData, height: u32) { + self.0.filtered_block_connected(header, txdata, height); + self.1.filtered_block_connected(header, txdata, height); } fn block_disconnected(&self, header: &BlockHeader, height: u32) { diff --git a/lightning/src/ln/chanmon_update_fail_tests.rs b/lightning/src/ln/chanmon_update_fail_tests.rs index 94af00c7..58fe30ba 100644 --- a/lightning/src/ln/chanmon_update_fail_tests.rs +++ b/lightning/src/ln/chanmon_update_fail_tests.rs @@ -1102,7 +1102,7 @@ fn test_monitor_update_fail_reestablish() { assert!(updates.update_fee.is_none()); assert_eq!(updates.update_fulfill_htlcs.len(), 1); nodes[1].node.handle_update_fulfill_htlc(&nodes[2].node.get_our_node_id(), &updates.update_fulfill_htlcs[0]); - expect_payment_forwarded!(nodes[1], Some(1000), false); + expect_payment_forwarded!(nodes[1], nodes[0], Some(1000), false); check_added_monitors!(nodes[1], 1); assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty()); commitment_signed_dance!(nodes[1], nodes[2], updates.commitment_signed, false); @@ -2087,7 +2087,7 @@ fn test_fail_htlc_on_broadcast_after_claim() { nodes[1].node.handle_update_fulfill_htlc(&nodes[2].node.get_our_node_id(), &cs_updates.update_fulfill_htlcs[0]); let bs_updates = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()); check_added_monitors!(nodes[1], 1); - expect_payment_forwarded!(nodes[1], Some(1000), false); + expect_payment_forwarded!(nodes[1], nodes[0], Some(1000), false); mine_transaction(&nodes[1], &bs_txn[0]); check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed); @@ -2423,7 +2423,7 @@ fn do_test_reconnect_dup_htlc_claims(htlc_status: HTLCStatusAtDupClaim, second_f let mut nodes = create_network(3, &node_cfgs, &node_chanmgrs); create_announced_chan_between_nodes(&nodes, 0, 1, InitFeatures::known(), InitFeatures::known()); - let chan_2 = create_announced_chan_between_nodes(&nodes, 1, 2, InitFeatures::known(), InitFeatures::known()).2; + let chan_id_2 = create_announced_chan_between_nodes(&nodes, 1, 2, InitFeatures::known(), InitFeatures::known()).2; let (payment_preimage, payment_hash, _) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 100_000); @@ -2450,7 +2450,7 @@ fn do_test_reconnect_dup_htlc_claims(htlc_status: HTLCStatusAtDupClaim, second_f } let fulfill_msg = msgs::UpdateFulfillHTLC { - channel_id: chan_2, + channel_id: chan_id_2, htlc_id: 0, payment_preimage, }; @@ -2468,7 +2468,7 @@ fn do_test_reconnect_dup_htlc_claims(htlc_status: HTLCStatusAtDupClaim, second_f assert_eq!(fulfill_msg, cs_updates.update_fulfill_htlcs[0]); } nodes[1].node.handle_update_fulfill_htlc(&nodes[2].node.get_our_node_id(), &fulfill_msg); - expect_payment_forwarded!(nodes[1], Some(1000), false); + expect_payment_forwarded!(nodes[1], nodes[0], Some(1000), false); check_added_monitors!(nodes[1], 1); let mut bs_updates = None; diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index b172b8c5..cf70749a 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -62,6 +62,17 @@ pub struct ChannelValueStat { pub counterparty_dust_limit_msat: u64, } +pub struct AvailableBalances { + /// The amount that would go to us if we close the channel, ignoring any on-chain fees. + pub balance_msat: u64, + /// Total amount available for our counterparty to send to us. + pub inbound_capacity_msat: u64, + /// Total amount available for us to send to our counterparty. + pub outbound_capacity_msat: u64, + /// The maximum value we can assign to the next outbound HTLC + pub next_outbound_htlc_limit_msat: u64, +} + #[derive(Debug, Clone, Copy, PartialEq)] enum FeeUpdateState { // Inbound states mirroring InboundHTLCState @@ -2340,40 +2351,39 @@ impl Channel { stats } - /// Get the available (ie not including pending HTLCs) inbound and outbound balance in msat. + /// Get the available balances, see [`AvailableBalances`]'s fields for more info. /// Doesn't bother handling the /// if-we-removed-it-already-but-haven't-fully-resolved-they-can-still-send-an-inbound-HTLC /// corner case properly. - /// The channel reserve is subtracted from each balance. - /// See also [`Channel::get_balance_msat`] - pub fn get_inbound_outbound_available_balance_msat(&self) -> (u64, u64) { + pub fn get_available_balances(&self) -> AvailableBalances { // Note that we have to handle overflow due to the above case. - ( - cmp::max(self.channel_value_satoshis as i64 * 1000 - - self.value_to_self_msat as i64 - - self.get_inbound_pending_htlc_stats(None).pending_htlcs_value_msat as i64 - - self.holder_selected_channel_reserve_satoshis as i64 * 1000, - 0) as u64, - cmp::max(self.value_to_self_msat as i64 - - self.get_outbound_pending_htlc_stats(None).pending_htlcs_value_msat as i64 - - self.counterparty_selected_channel_reserve_satoshis.unwrap_or(0) as i64 * 1000, - 0) as u64 - ) - } + let outbound_stats = self.get_outbound_pending_htlc_stats(None); - /// Get our total balance in msat. - /// This is the amount that would go to us if we close the channel, ignoring any on-chain fees. - /// See also [`Channel::get_inbound_outbound_available_balance_msat`] - pub fn get_balance_msat(&self) -> u64 { - // Include our local balance, plus any inbound HTLCs we know the preimage for, minus any - // HTLCs sent or which will be sent after commitment signed's are exchanged. let mut balance_msat = self.value_to_self_msat; for ref htlc in self.pending_inbound_htlcs.iter() { if let InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::Fulfill(_)) = htlc.state { balance_msat += htlc.amount_msat; } } - balance_msat - self.get_outbound_pending_htlc_stats(None).pending_htlcs_value_msat + balance_msat -= outbound_stats.pending_htlcs_value_msat; + + let outbound_capacity_msat = cmp::max(self.value_to_self_msat as i64 + - outbound_stats.pending_htlcs_value_msat as i64 + - self.counterparty_selected_channel_reserve_satoshis.unwrap_or(0) as i64 * 1000, + 0) as u64; + AvailableBalances { + inbound_capacity_msat: cmp::max(self.channel_value_satoshis as i64 * 1000 + - self.value_to_self_msat as i64 + - self.get_inbound_pending_htlc_stats(None).pending_htlcs_value_msat as i64 + - self.holder_selected_channel_reserve_satoshis as i64 * 1000, + 0) as u64, + outbound_capacity_msat, + next_outbound_htlc_limit_msat: cmp::max(cmp::min(outbound_capacity_msat as i64, + self.counterparty_max_htlc_value_in_flight_msat as i64 + - outbound_stats.pending_htlcs_value_msat as i64), + 0) as u64, + balance_msat, + } } pub fn get_holder_counterparty_selected_channel_reserve_satoshis(&self) -> (u64, Option) { @@ -4331,11 +4341,15 @@ impl Channel { } /// Allowed in any state (including after shutdown) - #[cfg(test)] pub fn get_holder_htlc_minimum_msat(&self) -> u64 { self.holder_htlc_minimum_msat } + /// Allowed in any state (including after shutdown), but will return none before TheirInitSent + pub fn get_holder_htlc_maximum_msat(&self) -> Option { + self.get_htlc_maximum_msat(self.holder_max_htlc_value_in_flight_msat) + } + /// Allowed in any state (including after shutdown) pub fn get_announced_htlc_max_msat(&self) -> u64 { return cmp::min( @@ -4353,6 +4367,21 @@ impl Channel { self.counterparty_htlc_minimum_msat } + /// Allowed in any state (including after shutdown), but will return none before TheirInitSent + pub fn get_counterparty_htlc_maximum_msat(&self) -> Option { + self.get_htlc_maximum_msat(self.counterparty_max_htlc_value_in_flight_msat) + } + + fn get_htlc_maximum_msat(&self, party_max_htlc_value_in_flight_msat: u64) -> Option { + self.counterparty_selected_channel_reserve_satoshis.map(|counterparty_reserve| { + let holder_reserve = self.holder_selected_channel_reserve_satoshis; + cmp::min( + (self.channel_value_satoshis - counterparty_reserve - holder_reserve) * 1000, + party_max_htlc_value_in_flight_msat + ) + }) + } + pub fn get_value_satoshis(&self) -> u64 { self.channel_value_satoshis } diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 4bc26874..5990b2ad 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -18,7 +18,7 @@ //! imply it needs to fail HTLCs/payments/channels it manages). //! -use bitcoin::blockdata::block::{Block, BlockHeader}; +use bitcoin::blockdata::block::BlockHeader; use bitcoin::blockdata::transaction::Transaction; use bitcoin::blockdata::constants::genesis_block; use bitcoin::network::constants::Network; @@ -922,6 +922,12 @@ pub struct ChannelCounterparty { /// Information on the fees and requirements that the counterparty requires when forwarding /// payments to us through this channel. pub forwarding_info: Option, + /// The smallest value HTLC (in msat) the remote peer will accept, for this channel. This field + /// is only `None` before we have received either the `OpenChannel` or `AcceptChannel` message + /// from the remote peer, or for `ChannelCounterparty` objects serialized prior to LDK 0.0.107. + pub outbound_htlc_minimum_msat: Option, + /// The largest value HTLC (in msat) the remote peer currently will accept, for this channel. + pub outbound_htlc_maximum_msat: Option, } /// Details of a channel, as returned by ChannelManager::list_channels and ChannelManager::list_usable_channels @@ -999,6 +1005,13 @@ pub struct ChannelDetails { /// conflict-avoidance policy, exactly this amount is not likely to be spendable. However, we /// should be able to spend nearly this amount. pub outbound_capacity_msat: u64, + /// The available outbound capacity for sending a single HTLC to the remote peer. This is + /// similar to [`ChannelDetails::outbound_capacity_msat`] but it may be further restricted by + /// the current state and per-HTLC limit(s). This is intended for use when routing, allowing us + /// to use a limit as close as possible to the HTLC limit we can currently send. + /// + /// See also [`ChannelDetails::balance_msat`] and [`ChannelDetails::outbound_capacity_msat`]. + pub next_outbound_htlc_limit_msat: u64, /// The available inbound capacity for the remote peer to send HTLCs to us. This does not /// include any pending HTLCs which are not yet fully resolved (and, thus, whose balance is not /// available for inclusion in new inbound HTLCs). @@ -1046,6 +1059,11 @@ pub struct ChannelDetails { pub is_usable: bool, /// True if this channel is (or will be) publicly-announced. pub is_public: bool, + /// The smallest value HTLC (in msat) we will accept, for this channel. This field + /// is only `None` for `ChannelDetails` objects serialized prior to LDK 0.0.107 + pub inbound_htlc_minimum_msat: Option, + /// The largest value HTLC (in msat) we currently will accept, for this channel. + pub inbound_htlc_maximum_msat: Option, } impl ChannelDetails { @@ -1657,8 +1675,7 @@ impl ChannelMana let channel_state = self.channel_state.lock().unwrap(); res.reserve(channel_state.by_id.len()); for (channel_id, channel) in channel_state.by_id.iter().filter(f) { - let (inbound_capacity_msat, outbound_capacity_msat) = channel.get_inbound_outbound_available_balance_msat(); - let balance_msat = channel.get_balance_msat(); + let balance = channel.get_available_balances(); let (to_remote_reserve_satoshis, to_self_reserve_satoshis) = channel.get_holder_counterparty_selected_channel_reserve_satoshis(); res.push(ChannelDetails { @@ -1668,6 +1685,14 @@ impl ChannelMana features: InitFeatures::empty(), unspendable_punishment_reserve: to_remote_reserve_satoshis, forwarding_info: channel.counterparty_forwarding_info(), + // Ensures that we have actually received the `htlc_minimum_msat` value + // from the counterparty through the `OpenChannel` or `AcceptChannel` + // message (as they are always the first message from the counterparty). + // Else `Channel::get_counterparty_htlc_minimum_msat` could return the + // default `0` value set by `Channel::new_outbound`. + outbound_htlc_minimum_msat: if channel.have_received_message() { + Some(channel.get_counterparty_htlc_minimum_msat()) } else { None }, + outbound_htlc_maximum_msat: channel.get_counterparty_htlc_maximum_msat(), }, funding_txo: channel.get_funding_txo(), // Note that accept_channel (or open_channel) is always the first message, so @@ -1677,9 +1702,10 @@ impl ChannelMana inbound_scid_alias: channel.latest_inbound_scid_alias(), channel_value_satoshis: channel.get_value_satoshis(), unspendable_punishment_reserve: to_self_reserve_satoshis, - balance_msat, - inbound_capacity_msat, - outbound_capacity_msat, + balance_msat: balance.balance_msat, + inbound_capacity_msat: balance.inbound_capacity_msat, + outbound_capacity_msat: balance.outbound_capacity_msat, + next_outbound_htlc_limit_msat: balance.next_outbound_htlc_limit_msat, user_channel_id: channel.get_user_id(), confirmations_required: channel.minimum_depth(), force_close_spend_delay: channel.get_counterparty_selected_contest_delay(), @@ -1687,6 +1713,8 @@ impl ChannelMana is_funding_locked: channel.is_usable(), is_usable: channel.is_live(), is_public: channel.should_announce(), + inbound_htlc_minimum_msat: Some(channel.get_holder_htlc_minimum_msat()), + inbound_htlc_maximum_msat: channel.get_holder_htlc_maximum_msat() }); } } @@ -4020,7 +4048,10 @@ impl ChannelMana } else { None }; let mut pending_events = self.pending_events.lock().unwrap(); + + let source_channel_id = Some(prev_outpoint.to_channel_id()); pending_events.push(events::Event::PaymentForwarded { + source_channel_id, fee_earned_msat, claim_from_onchain_tx: from_onchain, }); @@ -5277,18 +5308,17 @@ where F::Target: FeeEstimator, L::Target: Logger, { - fn block_connected(&self, block: &Block, height: u32) { + fn filtered_block_connected(&self, header: &BlockHeader, txdata: &TransactionData, height: u32) { { let best_block = self.best_block.read().unwrap(); - assert_eq!(best_block.block_hash(), block.header.prev_blockhash, + assert_eq!(best_block.block_hash(), header.prev_blockhash, "Blocks must be connected in chain-order - the connected header must build on the last connected header"); assert_eq!(best_block.height(), height - 1, "Blocks must be connected in chain-order - the connected block height must be one greater than the previous height"); } - let txdata: Vec<_> = block.txdata.iter().enumerate().collect(); - self.transactions_confirmed(&block.header, &txdata, height); - self.best_block_updated(&block.header, height); + self.transactions_confirmed(header, txdata, height); + self.best_block_updated(header, height); } fn block_disconnected(&self, header: &BlockHeader, height: u32) { @@ -5895,6 +5925,8 @@ impl_writeable_tlv_based!(ChannelCounterparty, { (4, features, required), (6, unspendable_punishment_reserve, required), (8, forwarding_info, option), + (9, outbound_htlc_minimum_msat, option), + (11, outbound_htlc_maximum_msat, option), }); impl_writeable_tlv_based!(ChannelDetails, { @@ -5909,6 +5941,9 @@ impl_writeable_tlv_based!(ChannelDetails, { (14, user_channel_id, required), (16, balance_msat, required), (18, outbound_capacity_msat, required), + // Note that by the time we get past the required read above, outbound_capacity_msat will be + // filled in, so we can safely unwrap it here. + (19, next_outbound_htlc_limit_msat, (default_value, outbound_capacity_msat.0.unwrap())), (20, inbound_capacity_msat, required), (22, confirmations_required, option), (24, force_close_spend_delay, option), @@ -5916,6 +5951,8 @@ impl_writeable_tlv_based!(ChannelDetails, { (28, is_funding_locked, required), (30, is_usable, required), (32, is_public, required), + (33, inbound_htlc_minimum_msat, option), + (35, inbound_htlc_maximum_msat, option), }); impl_writeable_tlv_based!(PhantomRouteHints, { diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index aa6fdc97..9bbd6c0e 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -1171,19 +1171,18 @@ macro_rules! get_payment_preimage_hash { #[macro_export] macro_rules! get_route_and_payment_hash { ($send_node: expr, $recv_node: expr, $recv_value: expr) => {{ - $crate::get_route_and_payment_hash!($send_node, $recv_node, vec![], $recv_value, TEST_FINAL_CLTV) + let payment_params = $crate::routing::router::PaymentParameters::from_node_id($recv_node.node.get_our_node_id()) + .with_features($crate::ln::features::InvoiceFeatures::known()); + $crate::get_route_and_payment_hash!($send_node, $recv_node, payment_params, $recv_value, TEST_FINAL_CLTV) }}; - ($send_node: expr, $recv_node: expr, $last_hops: expr, $recv_value: expr, $cltv: expr) => {{ + ($send_node: expr, $recv_node: expr, $payment_params: expr, $recv_value: expr, $cltv: expr) => {{ use $crate::chain::keysinterface::KeysInterface; let (payment_preimage, payment_hash, payment_secret) = $crate::get_payment_preimage_hash!($recv_node, Some($recv_value)); - let payment_params = $crate::routing::router::PaymentParameters::from_node_id($recv_node.node.get_our_node_id()) - .with_features($crate::ln::features::InvoiceFeatures::known()) - .with_route_hints($last_hops); let scorer = $crate::util::test_utils::TestScorer::with_penalty(0); let keys_manager = $crate::util::test_utils::TestKeysInterface::new(&[0u8; 32], bitcoin::network::constants::Network::Testnet); let random_seed_bytes = keys_manager.get_secure_random_bytes(); let route = $crate::routing::router::get_route( - &$send_node.node.get_our_node_id(), &payment_params, &$send_node.network_graph.read_only(), + &$send_node.node.get_our_node_id(), &$payment_params, &$send_node.network_graph.read_only(), Some(&$send_node.node.list_usable_channels().iter().collect::>()), $recv_value, $cltv, $send_node.logger, &scorer, &random_seed_bytes ).unwrap(); @@ -1328,12 +1327,16 @@ macro_rules! expect_payment_path_successful { } macro_rules! expect_payment_forwarded { - ($node: expr, $expected_fee: expr, $upstream_force_closed: expr) => { + ($node: expr, $source_node: expr, $expected_fee: expr, $upstream_force_closed: expr) => { let events = $node.node.get_and_clear_pending_events(); assert_eq!(events.len(), 1); match events[0] { - Event::PaymentForwarded { fee_earned_msat, claim_from_onchain_tx } => { + Event::PaymentForwarded { fee_earned_msat, source_channel_id, claim_from_onchain_tx } => { assert_eq!(fee_earned_msat, $expected_fee); + if fee_earned_msat.is_some() { + // Is the event channel_id in one of the channels between the two nodes? + assert!($node.node.list_channels().iter().any(|x| x.counterparty.node_id == $source_node.node.get_our_node_id() && x.channel_id == source_channel_id.unwrap())); + } assert_eq!(claim_from_onchain_tx, $upstream_force_closed); }, _ => panic!("Unexpected event"), @@ -1572,11 +1575,11 @@ pub fn do_claim_payment_along_route<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, } } macro_rules! mid_update_fulfill_dance { - ($node: expr, $prev_node: expr, $new_msgs: expr) => { + ($node: expr, $prev_node: expr, $next_node: expr, $new_msgs: expr) => { { $node.node.handle_update_fulfill_htlc(&$prev_node.node.get_our_node_id(), &next_msgs.as_ref().unwrap().0); let fee = $node.node.channel_state.lock().unwrap().by_id.get(&next_msgs.as_ref().unwrap().0.channel_id).unwrap().config.forwarding_fee_base_msat; - expect_payment_forwarded!($node, Some(fee as u64), false); + expect_payment_forwarded!($node, $next_node, Some(fee as u64), false); expected_total_fee_msat += fee as u64; check_added_monitors!($node, 1); let new_next_msgs = if $new_msgs { @@ -1600,7 +1603,14 @@ pub fn do_claim_payment_along_route<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, assert_eq!(expected_next_node, node.node.get_our_node_id()); let update_next_msgs = !skip_last || idx != expected_route.len() - 1; if next_msgs.is_some() { - mid_update_fulfill_dance!(node, prev_node, update_next_msgs); + // Since we are traversing in reverse, next_node is actually the previous node + let next_node: &Node; + if idx == expected_route.len() - 1 { + next_node = origin_node; + } else { + next_node = expected_route[expected_route.len() - 1 - idx - 1]; + } + mid_update_fulfill_dance!(node, prev_node, next_node, update_next_msgs); } else { assert!(!update_next_msgs); assert!(node.node.get_and_clear_pending_msg_events().is_empty()); diff --git a/lightning/src/ln/functional_tests.rs b/lightning/src/ln/functional_tests.rs index 3449bd94..d9756035 100644 --- a/lightning/src/ln/functional_tests.rs +++ b/lightning/src/ln/functional_tests.rs @@ -2706,10 +2706,23 @@ fn test_htlc_on_chain_success() { Event::ChannelClosed { reason: ClosureReason::CommitmentTxConfirmed, .. } => {} _ => panic!("Unexpected event"), } - if let Event::PaymentForwarded { fee_earned_msat: Some(1000), claim_from_onchain_tx: true } = forwarded_events[1] { - } else { panic!(); } - if let Event::PaymentForwarded { fee_earned_msat: Some(1000), claim_from_onchain_tx: true } = forwarded_events[2] { - } else { panic!(); } + let chan_id = Some(chan_1.2); + match forwarded_events[1] { + Event::PaymentForwarded { fee_earned_msat, source_channel_id, claim_from_onchain_tx } => { + assert_eq!(fee_earned_msat, Some(1000)); + assert_eq!(source_channel_id, chan_id); + assert_eq!(claim_from_onchain_tx, true); + }, + _ => panic!() + } + match forwarded_events[2] { + Event::PaymentForwarded { fee_earned_msat, source_channel_id, claim_from_onchain_tx } => { + assert_eq!(fee_earned_msat, Some(1000)); + assert_eq!(source_channel_id, chan_id); + assert_eq!(claim_from_onchain_tx, true); + }, + _ => panic!() + } let events = nodes[1].node.get_and_clear_pending_msg_events(); { let mut added_monitors = nodes[1].chain_monitor.added_monitors.lock().unwrap(); @@ -5126,8 +5139,9 @@ fn test_onchain_to_onchain_claim() { _ => panic!("Unexpected event"), } match events[1] { - Event::PaymentForwarded { fee_earned_msat, claim_from_onchain_tx } => { + Event::PaymentForwarded { fee_earned_msat, source_channel_id, claim_from_onchain_tx } => { assert_eq!(fee_earned_msat, Some(1000)); + assert_eq!(source_channel_id, Some(chan_1.2)); assert_eq!(claim_from_onchain_tx, true); }, _ => panic!("Unexpected event"), @@ -5210,7 +5224,9 @@ fn test_duplicate_payment_hash_one_failure_one_success() { // We reduce the final CLTV here by a somewhat arbitrary constant to keep it under the one-byte // script push size limit so that the below script length checks match // ACCEPTED_HTLC_SCRIPT_WEIGHT. - let (route, _, _, _) = get_route_and_payment_hash!(nodes[0], nodes[3], vec![], 900000, TEST_FINAL_CLTV - 40); + let payment_params = PaymentParameters::from_node_id(nodes[3].node.get_our_node_id()) + .with_features(InvoiceFeatures::known()); + let (route, _, _, _) = get_route_and_payment_hash!(nodes[0], nodes[3], payment_params, 900000, TEST_FINAL_CLTV - 40); send_along_route_with_secret(&nodes[0], route, &[&[&nodes[1], &nodes[2], &nodes[3]]], 900000, duplicate_payment_hash, payment_secret); let commitment_txn = get_local_commitment_txn!(nodes[2], chan_2.2); @@ -5293,7 +5309,7 @@ fn test_duplicate_payment_hash_one_failure_one_success() { // Note that the fee paid is effectively double as the HTLC value (including the nodes[1] fee // and nodes[2] fee) is rounded down and then claimed in full. mine_transaction(&nodes[1], &htlc_success_txn[0]); - expect_payment_forwarded!(nodes[1], Some(196*2), true); + expect_payment_forwarded!(nodes[1], nodes[0], Some(196*2), true); let updates = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()); assert!(updates.update_add_htlcs.is_empty()); assert!(updates.update_fail_htlcs.is_empty()); @@ -6459,7 +6475,9 @@ fn test_update_add_htlc_bolt2_sender_cltv_expiry_too_high() { let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs); let _chan = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1000000, 0, InitFeatures::known(), InitFeatures::known()); - let (mut route, our_payment_hash, _, our_payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[1], vec![], 100000000, 0); + let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id()) + .with_features(InvoiceFeatures::known()); + let (mut route, our_payment_hash, _, our_payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[1], payment_params, 100000000, 0); route.paths[0].last_mut().unwrap().cltv_expiry_delta = 500000001; unwrap_send_err!(nodes[0].node.send_payment(&route, our_payment_hash, &Some(our_payment_secret)), true, APIError::RouteError { ref err }, assert_eq!(err, &"Channel CLTV overflowed?")); @@ -7559,7 +7577,9 @@ fn test_bump_penalty_txn_on_revoked_commitment() { let chan = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1000000, 59000000, InitFeatures::known(), InitFeatures::known()); let payment_preimage = route_payment(&nodes[0], &vec!(&nodes[1])[..], 3000000).0; - let (route,_, _, _) = get_route_and_payment_hash!(nodes[1], nodes[0], vec![], 3000000, 30); + let payment_params = PaymentParameters::from_node_id(nodes[0].node.get_our_node_id()) + .with_features(InvoiceFeatures::known()); + let (route,_, _, _) = get_route_and_payment_hash!(nodes[1], nodes[0], payment_params, 3000000, 30); send_along_route(&nodes[1], route, &vec!(&nodes[0])[..], 3000000); let revoked_txn = get_local_commitment_txn!(nodes[0], chan.2); @@ -8871,7 +8891,7 @@ fn do_test_onchain_htlc_settlement_after_close(broadcast_alice: bool, go_onchain assert_eq!(carol_updates.update_fulfill_htlcs.len(), 1); nodes[1].node.handle_update_fulfill_htlc(&nodes[2].node.get_our_node_id(), &carol_updates.update_fulfill_htlcs[0]); - expect_payment_forwarded!(nodes[1], if go_onchain_before_fulfill || force_closing_node == 1 { None } else { Some(1000) }, false); + expect_payment_forwarded!(nodes[1], nodes[0], if go_onchain_before_fulfill || force_closing_node == 1 { None } else { Some(1000) }, false); // If Alice broadcasted but Bob doesn't know yet, here he prepares to tell her about the preimage. if !go_onchain_before_fulfill && broadcast_alice { let events = nodes[1].node.get_and_clear_pending_msg_events(); diff --git a/lightning/src/ln/onion_route_tests.rs b/lightning/src/ln/onion_route_tests.rs index 706fcb6c..834791cb 100644 --- a/lightning/src/ln/onion_route_tests.rs +++ b/lightning/src/ln/onion_route_tests.rs @@ -16,9 +16,9 @@ use chain::keysinterface::{KeysInterface, Recipient}; use ln::{PaymentHash, PaymentSecret}; use ln::channelmanager::{HTLCForwardInfo, CLTV_FAR_FAR_AWAY, MIN_CLTV_EXPIRY_DELTA, PendingHTLCInfo, PendingHTLCRouting}; use ln::onion_utils; -use routing::network_graph::{NetworkUpdate, RoutingFees}; +use routing::network_graph::{NetworkUpdate, RoutingFees, NodeId}; use routing::router::{get_route, PaymentParameters, Route, RouteHint, RouteHintHop}; -use ln::features::{InitFeatures, InvoiceFeatures}; +use ln::features::{InitFeatures, InvoiceFeatures, NodeFeatures}; use ln::msgs; use ln::msgs::{ChannelMessageHandler, ChannelUpdate, OptionalField}; use util::events::{Event, MessageSendEvent, MessageSendEventsProvider}; @@ -577,6 +577,168 @@ fn test_onion_failure() { }, true, Some(23), None, None); } +#[test] +fn test_default_to_onion_payload_tlv_format() { + // Tests that we default to creating tlv format onion payloads when no `NodeAnnouncementInfo` + // `features` for a node in the `network_graph` exists, or when the node isn't in the + // `network_graph`, and no other known `features` for the node exists. + let mut priv_channels_conf = UserConfig::default(); + priv_channels_conf.channel_options.announced_channel = false; + let chanmon_cfgs = create_chanmon_cfgs(5); + let node_cfgs = create_node_cfgs(5, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(5, &node_cfgs, &[None, None, None, None, Some(priv_channels_conf)]); + let mut nodes = create_network(5, &node_cfgs, &node_chanmgrs); + + create_announced_chan_between_nodes(&nodes, 0, 1, InitFeatures::known(), InitFeatures::known()); + create_announced_chan_between_nodes(&nodes, 1, 2, InitFeatures::known(), InitFeatures::known()); + create_announced_chan_between_nodes(&nodes, 2, 3, InitFeatures::known(), InitFeatures::known()); + create_unannounced_chan_between_nodes_with_value(&nodes, 3, 4, 100000, 10001, InitFeatures::known(), InitFeatures::known()); + + let payment_params = PaymentParameters::from_node_id(nodes[3].node.get_our_node_id()); + let origin_node = &nodes[0]; + let network_graph = origin_node.network_graph; + + // Clears all the `NodeAnnouncementInfo` for all nodes of `nodes[0]`'s `network_graph`, so that + // their `features` aren't used when creating the `route`. + network_graph.clear_nodes_announcement_info(); + + let (announced_route, _, _, _) = get_route_and_payment_hash!( + origin_node, nodes[3], payment_params, 10_000, TEST_FINAL_CLTV); + + let hops = &announced_route.paths[0]; + // Assert that the hop between `nodes[1]` and `nodes[2]` defaults to supporting variable length + // onions, as `nodes[0]` has no `NodeAnnouncementInfo` `features` for `node[2]` + assert!(hops[1].node_features.supports_variable_length_onion()); + // Assert that the hop between `nodes[2]` and `nodes[3]` defaults to supporting variable length + // onions, as `nodes[0]` has no `NodeAnnouncementInfo` `features` for `node[3]`, and no `InvoiceFeatures` + // for the `payment_params`, which would otherwise have been used. + assert!(hops[2].node_features.supports_variable_length_onion()); + // Note that we do not assert that `hops[0]` (the channel between `nodes[0]` and `nodes[1]`) + // supports variable length onions, as the `InitFeatures` exchanged in the init message + // between the nodes will be used when creating the route. We therefore do not default to + // supporting variable length onions for that hop, as the `InitFeatures` in this case are + // `InitFeatures::known()`. + + let unannounced_chan = &nodes[4].node.list_usable_channels()[0]; + + let last_hop = RouteHint(vec![RouteHintHop { + src_node_id: nodes[3].node.get_our_node_id(), + short_channel_id: unannounced_chan.short_channel_id.unwrap(), + fees: RoutingFees { + base_msat: 0, + proportional_millionths: 0, + }, + cltv_expiry_delta: 42, + htlc_minimum_msat: None, + htlc_maximum_msat: None, + }]); + + let unannounced_chan_params = PaymentParameters::from_node_id(nodes[4].node.get_our_node_id()).with_route_hints(vec![last_hop]); + let (unannounced_route, _, _, _) = get_route_and_payment_hash!( + origin_node, nodes[4], unannounced_chan_params, 10_000, TEST_FINAL_CLTV); + + let unannounced_chan_hop = &unannounced_route.paths[0][3]; + // Ensure that `nodes[4]` doesn't exist in `nodes[0]`'s `network_graph`, as it's not public. + assert!(&network_graph.read_only().nodes().get(&NodeId::from_pubkey(&nodes[4].node.get_our_node_id())).is_none()); + // Assert that the hop between `nodes[3]` and `nodes[4]` defaults to supporting variable length + // onions, even though `nodes[4]` as `nodes[0]` doesn't exists in `nodes[0]`'s `network_graph`, + // and no `InvoiceFeatures` for the `payment_params` exists, which would otherwise have been + // used. + assert!(unannounced_chan_hop.node_features.supports_variable_length_onion()); + + let cur_height = nodes[0].best_block_info().1 + 1; + let (announced_route_payloads, _htlc_msat, _htlc_cltv) = onion_utils::build_onion_payloads(&announced_route.paths[0], 40000, &None, cur_height, &None).unwrap(); + let (unannounced_route_paylods, _htlc_msat, _htlc_cltv) = onion_utils::build_onion_payloads(&unannounced_route.paths[0], 40000, &None, cur_height, &None).unwrap(); + + for onion_payloads in vec![announced_route_payloads, unannounced_route_paylods] { + for onion_payload in onion_payloads.iter() { + match onion_payload.format { + msgs::OnionHopDataFormat::Legacy {..} => { + panic!("Generated a `msgs::OnionHopDataFormat::Legacy` payload, even though that shouldn't have happend."); + } + _ => {} + } + } + } +} + +#[test] +fn test_do_not_default_to_onion_payload_tlv_format_when_unsupported() { + // Tests that we do not default to creating tlv onions if either of these types features + // exists, which specifies no support for variable length onions for a specific hop, when + // creating a route: + // 1. `InitFeatures` to the counterparty node exchanged with the init message to the node. + // 2. `NodeFeatures` in the `NodeAnnouncementInfo` of a node in sender node's `network_graph`. + // 3. `InvoiceFeatures` specified by the receiving node, when no `NodeAnnouncementInfo` + // `features` exists for the receiver in the sender's `network_graph`. + let chanmon_cfgs = create_chanmon_cfgs(4); + let mut node_cfgs = create_node_cfgs(4, &chanmon_cfgs); + + // Set `node[1]` config to `InitFeatures::empty()` which return `false` for + // `supports_variable_length_onion()` + let mut node_1_cfg = &mut node_cfgs[1]; + node_1_cfg.features = InitFeatures::empty(); + + let node_chanmgrs = create_node_chanmgrs(4, &node_cfgs, &[None, None, None, None]); + let mut nodes = create_network(4, &node_cfgs, &node_chanmgrs); + + create_announced_chan_between_nodes(&nodes, 0, 1, InitFeatures::known(), InitFeatures::known()); + create_announced_chan_between_nodes(&nodes, 1, 2, InitFeatures::known(), InitFeatures::known()); + create_announced_chan_between_nodes(&nodes, 2, 3, InitFeatures::known(), InitFeatures::known()); + + let payment_params = PaymentParameters::from_node_id(nodes[3].node.get_our_node_id()) + .with_features(InvoiceFeatures::empty()); + let origin_node = &nodes[0]; + let network_graph = origin_node.network_graph; + network_graph.clear_nodes_announcement_info(); + + // Set `NodeAnnouncementInfo` `features` which do not support variable length onions for + // `nodes[2]` in `nodes[0]`'s `network_graph`. + let nodes_2_unsigned_node_announcement = msgs::UnsignedNodeAnnouncement { + features: NodeFeatures::empty(), + timestamp: 0, + node_id: nodes[2].node.get_our_node_id(), + rgb: [32; 3], + alias: [16;32], + addresses: Vec::new(), + excess_address_data: Vec::new(), + excess_data: Vec::new(), + }; + let _res = network_graph.update_node_from_unsigned_announcement(&nodes_2_unsigned_node_announcement); + + let (route, _, _, _) = get_route_and_payment_hash!( + origin_node, nodes[3], payment_params, 10_000, TEST_FINAL_CLTV); + + let hops = &route.paths[0]; + + // Assert that the hop between `nodes[0]` and `nodes[1]` doesn't support variable length + // onions, as as the `InitFeatures` exchanged (`InitFeatures::empty()`) in the init message + // between the nodes when setting up the channel is used when creating the `route` and that we + // therefore do not default to supporting variable length onions. Despite `nodes[0]` having no + // `NodeAnnouncementInfo` `features` for `node[1]`. + assert!(!hops[0].node_features.supports_variable_length_onion()); + // Assert that the hop between `nodes[1]` and `nodes[2]` uses the `features` from + // `nodes_2_unsigned_node_announcement` that doesn't support variable length onions. + assert!(!hops[1].node_features.supports_variable_length_onion()); + // Assert that the hop between `nodes[2]` and `nodes[3]` uses the `InvoiceFeatures` set to the + // `payment_params`, that doesn't support variable length onions. We therefore do not end up + // defaulting to supporting variable length onions, despite `nodes[0]` having no + // `NodeAnnouncementInfo` `features` for `node[3]`. + assert!(!hops[2].node_features.supports_variable_length_onion()); + + let cur_height = nodes[0].best_block_info().1 + 1; + let (onion_payloads, _htlc_msat, _htlc_cltv) = onion_utils::build_onion_payloads(&route.paths[0], 40000, &None, cur_height, &None).unwrap(); + + for onion_payload in onion_payloads.iter() { + match onion_payload.format { + msgs::OnionHopDataFormat::Legacy {..} => {} + _ => { + panic!("Should have only have generated `msgs::OnionHopDataFormat::Legacy` payloads"); + } + } + } +} + macro_rules! get_phantom_route { ($nodes: expr, $amt: expr, $channel: expr) => {{ let secp_ctx = Secp256k1::new(); diff --git a/lightning/src/ln/payment_tests.rs b/lightning/src/ln/payment_tests.rs index 346fb98b..46d5d22b 100644 --- a/lightning/src/ln/payment_tests.rs +++ b/lightning/src/ln/payment_tests.rs @@ -495,7 +495,7 @@ fn do_retry_with_no_persist(confirm_before_reload: bool) { let bs_htlc_claim_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0); assert_eq!(bs_htlc_claim_txn.len(), 1); check_spends!(bs_htlc_claim_txn[0], as_commitment_tx); - expect_payment_forwarded!(nodes[1], None, false); + expect_payment_forwarded!(nodes[1], nodes[0], None, false); if !confirm_before_reload { mine_transaction(&nodes[0], &as_commitment_tx); diff --git a/lightning/src/ln/peer_handler.rs b/lightning/src/ln/peer_handler.rs index d12f8c06..c09df175 100644 --- a/lightning/src/ln/peer_handler.rs +++ b/lightning/src/ln/peer_handler.rs @@ -1707,7 +1707,11 @@ fn is_gossip_msg(type_id: u16) -> bool { match type_id { msgs::ChannelAnnouncement::TYPE | msgs::ChannelUpdate::TYPE | - msgs::NodeAnnouncement::TYPE => true, + msgs::NodeAnnouncement::TYPE | + msgs::QueryChannelRange::TYPE | + msgs::ReplyChannelRange::TYPE | + msgs::QueryShortChannelIds::TYPE | + msgs::ReplyShortChannelIdsEnd::TYPE => true, _ => false } } diff --git a/lightning/src/ln/priv_short_conf_tests.rs b/lightning/src/ln/priv_short_conf_tests.rs index d3d54467..fa44a065 100644 --- a/lightning/src/ln/priv_short_conf_tests.rs +++ b/lightning/src/ln/priv_short_conf_tests.rs @@ -16,8 +16,8 @@ use chain::channelmonitor::ChannelMonitor; use chain::keysinterface::{Recipient, KeysInterface}; use ln::channelmanager::{ChannelManager, ChannelManagerReadArgs, MIN_CLTV_EXPIRY_DELTA}; use routing::network_graph::RoutingFees; -use routing::router::{RouteHint, RouteHintHop}; -use ln::features::InitFeatures; +use routing::router::{PaymentParameters, RouteHint, RouteHintHop}; +use ln::features::{InitFeatures, InvoiceFeatures}; use ln::msgs; use ln::msgs::{ChannelMessageHandler, RoutingMessageHandler, OptionalField}; use util::enforcing_trait_impls::EnforcingSigner; @@ -71,7 +71,10 @@ fn test_priv_forwarding_rejection() { htlc_maximum_msat: None, }]); let last_hops = vec![route_hint]; - let (route, our_payment_hash, our_payment_preimage, our_payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[2], last_hops, 10_000, TEST_FINAL_CLTV); + let payment_params = PaymentParameters::from_node_id(nodes[2].node.get_our_node_id()) + .with_features(InvoiceFeatures::known()) + .with_route_hints(last_hops); + let (route, our_payment_hash, our_payment_preimage, our_payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[2], payment_params, 10_000, TEST_FINAL_CLTV); nodes[0].node.send_payment(&route, our_payment_hash, &Some(our_payment_secret)).unwrap(); check_added_monitors!(nodes[0], 1); @@ -269,7 +272,10 @@ fn test_routed_scid_alias() { htlc_maximum_msat: None, htlc_minimum_msat: None, }])]; - let (route, payment_hash, payment_preimage, payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[2], hop_hints, 100_000, 42); + let payment_params = PaymentParameters::from_node_id(nodes[2].node.get_our_node_id()) + .with_features(InvoiceFeatures::known()) + .with_route_hints(hop_hints); + let (route, payment_hash, payment_preimage, payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[2], payment_params, 100_000, 42); assert_eq!(route.paths[0][1].short_channel_id, last_hop[0].inbound_scid_alias.unwrap()); nodes[0].node.send_payment(&route, payment_hash, &Some(payment_secret)).unwrap(); check_added_monitors!(nodes[0], 1); @@ -419,7 +425,10 @@ fn test_inbound_scid_privacy() { htlc_maximum_msat: None, htlc_minimum_msat: None, }])]; - let (route, payment_hash, payment_preimage, payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[2], hop_hints.clone(), 100_000, 42); + let payment_params = PaymentParameters::from_node_id(nodes[2].node.get_our_node_id()) + .with_features(InvoiceFeatures::known()) + .with_route_hints(hop_hints.clone()); + let (route, payment_hash, payment_preimage, payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[2], payment_params, 100_000, 42); assert_eq!(route.paths[0][1].short_channel_id, last_hop[0].inbound_scid_alias.unwrap()); nodes[0].node.send_payment(&route, payment_hash, &Some(payment_secret)).unwrap(); check_added_monitors!(nodes[0], 1); @@ -431,7 +440,10 @@ fn test_inbound_scid_privacy() { // what channel we're talking about. hop_hints[0].0[0].short_channel_id = last_hop[0].short_channel_id.unwrap(); - let (route_2, payment_hash_2, _, payment_secret_2) = get_route_and_payment_hash!(nodes[0], nodes[2], hop_hints, 100_000, 42); + let payment_params_2 = PaymentParameters::from_node_id(nodes[2].node.get_our_node_id()) + .with_features(InvoiceFeatures::known()) + .with_route_hints(hop_hints); + let (route_2, payment_hash_2, _, payment_secret_2) = get_route_and_payment_hash!(nodes[0], nodes[2], payment_params_2, 100_000, 42); assert_eq!(route_2.paths[0][1].short_channel_id, last_hop[0].short_channel_id.unwrap()); nodes[0].node.send_payment(&route_2, payment_hash_2, &Some(payment_secret_2)).unwrap(); check_added_monitors!(nodes[0], 1); @@ -479,7 +491,10 @@ fn test_scid_alias_returned() { htlc_maximum_msat: None, htlc_minimum_msat: None, }])]; - let (mut route, payment_hash, _, payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[2], hop_hints, 10_000, 42); + let payment_params = PaymentParameters::from_node_id(nodes[2].node.get_our_node_id()) + .with_features(InvoiceFeatures::known()) + .with_route_hints(hop_hints); + let (mut route, payment_hash, _, payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[2], payment_params, 10_000, 42); assert_eq!(route.paths[0][1].short_channel_id, nodes[2].node.list_usable_channels()[0].inbound_scid_alias.unwrap()); route.paths[0][1].fee_msat = 10_000_000; // Overshoot the last channel's value diff --git a/lightning/src/ln/reorg_tests.rs b/lightning/src/ln/reorg_tests.rs index 8eb39cfe..7b36ae0f 100644 --- a/lightning/src/ln/reorg_tests.rs +++ b/lightning/src/ln/reorg_tests.rs @@ -138,7 +138,7 @@ fn do_test_onchain_htlc_reorg(local_commitment: bool, claim: bool) { // ChannelManager only polls chain::Watch::release_pending_monitor_events when we // probe it for events, so we probe non-message events here (which should just be the // PaymentForwarded event). - expect_payment_forwarded!(nodes[1], Some(1000), true); + expect_payment_forwarded!(nodes[1], nodes[0], Some(1000), true); } else { // Confirm the timeout tx and check that we fail the HTLC backwards let block = Block { diff --git a/lightning/src/ln/shutdown_tests.rs b/lightning/src/ln/shutdown_tests.rs index 557aff84..42c0e3b2 100644 --- a/lightning/src/ln/shutdown_tests.rs +++ b/lightning/src/ln/shutdown_tests.rs @@ -110,7 +110,7 @@ fn updates_shutdown_wait() { assert!(updates.update_fee.is_none()); assert_eq!(updates.update_fulfill_htlcs.len(), 1); nodes[1].node.handle_update_fulfill_htlc(&nodes[2].node.get_our_node_id(), &updates.update_fulfill_htlcs[0]); - expect_payment_forwarded!(nodes[1], Some(1000), false); + expect_payment_forwarded!(nodes[1], nodes[0], Some(1000), false); check_added_monitors!(nodes[1], 1); let updates_2 = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()); commitment_signed_dance!(nodes[1], nodes[2], updates.commitment_signed, false); @@ -279,7 +279,7 @@ fn do_test_shutdown_rebroadcast(recv_count: u8) { assert!(updates.update_fee.is_none()); assert_eq!(updates.update_fulfill_htlcs.len(), 1); nodes[1].node.handle_update_fulfill_htlc(&nodes[2].node.get_our_node_id(), &updates.update_fulfill_htlcs[0]); - expect_payment_forwarded!(nodes[1], Some(1000), false); + expect_payment_forwarded!(nodes[1], nodes[0], Some(1000), false); check_added_monitors!(nodes[1], 1); let updates_2 = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()); commitment_signed_dance!(nodes[1], nodes[2], updates.commitment_signed, false); diff --git a/lightning/src/routing/network_graph.rs b/lightning/src/routing/network_graph.rs index 282c8705..39c2894b 100644 --- a/lightning/src/routing/network_graph.rs +++ b/lightning/src/routing/network_graph.rs @@ -1033,6 +1033,15 @@ impl NetworkGraph { } } + /// Clears the `NodeAnnouncementInfo` field for all nodes in the `NetworkGraph` for testing + /// purposes. + #[cfg(test)] + pub fn clear_nodes_announcement_info(&self) { + for node in self.nodes.write().unwrap().iter_mut() { + node.1.announcement_info = None; + } + } + /// For an already known node (from channel announcements), update its stored properties from a /// given node announcement. /// diff --git a/lightning/src/routing/router.rs b/lightning/src/routing/router.rs index 6584eb5f..816dfaad 100644 --- a/lightning/src/routing/router.rs +++ b/lightning/src/routing/router.rs @@ -412,7 +412,7 @@ impl<'a> CandidateRouteHop<'a> { fn effective_capacity(&self) -> EffectiveCapacity { match self { CandidateRouteHop::FirstHop { details } => EffectiveCapacity::ExactLiquidity { - liquidity_msat: details.outbound_capacity_msat, + liquidity_msat: details.next_outbound_htlc_limit_msat, }, CandidateRouteHop::PublicHop { info, .. } => info.effective_capacity(), CandidateRouteHop::PrivateHop { .. } => EffectiveCapacity::Infinite, @@ -602,6 +602,17 @@ fn compute_fees(amount_msat: u64, channel_fees: RoutingFees) -> Option { } } +/// The default `features` we assume for a node in a route, when no `features` are known about that +/// specific node. +/// +/// Default features are: +/// * variable_length_onion_optional +fn default_node_features() -> NodeFeatures { + let mut features = NodeFeatures::empty(); + features.set_variable_length_onion_optional(); + features +} + /// Finds a route from us (payer) to the given target node (payee). /// /// If the payee provided features in their invoice, they should be provided via `params.payee`. @@ -807,7 +818,8 @@ where L::Target: Logger { // We don't want multiple paths (as per MPP) share liquidity of the same channels. // This map allows paths to be aware of the channel use by other paths in the same call. // This would help to make a better path finding decisions and not "overbook" channels. - // It is unaware of the directions (except for `outbound_capacity_msat` in `first_hops`). + // It is unaware of the directions (except for `next_outbound_htlc_limit_msat` in + // `first_hops`). let mut bookkept_channels_liquidity_available_msat = HashMap::with_capacity(network_nodes.len()); // Keeping track of how much value we already collected across other paths. Helps to decide: @@ -830,12 +842,12 @@ where L::Target: Logger { // sort channels above `recommended_value_msat` in ascending order, preferring channels // which have enough, but not too much, capacity for the payment. channels.sort_unstable_by(|chan_a, chan_b| { - if chan_b.outbound_capacity_msat < recommended_value_msat || chan_a.outbound_capacity_msat < recommended_value_msat { + if chan_b.next_outbound_htlc_limit_msat < recommended_value_msat || chan_a.next_outbound_htlc_limit_msat < recommended_value_msat { // Sort in descending order - chan_b.outbound_capacity_msat.cmp(&chan_a.outbound_capacity_msat) + chan_b.next_outbound_htlc_limit_msat.cmp(&chan_a.next_outbound_htlc_limit_msat) } else { // Sort in ascending order - chan_a.outbound_capacity_msat.cmp(&chan_b.outbound_capacity_msat) + chan_a.next_outbound_htlc_limit_msat.cmp(&chan_b.next_outbound_htlc_limit_msat) } }); } @@ -1101,7 +1113,8 @@ where L::Target: Logger { } } } - let empty_node_features = NodeFeatures::empty(); + let default_node_features = default_node_features(); + // Find ways (channels with destination) to reach a given node and store them // in the corresponding data structures (routing graph etc). // $fee_to_target_msat represents how much it costs to reach to this node from the payee, @@ -1132,7 +1145,7 @@ where L::Target: Logger { let features = if let Some(node_info) = $node.announcement_info.as_ref() { &node_info.features } else { - &empty_node_features + &default_node_features }; if !features.requires_unknown_bits() { @@ -1312,7 +1325,7 @@ where L::Target: Logger { // traversing the graph and arrange the path out of what we found. if node_id == our_node_id { let mut new_entry = dist.remove(&our_node_id).unwrap(); - let mut ordered_hops = vec!((new_entry.clone(), NodeFeatures::empty())); + let mut ordered_hops = vec!((new_entry.clone(), default_node_features.clone())); 'path_walk: loop { let mut features_set = false; @@ -1330,7 +1343,7 @@ where L::Target: Logger { if let Some(node_info) = node.announcement_info.as_ref() { ordered_hops.last_mut().unwrap().1 = node_info.features.clone(); } else { - ordered_hops.last_mut().unwrap().1 = NodeFeatures::empty(); + ordered_hops.last_mut().unwrap().1 = default_node_features.clone(); } } else { // We can fill in features for everything except hops which were @@ -1357,7 +1370,7 @@ where L::Target: Logger { // so that fees paid for a HTLC forwarding on the current channel are // associated with the previous channel (where they will be subtracted). ordered_hops.last_mut().unwrap().0.fee_msat = new_entry.hop_use_fee_msat; - ordered_hops.push((new_entry.clone(), NodeFeatures::empty())); + ordered_hops.push((new_entry.clone(), default_node_features.clone())); } ordered_hops.last_mut().unwrap().0.fee_msat = value_contribution_msat; ordered_hops.last_mut().unwrap().0.hop_use_fee_msat = 0; @@ -1684,7 +1697,7 @@ fn add_random_cltv_offset(route: &mut Route, payment_params: &PaymentParameters, #[cfg(test)] mod tests { use routing::network_graph::{NetworkGraph, NetGraphMsgHandler, NodeId}; - use routing::router::{get_route, add_random_cltv_offset, PaymentParameters, Route, RouteHint, RouteHintHop, RouteHop, RoutingFees, DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA}; + use routing::router::{get_route, add_random_cltv_offset, default_node_features, PaymentParameters, Route, RouteHint, RouteHintHop, RouteHop, RoutingFees, DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA}; use routing::scoring::Score; use chain::transaction::OutPoint; use chain::keysinterface::KeysInterface; @@ -1723,6 +1736,8 @@ mod tests { node_id, unspendable_punishment_reserve: 0, forwarding_info: None, + outbound_htlc_minimum_msat: None, + outbound_htlc_maximum_msat: None, }, funding_txo: Some(OutPoint { txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(), index: 0 }), channel_type: None, @@ -1732,12 +1747,15 @@ mod tests { user_channel_id: 0, balance_msat: 0, outbound_capacity_msat, + next_outbound_htlc_limit_msat: outbound_capacity_msat, inbound_capacity_msat: 42, unspendable_punishment_reserve: None, confirmations_required: None, force_close_spend_delay: None, is_outbound: true, is_funding_locked: true, is_usable: true, is_public: true, + inbound_htlc_minimum_msat: None, + inbound_htlc_maximum_msat: None, } } @@ -2785,7 +2803,7 @@ mod tests { assert_eq!(route.paths[0][4].short_channel_id, 8); assert_eq!(route.paths[0][4].fee_msat, 100); assert_eq!(route.paths[0][4].cltv_expiry_delta, 42); - assert_eq!(route.paths[0][4].node_features.le_flags(), &Vec::::new()); // We dont pass flags in from invoices yet + assert_eq!(route.paths[0][4].node_features.le_flags(), default_node_features().le_flags()); // We dont pass flags in from invoices yet assert_eq!(route.paths[0][4].channel_features.le_flags(), &Vec::::new()); // We can't learn any flags from invoices, sadly } @@ -2861,7 +2879,7 @@ mod tests { assert_eq!(route.paths[0][4].short_channel_id, 8); assert_eq!(route.paths[0][4].fee_msat, 100); assert_eq!(route.paths[0][4].cltv_expiry_delta, 42); - assert_eq!(route.paths[0][4].node_features.le_flags(), &Vec::::new()); // We dont pass flags in from invoices yet + assert_eq!(route.paths[0][4].node_features.le_flags(), default_node_features().le_flags()); // We dont pass flags in from invoices yet assert_eq!(route.paths[0][4].channel_features.le_flags(), &Vec::::new()); // We can't learn any flags from invoices, sadly } @@ -2958,7 +2976,7 @@ mod tests { assert_eq!(route.paths[0][3].short_channel_id, last_hops[0].0[1].short_channel_id); assert_eq!(route.paths[0][3].fee_msat, 100); assert_eq!(route.paths[0][3].cltv_expiry_delta, 42); - assert_eq!(route.paths[0][3].node_features.le_flags(), &Vec::::new()); // We dont pass flags in from invoices yet + assert_eq!(route.paths[0][3].node_features.le_flags(), default_node_features().le_flags()); // We dont pass flags in from invoices yet assert_eq!(route.paths[0][3].channel_features.le_flags(), &Vec::::new()); // We can't learn any flags from invoices, sadly } @@ -3023,14 +3041,14 @@ mod tests { assert_eq!(route.paths[0][2].short_channel_id, last_hops[0].0[0].short_channel_id); assert_eq!(route.paths[0][2].fee_msat, 0); assert_eq!(route.paths[0][2].cltv_expiry_delta, 129); - assert_eq!(route.paths[0][2].node_features.le_flags(), &Vec::::new()); // We dont pass flags in from invoices yet + assert_eq!(route.paths[0][2].node_features.le_flags(), default_node_features().le_flags()); // We dont pass flags in from invoices yet assert_eq!(route.paths[0][2].channel_features.le_flags(), &Vec::::new()); // We can't learn any flags from invoices, sadly assert_eq!(route.paths[0][3].pubkey, nodes[6]); assert_eq!(route.paths[0][3].short_channel_id, last_hops[0].0[1].short_channel_id); assert_eq!(route.paths[0][3].fee_msat, 100); assert_eq!(route.paths[0][3].cltv_expiry_delta, 42); - assert_eq!(route.paths[0][3].node_features.le_flags(), &Vec::::new()); // We dont pass flags in from invoices yet + assert_eq!(route.paths[0][3].node_features.le_flags(), default_node_features().le_flags()); // We dont pass flags in from invoices yet assert_eq!(route.paths[0][3].channel_features.le_flags(), &Vec::::new()); // We can't learn any flags from invoices, sadly } @@ -3121,7 +3139,7 @@ mod tests { assert_eq!(route.paths[0][4].short_channel_id, 8); assert_eq!(route.paths[0][4].fee_msat, 100); assert_eq!(route.paths[0][4].cltv_expiry_delta, 42); - assert_eq!(route.paths[0][4].node_features.le_flags(), &Vec::::new()); // We dont pass flags in from invoices yet + assert_eq!(route.paths[0][4].node_features.le_flags(), default_node_features().le_flags()); // We dont pass flags in from invoices yet assert_eq!(route.paths[0][4].channel_features.le_flags(), &Vec::::new()); // We can't learn any flags from invoices, sadly } @@ -3151,7 +3169,7 @@ mod tests { assert_eq!(route.paths[0][1].short_channel_id, 8); assert_eq!(route.paths[0][1].fee_msat, 100); assert_eq!(route.paths[0][1].cltv_expiry_delta, 42); - assert_eq!(route.paths[0][1].node_features.le_flags(), &Vec::::new()); // We dont pass flags in from invoices yet + assert_eq!(route.paths[0][1].node_features.le_flags(), default_node_features().le_flags()); // We dont pass flags in from invoices yet assert_eq!(route.paths[0][1].channel_features.le_flags(), &Vec::::new()); // We can't learn any flags from invoices, sadly last_hops[0].0[0].fees.base_msat = 1000; @@ -3188,7 +3206,7 @@ mod tests { assert_eq!(route.paths[0][3].short_channel_id, 10); assert_eq!(route.paths[0][3].fee_msat, 100); assert_eq!(route.paths[0][3].cltv_expiry_delta, 42); - assert_eq!(route.paths[0][3].node_features.le_flags(), &Vec::::new()); // We dont pass flags in from invoices yet + assert_eq!(route.paths[0][3].node_features.le_flags(), default_node_features().le_flags()); // We dont pass flags in from invoices yet assert_eq!(route.paths[0][3].channel_features.le_flags(), &Vec::::new()); // We can't learn any flags from invoices, sadly // ...but still use 8 for larger payments as 6 has a variable feerate @@ -3229,7 +3247,7 @@ mod tests { assert_eq!(route.paths[0][4].short_channel_id, 8); assert_eq!(route.paths[0][4].fee_msat, 2000); assert_eq!(route.paths[0][4].cltv_expiry_delta, 42); - assert_eq!(route.paths[0][4].node_features.le_flags(), &Vec::::new()); // We dont pass flags in from invoices yet + assert_eq!(route.paths[0][4].node_features.le_flags(), default_node_features().le_flags()); // We dont pass flags in from invoices yet assert_eq!(route.paths[0][4].channel_features.le_flags(), &Vec::::new()); // We can't learn any flags from invoices, sadly } @@ -3281,7 +3299,7 @@ mod tests { assert_eq!(route.paths[0][1].short_channel_id, 8); assert_eq!(route.paths[0][1].fee_msat, 1000000); assert_eq!(route.paths[0][1].cltv_expiry_delta, 42); - assert_eq!(route.paths[0][1].node_features.le_flags(), &[0; 0]); // We dont pass flags in from invoices yet + assert_eq!(route.paths[0][1].node_features.le_flags(), default_node_features().le_flags()); // We dont pass flags in from invoices yet assert_eq!(route.paths[0][1].channel_features.le_flags(), &[0; 0]); // We can't learn any flags from invoices, sadly } @@ -3391,7 +3409,7 @@ mod tests { assert_eq!(path.last().unwrap().fee_msat, 250_000_000); } - // Check that setting outbound_capacity_msat in first_hops limits the channels. + // Check that setting next_outbound_htlc_limit_msat in first_hops limits the channels. // Disable channel #1 and use another first hop. update_channel(&net_graph_msg_handler, &secp_ctx, &our_privkey, UnsignedChannelUpdate { chain_hash: genesis_block(Network::Testnet).header.block_hash(), @@ -3406,7 +3424,7 @@ mod tests { excess_data: Vec::new() }); - // Now, limit the first_hop by the outbound_capacity_msat of 200_000 sats. + // Now, limit the first_hop by the next_outbound_htlc_limit_msat of 200_000 sats. let our_chans = vec![get_channel_details(Some(42), nodes[0].clone(), InitFeatures::from_le_bytes(vec![0b11]), 200_000_000)]; { @@ -5334,8 +5352,9 @@ mod tests { let payment_params = PaymentParameters::from_node_id(dst); let amt = seed as u64 % 200_000_000; let params = ProbabilisticScoringParameters::default(); - let scorer = ProbabilisticScorer::new(params, &graph); - if get_route(src, &payment_params, &graph.read_only(), None, amt, 42, &test_utils::TestLogger::new(), &scorer, &random_seed_bytes).is_ok() { + let logger = test_utils::TestLogger::new(); + let scorer = ProbabilisticScorer::new(params, &graph, &logger); + if get_route(src, &payment_params, &graph.read_only(), None, amt, 42, &logger, &scorer, &random_seed_bytes).is_ok() { continue 'load_endpoints; } } @@ -5370,8 +5389,9 @@ mod tests { let payment_params = PaymentParameters::from_node_id(dst).with_features(InvoiceFeatures::known()); let amt = seed as u64 % 200_000_000; let params = ProbabilisticScoringParameters::default(); - let scorer = ProbabilisticScorer::new(params, &graph); - if get_route(src, &payment_params, &graph.read_only(), None, amt, 42, &test_utils::TestLogger::new(), &scorer, &random_seed_bytes).is_ok() { + let logger = test_utils::TestLogger::new(); + let scorer = ProbabilisticScorer::new(params, &graph, &logger); + if get_route(src, &payment_params, &graph.read_only(), None, amt, 42, &logger, &scorer, &random_seed_bytes).is_ok() { continue 'load_endpoints; } } @@ -5417,6 +5437,7 @@ mod benches { use ln::features::{InitFeatures, InvoiceFeatures}; use routing::scoring::{FixedPenaltyScorer, ProbabilisticScorer, ProbabilisticScoringParameters, Scorer}; use util::logger::{Logger, Record}; + use util::test_utils::TestLogger; use test::Bencher; @@ -5444,6 +5465,8 @@ mod benches { node_id, unspendable_punishment_reserve: 0, forwarding_info: None, + outbound_htlc_minimum_msat: None, + outbound_htlc_maximum_msat: None, }, funding_txo: Some(OutPoint { txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(), index: 0 @@ -5455,6 +5478,7 @@ mod benches { user_channel_id: 0, balance_msat: 10_000_000, outbound_capacity_msat: 10_000_000, + next_outbound_htlc_limit_msat: 10_000_000, inbound_capacity_msat: 0, unspendable_punishment_reserve: None, confirmations_required: None, @@ -5463,6 +5487,8 @@ mod benches { is_funding_locked: true, is_usable: true, is_public: true, + inbound_htlc_minimum_msat: None, + inbound_htlc_maximum_msat: None, } } @@ -5496,17 +5522,19 @@ mod benches { #[bench] fn generate_routes_with_probabilistic_scorer(bench: &mut Bencher) { + let logger = TestLogger::new(); let network_graph = read_network_graph(); let params = ProbabilisticScoringParameters::default(); - let scorer = ProbabilisticScorer::new(params, &network_graph); + let scorer = ProbabilisticScorer::new(params, &network_graph, &logger); generate_routes(bench, &network_graph, scorer, InvoiceFeatures::empty()); } #[bench] fn generate_mpp_routes_with_probabilistic_scorer(bench: &mut Bencher) { + let logger = TestLogger::new(); let network_graph = read_network_graph(); let params = ProbabilisticScoringParameters::default(); - let scorer = ProbabilisticScorer::new(params, &network_graph); + let scorer = ProbabilisticScorer::new(params, &network_graph, &logger); generate_routes(bench, &network_graph, scorer, InvoiceFeatures::known()); } diff --git a/lightning/src/routing/scoring.rs b/lightning/src/routing/scoring.rs index 206cc0a8..0e04c639 100644 --- a/lightning/src/routing/scoring.rs +++ b/lightning/src/routing/scoring.rs @@ -33,14 +33,14 @@ //! # //! // Use the default channel penalties. //! let params = ProbabilisticScoringParameters::default(); -//! let scorer = ProbabilisticScorer::new(params, &network_graph); +//! let scorer = ProbabilisticScorer::new(params, &network_graph, &logger); //! //! // Or use custom channel penalties. //! let params = ProbabilisticScoringParameters { //! liquidity_penalty_multiplier_msat: 2 * 1000, //! ..ProbabilisticScoringParameters::default() //! }; -//! let scorer = ProbabilisticScorer::new(params, &network_graph); +//! let scorer = ProbabilisticScorer::new(params, &network_graph, &logger); //! # let random_seed_bytes = [42u8; 32]; //! //! let route = find_route(&payer, &route_params, &network_graph, None, &logger, &scorer, &random_seed_bytes); @@ -58,8 +58,10 @@ use ln::msgs::DecodeError; use routing::network_graph::{NetworkGraph, NodeId}; use routing::router::RouteHop; use util::ser::{Readable, ReadableArgs, Writeable, Writer}; +use util::logger::Logger; use prelude::*; +use core::fmt; use core::cell::{RefCell, RefMut}; use core::ops::{Deref, DerefMut}; use core::time::Duration; @@ -503,14 +505,15 @@ impl Readable for ChannelFailure { /// behavior. /// /// [1]: https://arxiv.org/abs/2107.05322 -pub type ProbabilisticScorer = ProbabilisticScorerUsingTime::; +pub type ProbabilisticScorer = ProbabilisticScorerUsingTime::; /// Probabilistic [`Score`] implementation. /// /// (C-not exported) generally all users should use the [`ProbabilisticScorer`] type alias. -pub struct ProbabilisticScorerUsingTime, T: Time> { +pub struct ProbabilisticScorerUsingTime, L: Deref, T: Time> where L::Target: Logger { params: ProbabilisticScoringParameters, network_graph: G, + logger: L, // TODO: Remove entries of closed channels. channel_liquidities: HashMap>, } @@ -603,13 +606,14 @@ struct DirectedChannelLiquidity, T: Time, U: Deref, T: Time> ProbabilisticScorerUsingTime { +impl, L: Deref, T: Time> ProbabilisticScorerUsingTime where L::Target: Logger { /// Creates a new scorer using the given scoring parameters for sending payments from a node /// through a network graph. - pub fn new(params: ProbabilisticScoringParameters, network_graph: G) -> Self { + pub fn new(params: ProbabilisticScoringParameters, network_graph: G, logger: L) -> Self { Self { params, network_graph, + logger, channel_liquidities: HashMap::new(), } } @@ -787,22 +791,29 @@ impl, T: Time, U: Deref> DirectedChannelLiqui impl, T: Time, U: DerefMut> DirectedChannelLiquidity { /// Adjusts the channel liquidity balance bounds when failing to route `amount_msat`. - fn failed_at_channel(&mut self, amount_msat: u64) { + fn failed_at_channel(&mut self, amount_msat: u64, chan_descr: fmt::Arguments, logger: &Log) where Log::Target: Logger { if amount_msat < self.max_liquidity_msat() { + log_debug!(logger, "Setting max liquidity of {} to {}", chan_descr, amount_msat); self.set_max_liquidity_msat(amount_msat); + } else { + log_trace!(logger, "Max liquidity of {} already more than {}", chan_descr, amount_msat); } } /// Adjusts the channel liquidity balance bounds when failing to route `amount_msat` downstream. - fn failed_downstream(&mut self, amount_msat: u64) { + fn failed_downstream(&mut self, amount_msat: u64, chan_descr: fmt::Arguments, logger: &Log) where Log::Target: Logger { if amount_msat > self.min_liquidity_msat() { + log_debug!(logger, "Setting min liquidity of {} to {}", chan_descr, amount_msat); self.set_min_liquidity_msat(amount_msat); + } else { + log_trace!(logger, "Min liquidity of {} already less than {}", chan_descr, amount_msat); } } /// Adjusts the channel liquidity balance bounds when successfully routing `amount_msat`. - fn successful(&mut self, amount_msat: u64) { + fn successful(&mut self, amount_msat: u64, chan_descr: fmt::Arguments, logger: &Log) where Log::Target: Logger { let max_liquidity_msat = self.max_liquidity_msat().checked_sub(amount_msat).unwrap_or(0); + log_debug!(logger, "Subtracting {} from max liquidity of {} (setting it to {})", amount_msat, chan_descr, max_liquidity_msat); self.set_max_liquidity_msat(max_liquidity_msat); } @@ -829,7 +840,7 @@ impl, T: Time, U: DerefMut> DirectedChanne } } -impl, T: Time> Score for ProbabilisticScorerUsingTime { +impl, L: Deref, T: Time> Score for ProbabilisticScorerUsingTime where L::Target: Logger { fn channel_penalty_msat( &self, short_channel_id: u64, amount_msat: u64, capacity_msat: u64, source: &NodeId, target: &NodeId @@ -845,13 +856,18 @@ impl, T: Time> Score for ProbabilisticScorerUsin fn payment_path_failed(&mut self, path: &[&RouteHop], short_channel_id: u64) { let amount_msat = path.split_last().map(|(hop, _)| hop.fee_msat).unwrap_or(0); let liquidity_offset_half_life = self.params.liquidity_offset_half_life; + log_trace!(self.logger, "Scoring path through to SCID {} as having failed at {} msat", short_channel_id, amount_msat); let network_graph = self.network_graph.read_only(); - for hop in path { + for (hop_idx, hop) in path.iter().enumerate() { let target = NodeId::from_pubkey(&hop.pubkey); let channel_directed_from_source = network_graph.channels() .get(&hop.short_channel_id) .and_then(|channel| channel.as_directed_to(&target)); + if hop.short_channel_id == short_channel_id && hop_idx == 0 { + log_warn!(self.logger, "Payment failed at the first hop - we do not attempt to learn channel info in such cases as we can directly observe local state.\n\tBecause we know the local state, we should generally not see failures here - this may be an indication that your channel peer on channel {} is broken and you may wish to close the channel.", hop.short_channel_id); + } + // Only score announced channels. if let Some((channel, source)) = channel_directed_from_source { let capacity_msat = channel.effective_capacity().as_msat(); @@ -860,7 +876,7 @@ impl, T: Time> Score for ProbabilisticScorerUsin .entry(hop.short_channel_id) .or_insert_with(ChannelLiquidity::new) .as_directed_mut(source, &target, capacity_msat, liquidity_offset_half_life) - .failed_at_channel(amount_msat); + .failed_at_channel(amount_msat, format_args!("SCID {}, towards {:?}", hop.short_channel_id, target), &self.logger); break; } @@ -868,7 +884,10 @@ impl, T: Time> Score for ProbabilisticScorerUsin .entry(hop.short_channel_id) .or_insert_with(ChannelLiquidity::new) .as_directed_mut(source, &target, capacity_msat, liquidity_offset_half_life) - .failed_downstream(amount_msat); + .failed_downstream(amount_msat, format_args!("SCID {}, towards {:?}", hop.short_channel_id, target), &self.logger); + } else { + log_debug!(self.logger, "Not able to penalize channel with SCID {} as we do not have graph info for it (likely a route-hint last-hop).", + hop.short_channel_id); } } } @@ -876,6 +895,8 @@ impl, T: Time> Score for ProbabilisticScorerUsin fn payment_path_successful(&mut self, path: &[&RouteHop]) { let amount_msat = path.split_last().map(|(hop, _)| hop.fee_msat).unwrap_or(0); let liquidity_offset_half_life = self.params.liquidity_offset_half_life; + log_trace!(self.logger, "Scoring path through SCID {} as having succeeded at {} msat.", + path.split_last().map(|(hop, _)| hop.short_channel_id).unwrap_or(0), amount_msat); let network_graph = self.network_graph.read_only(); for hop in path { let target = NodeId::from_pubkey(&hop.pubkey); @@ -890,7 +911,10 @@ impl, T: Time> Score for ProbabilisticScorerUsin .entry(hop.short_channel_id) .or_insert_with(ChannelLiquidity::new) .as_directed_mut(source, &target, capacity_msat, liquidity_offset_half_life) - .successful(amount_msat); + .successful(amount_msat, format_args!("SCID {}, towards {:?}", hop.short_channel_id, target), &self.logger); + } else { + log_debug!(self.logger, "Not able to learn for channel with SCID {} as we do not have graph info for it (likely a route-hint last-hop).", + hop.short_channel_id); } } } @@ -1206,7 +1230,7 @@ mod approx { } } -impl, T: Time> Writeable for ProbabilisticScorerUsingTime { +impl, L: Deref, T: Time> Writeable for ProbabilisticScorerUsingTime where L::Target: Logger { #[inline] fn write(&self, w: &mut W) -> Result<(), io::Error> { write_tlv_fields!(w, { @@ -1216,13 +1240,13 @@ impl, T: Time> Writeable for ProbabilisticScorer } } -impl, T: Time> -ReadableArgs<(ProbabilisticScoringParameters, G)> for ProbabilisticScorerUsingTime { +impl, L: Deref, T: Time> +ReadableArgs<(ProbabilisticScoringParameters, G, L)> for ProbabilisticScorerUsingTime where L::Target: Logger { #[inline] fn read( - r: &mut R, args: (ProbabilisticScoringParameters, G) + r: &mut R, args: (ProbabilisticScoringParameters, G, L) ) -> Result { - let (params, network_graph) = args; + let (params, network_graph, logger) = args; let mut channel_liquidities = HashMap::new(); read_tlv_fields!(r, { (0, channel_liquidities, required) @@ -1230,6 +1254,7 @@ ReadableArgs<(ProbabilisticScoringParameters, G)> for ProbabilisticScorerUsingTi Ok(Self { params, network_graph, + logger, channel_liquidities, }) } @@ -1351,6 +1376,7 @@ mod tests { use routing::network_graph::{NetworkGraph, NodeId}; use routing::router::RouteHop; use util::ser::{Readable, ReadableArgs, Writeable}; + use util::test_utils::TestLogger; use bitcoin::blockdata::constants::genesis_block; use bitcoin::hashes::Hash; @@ -1695,7 +1721,7 @@ mod tests { // `ProbabilisticScorer` tests /// A probabilistic scorer for testing with time that can be manually advanced. - type ProbabilisticScorer<'a> = ProbabilisticScorerUsingTime::<&'a NetworkGraph, SinceEpoch>; + type ProbabilisticScorer<'a> = ProbabilisticScorerUsingTime::<&'a NetworkGraph, &'a TestLogger, SinceEpoch>; fn sender_privkey() -> SecretKey { SecretKey::from_slice(&[41; 32]).unwrap() @@ -1821,10 +1847,11 @@ mod tests { #[test] fn liquidity_bounds_directed_from_lowest_node_id() { + let logger = TestLogger::new(); let last_updated = SinceEpoch::now(); let network_graph = network_graph(); let params = ProbabilisticScoringParameters::default(); - let mut scorer = ProbabilisticScorer::new(params, &network_graph) + let mut scorer = ProbabilisticScorer::new(params, &network_graph, &logger) .with_channel(42, ChannelLiquidity { min_liquidity_offset_msat: 700, max_liquidity_offset_msat: 100, last_updated @@ -1895,10 +1922,11 @@ mod tests { #[test] fn resets_liquidity_upper_bound_when_crossed_by_lower_bound() { + let logger = TestLogger::new(); let last_updated = SinceEpoch::now(); let network_graph = network_graph(); let params = ProbabilisticScoringParameters::default(); - let mut scorer = ProbabilisticScorer::new(params, &network_graph) + let mut scorer = ProbabilisticScorer::new(params, &network_graph, &logger) .with_channel(42, ChannelLiquidity { min_liquidity_offset_msat: 200, max_liquidity_offset_msat: 400, last_updated @@ -1952,10 +1980,11 @@ mod tests { #[test] fn resets_liquidity_lower_bound_when_crossed_by_upper_bound() { + let logger = TestLogger::new(); let last_updated = SinceEpoch::now(); let network_graph = network_graph(); let params = ProbabilisticScoringParameters::default(); - let mut scorer = ProbabilisticScorer::new(params, &network_graph) + let mut scorer = ProbabilisticScorer::new(params, &network_graph, &logger) .with_channel(42, ChannelLiquidity { min_liquidity_offset_msat: 200, max_liquidity_offset_msat: 400, last_updated @@ -2009,12 +2038,13 @@ mod tests { #[test] fn increased_penalty_nearing_liquidity_upper_bound() { + let logger = TestLogger::new(); let network_graph = network_graph(); let params = ProbabilisticScoringParameters { liquidity_penalty_multiplier_msat: 1_000, ..ProbabilisticScoringParameters::zero_penalty() }; - let scorer = ProbabilisticScorer::new(params, &network_graph); + let scorer = ProbabilisticScorer::new(params, &network_graph, &logger); let source = source_node_id(); let target = target_node_id(); @@ -2034,13 +2064,14 @@ mod tests { #[test] fn constant_penalty_outside_liquidity_bounds() { + let logger = TestLogger::new(); let last_updated = SinceEpoch::now(); let network_graph = network_graph(); let params = ProbabilisticScoringParameters { liquidity_penalty_multiplier_msat: 1_000, ..ProbabilisticScoringParameters::zero_penalty() }; - let scorer = ProbabilisticScorer::new(params, &network_graph) + let scorer = ProbabilisticScorer::new(params, &network_graph, &logger) .with_channel(42, ChannelLiquidity { min_liquidity_offset_msat: 40, max_liquidity_offset_msat: 40, last_updated @@ -2056,12 +2087,13 @@ mod tests { #[test] fn does_not_further_penalize_own_channel() { + let logger = TestLogger::new(); let network_graph = network_graph(); let params = ProbabilisticScoringParameters { liquidity_penalty_multiplier_msat: 1_000, ..ProbabilisticScoringParameters::zero_penalty() }; - let mut scorer = ProbabilisticScorer::new(params, &network_graph); + let mut scorer = ProbabilisticScorer::new(params, &network_graph, &logger); let sender = sender_node_id(); let source = source_node_id(); let failed_path = payment_path_for_amount(500); @@ -2078,12 +2110,13 @@ mod tests { #[test] fn sets_liquidity_lower_bound_on_downstream_failure() { + let logger = TestLogger::new(); let network_graph = network_graph(); let params = ProbabilisticScoringParameters { liquidity_penalty_multiplier_msat: 1_000, ..ProbabilisticScoringParameters::zero_penalty() }; - let mut scorer = ProbabilisticScorer::new(params, &network_graph); + let mut scorer = ProbabilisticScorer::new(params, &network_graph, &logger); let source = source_node_id(); let target = target_node_id(); let path = payment_path_for_amount(500); @@ -2101,12 +2134,13 @@ mod tests { #[test] fn sets_liquidity_upper_bound_on_failure() { + let logger = TestLogger::new(); let network_graph = network_graph(); let params = ProbabilisticScoringParameters { liquidity_penalty_multiplier_msat: 1_000, ..ProbabilisticScoringParameters::zero_penalty() }; - let mut scorer = ProbabilisticScorer::new(params, &network_graph); + let mut scorer = ProbabilisticScorer::new(params, &network_graph, &logger); let source = source_node_id(); let target = target_node_id(); let path = payment_path_for_amount(500); @@ -2124,12 +2158,13 @@ mod tests { #[test] fn reduces_liquidity_upper_bound_along_path_on_success() { + let logger = TestLogger::new(); let network_graph = network_graph(); let params = ProbabilisticScoringParameters { liquidity_penalty_multiplier_msat: 1_000, ..ProbabilisticScoringParameters::zero_penalty() }; - let mut scorer = ProbabilisticScorer::new(params, &network_graph); + let mut scorer = ProbabilisticScorer::new(params, &network_graph, &logger); let sender = sender_node_id(); let source = source_node_id(); let target = target_node_id(); @@ -2149,13 +2184,14 @@ mod tests { #[test] fn decays_liquidity_bounds_over_time() { + let logger = TestLogger::new(); let network_graph = network_graph(); let params = ProbabilisticScoringParameters { liquidity_penalty_multiplier_msat: 1_000, liquidity_offset_half_life: Duration::from_secs(10), ..ProbabilisticScoringParameters::zero_penalty() }; - let mut scorer = ProbabilisticScorer::new(params, &network_graph); + let mut scorer = ProbabilisticScorer::new(params, &network_graph, &logger); let source = source_node_id(); let target = target_node_id(); @@ -2201,13 +2237,14 @@ mod tests { #[test] fn decays_liquidity_bounds_without_shift_overflow() { + let logger = TestLogger::new(); let network_graph = network_graph(); let params = ProbabilisticScoringParameters { liquidity_penalty_multiplier_msat: 1_000, liquidity_offset_half_life: Duration::from_secs(10), ..ProbabilisticScoringParameters::zero_penalty() }; - let mut scorer = ProbabilisticScorer::new(params, &network_graph); + let mut scorer = ProbabilisticScorer::new(params, &network_graph, &logger); let source = source_node_id(); let target = target_node_id(); assert_eq!(scorer.channel_penalty_msat(42, 256, 1_024, &source, &target), 125); @@ -2226,13 +2263,14 @@ mod tests { #[test] fn restricts_liquidity_bounds_after_decay() { + let logger = TestLogger::new(); let network_graph = network_graph(); let params = ProbabilisticScoringParameters { liquidity_penalty_multiplier_msat: 1_000, liquidity_offset_half_life: Duration::from_secs(10), ..ProbabilisticScoringParameters::zero_penalty() }; - let mut scorer = ProbabilisticScorer::new(params, &network_graph); + let mut scorer = ProbabilisticScorer::new(params, &network_graph, &logger); let source = source_node_id(); let target = target_node_id(); @@ -2264,13 +2302,14 @@ mod tests { #[test] fn restores_persisted_liquidity_bounds() { + let logger = TestLogger::new(); let network_graph = network_graph(); let params = ProbabilisticScoringParameters { liquidity_penalty_multiplier_msat: 1_000, liquidity_offset_half_life: Duration::from_secs(10), ..ProbabilisticScoringParameters::zero_penalty() }; - let mut scorer = ProbabilisticScorer::new(params, &network_graph); + let mut scorer = ProbabilisticScorer::new(params, &network_graph, &logger); let source = source_node_id(); let target = target_node_id(); @@ -2288,19 +2327,20 @@ mod tests { let mut serialized_scorer = io::Cursor::new(&serialized_scorer); let deserialized_scorer = - ::read(&mut serialized_scorer, (params, &network_graph)).unwrap(); + ::read(&mut serialized_scorer, (params, &network_graph, &logger)).unwrap(); assert_eq!(deserialized_scorer.channel_penalty_msat(42, 500, 1_000, &source, &target), 300); } #[test] fn decays_persisted_liquidity_bounds() { + let logger = TestLogger::new(); let network_graph = network_graph(); let params = ProbabilisticScoringParameters { liquidity_penalty_multiplier_msat: 1_000, liquidity_offset_half_life: Duration::from_secs(10), ..ProbabilisticScoringParameters::zero_penalty() }; - let mut scorer = ProbabilisticScorer::new(params, &network_graph); + let mut scorer = ProbabilisticScorer::new(params, &network_graph, &logger); let source = source_node_id(); let target = target_node_id(); @@ -2314,7 +2354,7 @@ mod tests { let mut serialized_scorer = io::Cursor::new(&serialized_scorer); let deserialized_scorer = - ::read(&mut serialized_scorer, (params, &network_graph)).unwrap(); + ::read(&mut serialized_scorer, (params, &network_graph, &logger)).unwrap(); assert_eq!(deserialized_scorer.channel_penalty_msat(42, 500, 1_000, &source, &target), 473); scorer.payment_path_failed(&payment_path_for_amount(250).iter().collect::>(), 43); @@ -2328,9 +2368,10 @@ mod tests { fn scores_realistic_payments() { // Shows the scores of "realistic" sends of 100k sats over channels of 1-10m sats (with a // 50k sat reserve). + let logger = TestLogger::new(); let network_graph = network_graph(); let params = ProbabilisticScoringParameters::default(); - let scorer = ProbabilisticScorer::new(params, &network_graph); + let scorer = ProbabilisticScorer::new(params, &network_graph, &logger); let source = source_node_id(); let target = target_node_id(); @@ -2349,6 +2390,7 @@ mod tests { #[test] fn adds_base_penalty_to_liquidity_penalty() { + let logger = TestLogger::new(); let network_graph = network_graph(); let source = source_node_id(); let target = target_node_id(); @@ -2357,18 +2399,19 @@ mod tests { liquidity_penalty_multiplier_msat: 1_000, ..ProbabilisticScoringParameters::zero_penalty() }; - let scorer = ProbabilisticScorer::new(params, &network_graph); + let scorer = ProbabilisticScorer::new(params, &network_graph, &logger); assert_eq!(scorer.channel_penalty_msat(42, 128, 1_024, &source, &target), 58); let params = ProbabilisticScoringParameters { base_penalty_msat: 500, liquidity_penalty_multiplier_msat: 1_000, ..Default::default() }; - let scorer = ProbabilisticScorer::new(params, &network_graph); + let scorer = ProbabilisticScorer::new(params, &network_graph, &logger); assert_eq!(scorer.channel_penalty_msat(42, 128, 1_024, &source, &target), 558); } #[test] fn adds_amount_penalty_to_liquidity_penalty() { + let logger = TestLogger::new(); let network_graph = network_graph(); let source = source_node_id(); let target = target_node_id(); @@ -2378,7 +2421,7 @@ mod tests { amount_penalty_multiplier_msat: 0, ..ProbabilisticScoringParameters::zero_penalty() }; - let scorer = ProbabilisticScorer::new(params, &network_graph); + let scorer = ProbabilisticScorer::new(params, &network_graph, &logger); assert_eq!(scorer.channel_penalty_msat(42, 512_000, 1_024_000, &source, &target), 300); let params = ProbabilisticScoringParameters { @@ -2386,12 +2429,13 @@ mod tests { amount_penalty_multiplier_msat: 256, ..ProbabilisticScoringParameters::zero_penalty() }; - let scorer = ProbabilisticScorer::new(params, &network_graph); + let scorer = ProbabilisticScorer::new(params, &network_graph, &logger); assert_eq!(scorer.channel_penalty_msat(42, 512_000, 1_024_000, &source, &target), 337); } #[test] fn calculates_log10_without_overflowing_u64_max_value() { + let logger = TestLogger::new(); let network_graph = network_graph(); let source = source_node_id(); let target = target_node_id(); @@ -2400,7 +2444,7 @@ mod tests { liquidity_penalty_multiplier_msat: 40_000, ..ProbabilisticScoringParameters::zero_penalty() }; - let scorer = ProbabilisticScorer::new(params, &network_graph); + let scorer = ProbabilisticScorer::new(params, &network_graph, &logger); assert_eq!( scorer.channel_penalty_msat(42, u64::max_value(), u64::max_value(), &source, &target), 80_000, diff --git a/lightning/src/util/events.rs b/lightning/src/util/events.rs index ea50398b..f35dc14e 100644 --- a/lightning/src/util/events.rs +++ b/lightning/src/util/events.rs @@ -230,6 +230,47 @@ pub enum Event { /// [`Route::get_total_fees`]: crate::routing::router::Route::get_total_fees fee_paid_msat: Option, }, + /// Indicates an outbound payment failed. Individual [`Event::PaymentPathFailed`] events + /// provide failure information for each MPP part in the payment. + /// + /// This event is provided once there are no further pending HTLCs for the payment and the + /// payment is no longer retryable, either due to a several-block timeout or because + /// [`ChannelManager::abandon_payment`] was previously called for the corresponding payment. + /// + /// [`ChannelManager::abandon_payment`]: crate::ln::channelmanager::ChannelManager::abandon_payment + PaymentFailed { + /// The id returned by [`ChannelManager::send_payment`] and used with + /// [`ChannelManager::retry_payment`] and [`ChannelManager::abandon_payment`]. + /// + /// [`ChannelManager::send_payment`]: crate::ln::channelmanager::ChannelManager::send_payment + /// [`ChannelManager::retry_payment`]: crate::ln::channelmanager::ChannelManager::retry_payment + /// [`ChannelManager::abandon_payment`]: crate::ln::channelmanager::ChannelManager::abandon_payment + payment_id: PaymentId, + /// The hash that was given to [`ChannelManager::send_payment`]. + /// + /// [`ChannelManager::send_payment`]: crate::ln::channelmanager::ChannelManager::send_payment + payment_hash: PaymentHash, + }, + /// Indicates that a path for an outbound payment was successful. + /// + /// Always generated after [`Event::PaymentSent`] and thus useful for scoring channels. See + /// [`Event::PaymentSent`] for obtaining the payment preimage. + PaymentPathSuccessful { + /// The id returned by [`ChannelManager::send_payment`] and used with + /// [`ChannelManager::retry_payment`]. + /// + /// [`ChannelManager::send_payment`]: crate::ln::channelmanager::ChannelManager::send_payment + /// [`ChannelManager::retry_payment`]: crate::ln::channelmanager::ChannelManager::retry_payment + payment_id: PaymentId, + /// The hash that was given to [`ChannelManager::send_payment`]. + /// + /// [`ChannelManager::send_payment`]: crate::ln::channelmanager::ChannelManager::send_payment + payment_hash: Option, + /// The payment path that was successful. + /// + /// May contain a closed channel if the HTLC sent along the path was fulfilled on chain. + path: Vec, + }, /// Indicates an outbound HTLC we sent failed. Probably some intermediary node dropped /// something. You may wish to retry with a different route. /// @@ -299,27 +340,6 @@ pub enum Event { #[cfg(test)] error_data: Option>, }, - /// Indicates an outbound payment failed. Individual [`Event::PaymentPathFailed`] events - /// provide failure information for each MPP part in the payment. - /// - /// This event is provided once there are no further pending HTLCs for the payment and the - /// payment is no longer retryable, either due to a several-block timeout or because - /// [`ChannelManager::abandon_payment`] was previously called for the corresponding payment. - /// - /// [`ChannelManager::abandon_payment`]: crate::ln::channelmanager::ChannelManager::abandon_payment - PaymentFailed { - /// The id returned by [`ChannelManager::send_payment`] and used with - /// [`ChannelManager::retry_payment`] and [`ChannelManager::abandon_payment`]. - /// - /// [`ChannelManager::send_payment`]: crate::ln::channelmanager::ChannelManager::send_payment - /// [`ChannelManager::retry_payment`]: crate::ln::channelmanager::ChannelManager::retry_payment - /// [`ChannelManager::abandon_payment`]: crate::ln::channelmanager::ChannelManager::abandon_payment - payment_id: PaymentId, - /// The hash that was given to [`ChannelManager::send_payment`]. - /// - /// [`ChannelManager::send_payment`]: crate::ln::channelmanager::ChannelManager::send_payment - payment_hash: PaymentHash, - }, /// Used to indicate that [`ChannelManager::process_pending_htlc_forwards`] should be called at /// a time in the future. /// @@ -343,6 +363,9 @@ pub enum Event { /// This event is generated when a payment has been successfully forwarded through us and a /// forwarding fee earned. PaymentForwarded { + /// The channel between the source node and us. Optional because versions prior to 0.0.107 + /// do not serialize this field. + source_channel_id: Option<[u8; 32]>, /// The fee, in milli-satoshis, which was earned as a result of the payment. /// /// Note that if we force-closed the channel over which we forwarded an HTLC while the HTLC @@ -387,26 +410,6 @@ pub enum Event { /// The full transaction received from the user transaction: Transaction }, - /// Indicates that a path for an outbound payment was successful. - /// - /// Always generated after [`Event::PaymentSent`] and thus useful for scoring channels. See - /// [`Event::PaymentSent`] for obtaining the payment preimage. - PaymentPathSuccessful { - /// The id returned by [`ChannelManager::send_payment`] and used with - /// [`ChannelManager::retry_payment`]. - /// - /// [`ChannelManager::send_payment`]: crate::ln::channelmanager::ChannelManager::send_payment - /// [`ChannelManager::retry_payment`]: crate::ln::channelmanager::ChannelManager::retry_payment - payment_id: PaymentId, - /// The hash that was given to [`ChannelManager::send_payment`]. - /// - /// [`ChannelManager::send_payment`]: crate::ln::channelmanager::ChannelManager::send_payment - payment_hash: Option, - /// The payment path that was successful. - /// - /// May contain a closed channel if the HTLC sent along the path was fulfilled on chain. - path: Vec, - }, /// Indicates a request to open a new channel by a peer. /// /// To accept the request, call [`ChannelManager::accept_inbound_channel`]. To reject the @@ -520,10 +523,11 @@ impl Writeable for Event { (0, VecWriteWrapper(outputs), required), }); }, - &Event::PaymentForwarded { fee_earned_msat, claim_from_onchain_tx } => { + &Event::PaymentForwarded { fee_earned_msat, source_channel_id, claim_from_onchain_tx } => { 7u8.write(writer)?; write_tlv_fields!(writer, { (0, fee_earned_msat, option), + (1, source_channel_id, option), (2, claim_from_onchain_tx, required), }); }, @@ -684,12 +688,14 @@ impl MaybeReadable for Event { 7u8 => { let f = || { let mut fee_earned_msat = None; + let mut source_channel_id = None; let mut claim_from_onchain_tx = false; read_tlv_fields!(reader, { (0, fee_earned_msat, option), + (1, source_channel_id, option), (2, claim_from_onchain_tx, required), }); - Ok(Some(Event::PaymentForwarded { fee_earned_msat, claim_from_onchain_tx })) + Ok(Some(Event::PaymentForwarded { fee_earned_msat, source_channel_id, claim_from_onchain_tx })) }; f() }, diff --git a/lightning/src/util/mod.rs b/lightning/src/util/mod.rs index a1e92a0f..95826b7e 100644 --- a/lightning/src/util/mod.rs +++ b/lightning/src/util/mod.rs @@ -20,6 +20,7 @@ pub mod errors; pub mod ser; pub mod message_signing; pub mod invoice; +pub mod persist; pub(crate) mod atomic_counter; pub(crate) mod byte_utils; diff --git a/lightning/src/util/persist.rs b/lightning/src/util/persist.rs new file mode 100644 index 00000000..9476331c --- /dev/null +++ b/lightning/src/util/persist.rs @@ -0,0 +1,77 @@ +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +//! This module contains a simple key-value store trait KVStorePersister that +//! allows one to implement the persistence for [`ChannelManager`], [`NetworkGraph`], +//! and [`ChannelMonitor`] all in one place. + +use core::ops::Deref; +use bitcoin::hashes::hex::ToHex; +use io::{self}; + +use crate::{chain::{keysinterface::{Sign, KeysInterface}, self, transaction::{OutPoint}, chaininterface::{BroadcasterInterface, FeeEstimator}, chainmonitor::{Persist, MonitorUpdateId}, channelmonitor::{ChannelMonitor, ChannelMonitorUpdate}}, ln::channelmanager::ChannelManager, routing::network_graph::NetworkGraph}; +use super::{logger::Logger, ser::Writeable}; + +/// Trait for a key-value store for persisting some writeable object at some key +/// Implementing `KVStorePersister` provides auto-implementations for [`Persister`] +/// and [`Persist`] traits. It uses "manager", "network_graph", +/// and "monitors/{funding_txo_id}_{funding_txo_index}" for keys. +pub trait KVStorePersister { + /// Persist the given writeable using the provided key + fn persist(&self, key: &str, object: &W) -> io::Result<()>; +} + +/// Trait that handles persisting a [`ChannelManager`] and [`NetworkGraph`] to disk. +pub trait Persister + where M::Target: 'static + chain::Watch, + T::Target: 'static + BroadcasterInterface, + K::Target: 'static + KeysInterface, + F::Target: 'static + FeeEstimator, + L::Target: 'static + Logger, +{ + /// Persist the given ['ChannelManager'] to disk, returning an error if persistence failed. + fn persist_manager(&self, channel_manager: &ChannelManager) -> Result<(), io::Error>; + + /// Persist the given [`NetworkGraph`] to disk, returning an error if persistence failed. + fn persist_graph(&self, network_graph: &NetworkGraph) -> Result<(), io::Error>; +} + +impl Persister for A + where M::Target: 'static + chain::Watch, + T::Target: 'static + BroadcasterInterface, + K::Target: 'static + KeysInterface, + F::Target: 'static + FeeEstimator, + L::Target: 'static + Logger, +{ + /// Persist the given ['ChannelManager'] to disk, returning an error if persistence failed. + fn persist_manager(&self, channel_manager: &ChannelManager) -> Result<(), io::Error> { + self.persist("manager", channel_manager) + } + + /// Persist the given [`NetworkGraph`] to disk, returning an error if persistence failed. + fn persist_graph(&self, network_graph: &NetworkGraph) -> Result<(), io::Error> { + self.persist("network_graph", network_graph) + } +} + +impl Persist for K { + // TODO: We really need a way for the persister to inform the user that its time to crash/shut + // down once these start returning failure. + // A PermanentFailure implies we need to shut down since we're force-closing channels without + // even broadcasting! + + fn persist_new_channel(&self, funding_txo: OutPoint, monitor: &ChannelMonitor, _update_id: MonitorUpdateId) -> Result<(), chain::ChannelMonitorUpdateErr> { + let key = format!("monitors/{}_{}", funding_txo.txid.to_hex(), funding_txo.index); + self.persist(&key, monitor) + .map_err(|_| chain::ChannelMonitorUpdateErr::PermanentFailure) + } + + fn update_persisted_channel(&self, funding_txo: OutPoint, _update: &Option, monitor: &ChannelMonitor, _update_id: MonitorUpdateId) -> Result<(), chain::ChannelMonitorUpdateErr> { + let key = format!("monitors/{}_{}", funding_txo.txid.to_hex(), funding_txo.index); + self.persist(&key, monitor) + .map_err(|_| chain::ChannelMonitorUpdateErr::PermanentFailure) + } +}