From ec8c6fb2a922a36e9a9a28066160d4f3a096a06b Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Wed, 1 Nov 2023 13:12:44 -0500 Subject: [PATCH 01/19] Add peel_onion_message method to OnionMessenger When testing OnionMessenger in functional tests, it would be useful to examine the contents of an OnionMessage response. Expose the standalone peel_onion_message on OnionMessenger to facilitate this. --- lightning/src/onion_message/messenger.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lightning/src/onion_message/messenger.rs b/lightning/src/onion_message/messenger.rs index 5dc3bb422..812429f90 100644 --- a/lightning/src/onion_message/messenger.rs +++ b/lightning/src/onion_message/messenger.rs @@ -814,6 +814,14 @@ where self.enqueue_onion_message(path, contents, reply_path, format_args!("")) } + pub(crate) fn peel_onion_message( + &self, msg: &OnionMessage + ) -> Result::Target as CustomOnionMessageHandler>::CustomMessage>, ()> { + peel_onion_message( + msg, &self.secp_ctx, &*self.node_signer, &*self.logger, &*self.custom_handler + ) + } + fn handle_onion_message_response( &self, response: Option, reply_path: Option, log_suffix: fmt::Arguments ) { @@ -899,9 +907,7 @@ where CMH::Target: CustomOnionMessageHandler, { fn handle_onion_message(&self, _peer_node_id: &PublicKey, msg: &OnionMessage) { - match peel_onion_message( - msg, &self.secp_ctx, &*self.node_signer, &*self.logger, &*self.custom_handler - ) { + match self.peel_onion_message(msg) { Ok(PeeledOnion::Receive(message, path_id, reply_path)) => { log_trace!( self.logger, From 911ed6dafcc3eca3a86b9bebc57489795ad44876 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Fri, 27 Oct 2023 10:39:41 -0500 Subject: [PATCH 02/19] Fix import order in functional_test_utils.rs --- lightning/src/ln/functional_test_utils.rs | 26 +++++++++++------------ 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index d153a0a4c..dd7b7c2cd 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -11,27 +11,27 @@ //! nodes for functional tests. use crate::chain::{BestBlock, ChannelMonitorUpdateStatus, Confirm, Listen, Watch, chainmonitor::Persist}; -use crate::sign::EntropySource; use crate::chain::channelmonitor::ChannelMonitor; use crate::chain::transaction::OutPoint; use crate::events::{ClaimedHTLC, ClosureReason, Event, HTLCDestination, MessageSendEvent, MessageSendEventsProvider, PathFailure, PaymentPurpose, PaymentFailureReason}; use crate::events::bump_transaction::{BumpTransactionEvent, BumpTransactionEventHandler, Wallet, WalletSource}; use crate::ln::{ChannelId, PaymentPreimage, PaymentHash, PaymentSecret}; use crate::ln::channelmanager::{AChannelManager, ChainParameters, ChannelManager, ChannelManagerReadArgs, RAACommitmentOrder, PaymentSendFailure, RecipientOnionFields, PaymentId, MIN_CLTV_EXPIRY_DELTA}; -use crate::routing::gossip::{P2PGossipSync, NetworkGraph, NetworkUpdate}; -use crate::routing::router::{self, PaymentParameters, Route, RouteParameters}; use crate::ln::features::InitFeatures; use crate::ln::msgs; use crate::ln::msgs::{ChannelMessageHandler,RoutingMessageHandler}; -use crate::util::test_channel_signer::TestChannelSigner; -use crate::util::scid_utils; -use crate::util::test_utils; -use crate::util::test_utils::{panicking, TestChainMonitor, TestScorer, TestKeysInterface}; -use crate::util::errors::APIError; +use crate::routing::gossip::{P2PGossipSync, NetworkGraph, NetworkUpdate}; +use crate::routing::router::{self, PaymentParameters, Route, RouteParameters}; +use crate::sign::EntropySource; use crate::util::config::{UserConfig, MaxDustHTLCExposure}; -use crate::util::ser::{ReadableArgs, Writeable}; +use crate::util::errors::APIError; #[cfg(test)] use crate::util::logger::Logger; +use crate::util::scid_utils; +use crate::util::test_channel_signer::TestChannelSigner; +use crate::util::test_utils; +use crate::util::test_utils::{panicking, TestChainMonitor, TestScorer, TestKeysInterface}; +use crate::util::ser::{ReadableArgs, Writeable}; use bitcoin::blockdata::block::{Block, Header, Version}; use bitcoin::blockdata::locktime::absolute::LockTime; @@ -43,13 +43,13 @@ use bitcoin::network::constants::Network; use bitcoin::pow::CompactTarget; use bitcoin::secp256k1::{PublicKey, SecretKey}; +use alloc::rc::Rc; +use core::cell::RefCell; +use core::iter::repeat; +use core::mem; use crate::io; use crate::prelude::*; -use core::cell::RefCell; -use alloc::rc::Rc; use crate::sync::{Arc, Mutex, LockTestExt, RwLock}; -use core::mem; -use core::iter::repeat; pub const CHAN_CONFIRM_DEPTH: u32 = 10; From 8deac898ab7276ed633a8233e81ffef924b21e76 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Wed, 3 Jan 2024 15:41:00 -0600 Subject: [PATCH 03/19] DRY up EntropySource implementation The ChaCha20-based EntropySource implementation is duplicated within the sign module. Refactor those into a RandomBytes implementation so that it may be reused both there. Also useful as a standalone EntropySource implementation for tests where an independent EntropySource is needed to ensure that backwards-compatibility testing is not broken. --- lightning/src/sign/mod.rs | 61 ++++++++++++++++++++++++--------------- 1 file changed, 38 insertions(+), 23 deletions(-) diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs index 4e418f049..312208794 100644 --- a/lightning/src/sign/mod.rs +++ b/lightning/src/sign/mod.rs @@ -822,11 +822,8 @@ pub struct InMemorySigner { channel_value_satoshis: u64, /// Key derivation parameters. channel_keys_id: [u8; 32], - /// Seed from which all randomness produced is derived from. - rand_bytes_unique_start: [u8; 32], - /// Tracks the number of times we've produced randomness to ensure we don't return the same - /// bytes twice. - rand_bytes_index: AtomicCounter, + /// A source of random bytes. + entropy_source: RandomBytes, } impl PartialEq for InMemorySigner { @@ -857,8 +854,7 @@ impl Clone for InMemorySigner { channel_parameters: self.channel_parameters.clone(), channel_value_satoshis: self.channel_value_satoshis, channel_keys_id: self.channel_keys_id, - rand_bytes_unique_start: self.get_secure_random_bytes(), - rand_bytes_index: AtomicCounter::new(), + entropy_source: RandomBytes::new(self.get_secure_random_bytes()), } } } @@ -892,8 +888,7 @@ impl InMemorySigner { holder_channel_pubkeys, channel_parameters: None, channel_keys_id, - rand_bytes_unique_start, - rand_bytes_index: AtomicCounter::new(), + entropy_source: RandomBytes::new(rand_bytes_unique_start), } } @@ -1069,10 +1064,7 @@ impl InMemorySigner { impl EntropySource for InMemorySigner { fn get_secure_random_bytes(&self) -> [u8; 32] { - let index = self.rand_bytes_index.get_increment(); - let mut nonce = [0u8; 16]; - nonce[..8].copy_from_slice(&index.to_be_bytes()); - ChaCha20::get_single_block(&self.rand_bytes_unique_start, &nonce) + self.entropy_source.get_secure_random_bytes() } } @@ -1350,8 +1342,7 @@ impl ReadableArgs for InMemorySigner where ES::Target: EntropySou holder_channel_pubkeys, channel_parameters: counterparty_channel_data, channel_keys_id: keys_id, - rand_bytes_unique_start: entropy_source.get_secure_random_bytes(), - rand_bytes_index: AtomicCounter::new(), + entropy_source: RandomBytes::new(entropy_source.get_secure_random_bytes()), }) } } @@ -1379,8 +1370,7 @@ pub struct KeysManager { channel_master_key: ExtendedPrivKey, channel_child_index: AtomicUsize, - rand_bytes_unique_start: [u8; 32], - rand_bytes_index: AtomicCounter, + entropy_source: RandomBytes, seed: [u8; 32], starting_time_secs: u64, @@ -1449,8 +1439,7 @@ impl KeysManager { channel_master_key, channel_child_index: AtomicUsize::new(0), - rand_bytes_unique_start, - rand_bytes_index: AtomicCounter::new(), + entropy_source: RandomBytes::new(rand_bytes_unique_start), seed: *seed, starting_time_secs, @@ -1631,10 +1620,7 @@ impl KeysManager { impl EntropySource for KeysManager { fn get_secure_random_bytes(&self) -> [u8; 32] { - let index = self.rand_bytes_index.get_increment(); - let mut nonce = [0u8; 16]; - nonce[..8].copy_from_slice(&index.to_be_bytes()); - ChaCha20::get_single_block(&self.rand_bytes_unique_start, &nonce) + self.entropy_source.get_secure_random_bytes() } } @@ -1888,6 +1874,35 @@ impl PhantomKeysManager { } } +/// An implementation of [`EntropySource`] using [`ChaCha20`]. +#[derive(Debug)] +struct RandomBytes { + /// Seed from which all randomness produced is derived from. + seed: [u8; 32], + /// Tracks the number of times we've produced randomness to ensure we don't return the same + /// bytes twice. + index: AtomicCounter, +} + +impl RandomBytes { + /// Creates a new instance using the given seed. + pub fn new(seed: [u8; 32]) -> Self { + Self { + seed, + index: AtomicCounter::new(), + } + } +} + +impl EntropySource for RandomBytes { + fn get_secure_random_bytes(&self) -> [u8; 32] { + let index = self.index.get_increment(); + let mut nonce = [0u8; 16]; + nonce[..8].copy_from_slice(&index.to_be_bytes()); + ChaCha20::get_single_block(&self.seed, &nonce) + } +} + // Ensure that EcdsaChannelSigner can have a vtable #[test] pub fn dyn_sign() { From 3b6d039f89b75758dda7c5aeedce5e79bbc0d5fa Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Fri, 27 Oct 2023 13:27:55 -0500 Subject: [PATCH 04/19] Support OnionMessenger in functional_test_utils OnionMessenger is needed to write functional tests for ChannelManager's OffersMessageHandler implementation. Also adds a TestMessageRouter, which simply wraps DefaultMessageRouter for now. --- lightning/src/ln/functional_test_utils.rs | 66 +++++++++++++++++++---- lightning/src/ln/functional_tests.rs | 3 +- lightning/src/onion_message/messenger.rs | 5 ++ lightning/src/routing/scoring.rs | 2 +- lightning/src/sign/mod.rs | 4 +- lightning/src/util/test_utils.rs | 32 ++++++++++- 6 files changed, 98 insertions(+), 14 deletions(-) diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index dd7b7c2cd..5971fc9ea 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -19,10 +19,12 @@ use crate::ln::{ChannelId, PaymentPreimage, PaymentHash, PaymentSecret}; use crate::ln::channelmanager::{AChannelManager, ChainParameters, ChannelManager, ChannelManagerReadArgs, RAACommitmentOrder, PaymentSendFailure, RecipientOnionFields, PaymentId, MIN_CLTV_EXPIRY_DELTA}; use crate::ln::features::InitFeatures; use crate::ln::msgs; -use crate::ln::msgs::{ChannelMessageHandler,RoutingMessageHandler}; +use crate::ln::msgs::{ChannelMessageHandler, OnionMessageHandler, RoutingMessageHandler}; +use crate::ln::peer_handler::IgnoringMessageHandler; +use crate::onion_message::messenger::OnionMessenger; use crate::routing::gossip::{P2PGossipSync, NetworkGraph, NetworkUpdate}; use crate::routing::router::{self, PaymentParameters, Route, RouteParameters}; -use crate::sign::EntropySource; +use crate::sign::{EntropySource, RandomBytes}; use crate::util::config::{UserConfig, MaxDustHTLCExposure}; use crate::util::errors::APIError; #[cfg(test)] @@ -47,6 +49,7 @@ use alloc::rc::Rc; use core::cell::RefCell; use core::iter::repeat; use core::mem; +use core::ops::Deref; use crate::io; use crate::prelude::*; use crate::sync::{Arc, Mutex, LockTestExt, RwLock}; @@ -388,6 +391,7 @@ pub struct NodeCfg<'a> { pub tx_broadcaster: &'a test_utils::TestBroadcaster, pub fee_estimator: &'a test_utils::TestFeeEstimator, pub router: test_utils::TestRouter<'a>, + pub message_router: test_utils::TestMessageRouter<'a>, pub chain_monitor: test_utils::TestChainMonitor<'a>, pub keys_manager: &'a test_utils::TestKeysInterface, pub logger: &'a test_utils::TestLogger, @@ -407,6 +411,26 @@ type TestChannelManager<'node_cfg, 'chan_mon_cfg> = ChannelManager< &'chan_mon_cfg test_utils::TestLogger, >; +type TestOnionMessenger<'chan_man, 'node_cfg, 'chan_mon_cfg> = OnionMessenger< + DedicatedEntropy, + &'node_cfg test_utils::TestKeysInterface, + &'chan_mon_cfg test_utils::TestLogger, + &'node_cfg test_utils::TestMessageRouter<'chan_mon_cfg>, + &'chan_man TestChannelManager<'node_cfg, 'chan_mon_cfg>, + IgnoringMessageHandler, +>; + +/// For use with [`OnionMessenger`] otherwise `test_restored_packages_retry` will fail. This is +/// because that test uses older serialized data produced by calling [`EntropySource`] in a specific +/// manner. Using the same [`EntropySource`] with [`OnionMessenger`] would introduce another call, +/// causing the produced data to no longer match. +pub struct DedicatedEntropy(RandomBytes); + +impl Deref for DedicatedEntropy { + type Target = RandomBytes; + fn deref(&self) -> &Self::Target { &self.0 } +} + pub struct Node<'chan_man, 'node_cfg: 'chan_man, 'chan_mon_cfg: 'node_cfg> { pub chain_source: &'chan_mon_cfg test_utils::TestChainSource, pub tx_broadcaster: &'chan_mon_cfg test_utils::TestBroadcaster, @@ -415,6 +439,7 @@ pub struct Node<'chan_man, 'node_cfg: 'chan_man, 'chan_mon_cfg: 'node_cfg> { pub chain_monitor: &'node_cfg test_utils::TestChainMonitor<'chan_mon_cfg>, pub keys_manager: &'chan_mon_cfg test_utils::TestKeysInterface, pub node: &'chan_man TestChannelManager<'node_cfg, 'chan_mon_cfg>, + pub onion_messenger: TestOnionMessenger<'chan_man, 'node_cfg, 'chan_mon_cfg>, pub network_graph: &'node_cfg NetworkGraph<&'chan_mon_cfg test_utils::TestLogger>, pub gossip_sync: P2PGossipSync<&'node_cfg NetworkGraph<&'chan_mon_cfg test_utils::TestLogger>, &'chan_mon_cfg test_utils::TestChainSource, &'chan_mon_cfg test_utils::TestLogger>, pub node_seed: [u8; 32], @@ -432,6 +457,14 @@ pub struct Node<'chan_man, 'node_cfg: 'chan_man, 'chan_mon_cfg: 'node_cfg> { &'chan_mon_cfg test_utils::TestLogger, >, } + +impl<'a, 'b, 'c> Node<'a, 'b, 'c> { + pub fn init_features(&self, peer_node_id: &PublicKey) -> InitFeatures { + self.override_init_features.borrow().clone() + .unwrap_or_else(|| self.node.init_features() | self.onion_messenger.provided_init_features(peer_node_id)) + } +} + #[cfg(feature = "std")] impl<'a, 'b, 'c> std::panic::UnwindSafe for Node<'a, 'b, 'c> {} #[cfg(feature = "std")] @@ -1054,6 +1087,7 @@ macro_rules! reload_node { $new_channelmanager = _reload_node(&$node, $new_config, &chanman_encoded, $monitors_encoded); $node.node = &$new_channelmanager; + $node.onion_messenger.set_offers_handler(&$new_channelmanager); }; ($node: expr, $chanman_encoded: expr, $monitors_encoded: expr, $persister: ident, $new_chain_monitor: ident, $new_channelmanager: ident) => { reload_node!($node, $crate::util::config::UserConfig::default(), $chanman_encoded, $monitors_encoded, $persister, $new_chain_monitor, $new_channelmanager); @@ -2898,6 +2932,7 @@ pub fn create_node_cfgs_with_persisters<'a>(node_count: usize, chanmon_cfgs: &'a tx_broadcaster: &chanmon_cfgs[i].tx_broadcaster, fee_estimator: &chanmon_cfgs[i].fee_estimator, router: test_utils::TestRouter::new(network_graph.clone(), &chanmon_cfgs[i].scorer), + message_router: test_utils::TestMessageRouter::new(network_graph.clone()), chain_monitor, keys_manager: &chanmon_cfgs[i].keys_manager, node_seed: seed, @@ -2951,6 +2986,11 @@ pub fn create_network<'a, 'b: 'a, 'c: 'b>(node_count: usize, cfgs: &'b Vec(node_count: usize, cfgs: &'b Vec(node_count: usize, cfgs: &'b Vec LockableScore<'a> for RefCell { } } -#[cfg(not(c_bindings))] +#[cfg(any(not(c_bindings), feature = "_test_utils", test))] impl<'a, T: Score + 'a> LockableScore<'a> for RwLock { type ScoreUpdate = T; type ScoreLookUp = T; diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs index 312208794..717c05517 100644 --- a/lightning/src/sign/mod.rs +++ b/lightning/src/sign/mod.rs @@ -1874,9 +1874,9 @@ impl PhantomKeysManager { } } -/// An implementation of [`EntropySource`] using [`ChaCha20`]. +/// An implementation of [`EntropySource`] using ChaCha20. #[derive(Debug)] -struct RandomBytes { +pub struct RandomBytes { /// Seed from which all randomness produced is derived from. seed: [u8; 32], /// Tracks the number of times we've produced randomness to ensure we don't return the same diff --git a/lightning/src/util/test_utils.rs b/lightning/src/util/test_utils.rs index 1c45c4a4c..c1692f9e3 100644 --- a/lightning/src/util/test_utils.rs +++ b/lightning/src/util/test_utils.rs @@ -32,7 +32,7 @@ use crate::ln::msgs::LightningError; use crate::ln::script::ShutdownScript; use crate::offers::invoice::{BlindedPayInfo, UnsignedBolt12Invoice}; use crate::offers::invoice_request::UnsignedInvoiceRequest; -use crate::onion_message::messenger::{Destination, MessageRouter, OnionMessagePath}; +use crate::onion_message::messenger::{DefaultMessageRouter, Destination, MessageRouter, OnionMessagePath}; use crate::routing::gossip::{EffectiveCapacity, NetworkGraph, NodeId, RoutingFees}; use crate::routing::utxo::{UtxoLookup, UtxoLookupError, UtxoResult}; use crate::routing::router::{find_route, InFlightHtlcs, Path, Route, RouteParameters, RouteHintHop, Router, ScorerAccountingForInFlightHtlcs}; @@ -231,6 +231,33 @@ impl<'a> Drop for TestRouter<'a> { } } +pub struct TestMessageRouter<'a> { + inner: DefaultMessageRouter>, &'a TestLogger>, +} + +impl<'a> TestMessageRouter<'a> { + pub fn new(network_graph: Arc>) -> Self { + Self { inner: DefaultMessageRouter::new(network_graph) } + } +} + +impl<'a> MessageRouter for TestMessageRouter<'a> { + fn find_path( + &self, sender: PublicKey, peers: Vec, destination: Destination + ) -> Result { + self.inner.find_path(sender, peers, destination) + } + + fn create_blinded_paths< + ES: EntropySource + ?Sized, T: secp256k1::Signing + secp256k1::Verification + >( + &self, recipient: PublicKey, peers: Vec, entropy_source: &ES, + secp_ctx: &Secp256k1 + ) -> Result, ()> { + self.inner.create_blinded_paths(recipient, peers, entropy_source, secp_ctx) + } +} + pub struct OnlyReadsKeysInterface {} impl EntropySource for OnlyReadsKeysInterface { @@ -1390,6 +1417,9 @@ impl ScoreUpdate for TestScorer { fn time_passed(&mut self, _duration_since_epoch: Duration) {} } +#[cfg(c_bindings)] +impl crate::routing::scoring::Score for TestScorer {} + impl Drop for TestScorer { fn drop(&mut self) { #[cfg(feature = "std")] { From b5783632482164dc53e9fb50ea52b66adf330001 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Thu, 14 Dec 2023 10:25:10 -0600 Subject: [PATCH 05/19] Have TestRouter wrap DefaultRouter TestRouter checks route and scoring expectations before delegating to the standalone find_route function. Refactor it to wrap DefaultRouter and delegate to it instead. This allows TestRouter to implement create_blinded_payment_paths by delegating to DefaultRouter. --- lightning/src/ln/channelmanager.rs | 2 +- lightning/src/ln/functional_test_utils.rs | 4 +- lightning/src/ln/functional_tests.rs | 2 +- lightning/src/ln/outbound_payment.rs | 14 +++---- lightning/src/util/test_utils.rs | 47 +++++++++++++++-------- 5 files changed, 41 insertions(+), 28 deletions(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index e405c3a67..01888b202 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -12527,7 +12527,7 @@ pub mod bench { let fee_estimator = test_utils::TestFeeEstimator { sat_per_kw: Mutex::new(253) }; let logger_a = test_utils::TestLogger::with_id("node a".to_owned()); let scorer = RwLock::new(test_utils::TestScorer::new()); - let router = test_utils::TestRouter::new(Arc::new(NetworkGraph::new(network, &logger_a)), &scorer); + let router = test_utils::TestRouter::new(Arc::new(NetworkGraph::new(network, &logger_a)), &logger_a, &scorer); let mut config: UserConfig = Default::default(); config.channel_config.max_dust_htlc_exposure = MaxDustHTLCExposure::FeeRateMultiplier(5_000_000 / 253); diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index 5971fc9ea..8df84000c 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -632,7 +632,7 @@ impl<'a, 'b, 'c> Drop for Node<'a, 'b, 'c> { node_signer: self.keys_manager, signer_provider: self.keys_manager, fee_estimator: &test_utils::TestFeeEstimator { sat_per_kw: Mutex::new(253) }, - router: &test_utils::TestRouter::new(Arc::new(network_graph), &scorer), + router: &test_utils::TestRouter::new(Arc::new(network_graph), &self.logger, &scorer), chain_monitor: self.chain_monitor, tx_broadcaster: &broadcaster, logger: &self.logger, @@ -2931,7 +2931,7 @@ pub fn create_node_cfgs_with_persisters<'a>(node_count: usize, chanmon_cfgs: &'a logger: &chanmon_cfgs[i].logger, tx_broadcaster: &chanmon_cfgs[i].tx_broadcaster, fee_estimator: &chanmon_cfgs[i].fee_estimator, - router: test_utils::TestRouter::new(network_graph.clone(), &chanmon_cfgs[i].scorer), + router: test_utils::TestRouter::new(network_graph.clone(), &chanmon_cfgs[i].logger, &chanmon_cfgs[i].scorer), message_router: test_utils::TestMessageRouter::new(network_graph.clone()), chain_monitor, keys_manager: &chanmon_cfgs[i].keys_manager, diff --git a/lightning/src/ln/functional_tests.rs b/lightning/src/ln/functional_tests.rs index db3861c2e..be9bfb81f 100644 --- a/lightning/src/ln/functional_tests.rs +++ b/lightning/src/ln/functional_tests.rs @@ -5533,7 +5533,7 @@ fn test_key_derivation_params() { let chain_monitor = test_utils::TestChainMonitor::new(Some(&chanmon_cfgs[0].chain_source), &chanmon_cfgs[0].tx_broadcaster, &chanmon_cfgs[0].logger, &chanmon_cfgs[0].fee_estimator, &chanmon_cfgs[0].persister, &keys_manager); let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &chanmon_cfgs[0].logger)); let scorer = RwLock::new(test_utils::TestScorer::new()); - let router = test_utils::TestRouter::new(network_graph.clone(), &scorer); + let router = test_utils::TestRouter::new(network_graph.clone(), &chanmon_cfgs[0].logger, &scorer); let message_router = test_utils::TestMessageRouter::new(network_graph.clone()); let node = NodeCfg { chain_source: &chanmon_cfgs[0].chain_source, logger: &chanmon_cfgs[0].logger, tx_broadcaster: &chanmon_cfgs[0].tx_broadcaster, fee_estimator: &chanmon_cfgs[0].fee_estimator, router, message_router, chain_monitor, keys_manager: &keys_manager, network_graph, node_seed: seed, override_init_features: alloc::rc::Rc::new(core::cell::RefCell::new(None)) }; let mut node_cfgs = create_node_cfgs(3, &chanmon_cfgs); diff --git a/lightning/src/ln/outbound_payment.rs b/lightning/src/ln/outbound_payment.rs index f53960461..46ec29770 100644 --- a/lightning/src/ln/outbound_payment.rs +++ b/lightning/src/ln/outbound_payment.rs @@ -1876,7 +1876,7 @@ mod tests { let logger = test_utils::TestLogger::new(); let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &logger)); let scorer = RwLock::new(test_utils::TestScorer::new()); - let router = test_utils::TestRouter::new(network_graph, &scorer); + let router = test_utils::TestRouter::new(network_graph, &logger, &scorer); let secp_ctx = Secp256k1::new(); let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet); @@ -1920,7 +1920,7 @@ mod tests { let logger = test_utils::TestLogger::new(); let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &logger)); let scorer = RwLock::new(test_utils::TestScorer::new()); - let router = test_utils::TestRouter::new(network_graph, &scorer); + let router = test_utils::TestRouter::new(network_graph, &logger, &scorer); let secp_ctx = Secp256k1::new(); let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet); @@ -1959,7 +1959,7 @@ mod tests { let logger = test_utils::TestLogger::new(); let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &logger)); let scorer = RwLock::new(test_utils::TestScorer::new()); - let router = test_utils::TestRouter::new(network_graph, &scorer); + let router = test_utils::TestRouter::new(network_graph, &logger, &scorer); let secp_ctx = Secp256k1::new(); let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet); @@ -2166,7 +2166,7 @@ mod tests { let logger = test_utils::TestLogger::new(); let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &logger)); let scorer = RwLock::new(test_utils::TestScorer::new()); - let router = test_utils::TestRouter::new(network_graph, &scorer); + let router = test_utils::TestRouter::new(network_graph, &logger, &scorer); let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet); let pending_events = Mutex::new(VecDeque::new()); @@ -2217,7 +2217,7 @@ mod tests { let logger = test_utils::TestLogger::new(); let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &logger)); let scorer = RwLock::new(test_utils::TestScorer::new()); - let router = test_utils::TestRouter::new(network_graph, &scorer); + let router = test_utils::TestRouter::new(network_graph, &logger, &scorer); let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet); let pending_events = Mutex::new(VecDeque::new()); @@ -2276,7 +2276,7 @@ mod tests { let logger = test_utils::TestLogger::new(); let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &logger)); let scorer = RwLock::new(test_utils::TestScorer::new()); - let router = test_utils::TestRouter::new(network_graph, &scorer); + let router = test_utils::TestRouter::new(network_graph, &logger, &scorer); let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet); let pending_events = Mutex::new(VecDeque::new()); @@ -2335,7 +2335,7 @@ mod tests { let logger = test_utils::TestLogger::new(); let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &logger)); let scorer = RwLock::new(test_utils::TestScorer::new()); - let router = test_utils::TestRouter::new(network_graph, &scorer); + let router = test_utils::TestRouter::new(network_graph, &logger, &scorer); let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet); let pending_events = Mutex::new(VecDeque::new()); diff --git a/lightning/src/util/test_utils.rs b/lightning/src/util/test_utils.rs index c1692f9e3..02423541f 100644 --- a/lightning/src/util/test_utils.rs +++ b/lightning/src/util/test_utils.rs @@ -35,7 +35,7 @@ use crate::offers::invoice_request::UnsignedInvoiceRequest; use crate::onion_message::messenger::{DefaultMessageRouter, Destination, MessageRouter, OnionMessagePath}; use crate::routing::gossip::{EffectiveCapacity, NetworkGraph, NodeId, RoutingFees}; use crate::routing::utxo::{UtxoLookup, UtxoLookupError, UtxoResult}; -use crate::routing::router::{find_route, InFlightHtlcs, Path, Route, RouteParameters, RouteHintHop, Router, ScorerAccountingForInFlightHtlcs}; +use crate::routing::router::{DefaultRouter, InFlightHtlcs, Path, Route, RouteParameters, RouteHintHop, Router, ScorerAccountingForInFlightHtlcs}; use crate::routing::scoring::{ChannelUsage, ScoreUpdate, ScoreLookUp}; use crate::sync::RwLock; use crate::util::config::UserConfig; @@ -104,14 +104,29 @@ impl chaininterface::FeeEstimator for TestFeeEstimator { } pub struct TestRouter<'a> { + pub router: DefaultRouter< + Arc>, + &'a TestLogger, + &'a RwLock, + (), + TestScorer, + >, pub network_graph: Arc>, pub next_routes: Mutex)>>, pub scorer: &'a RwLock, } impl<'a> TestRouter<'a> { - pub fn new(network_graph: Arc>, scorer: &'a RwLock) -> Self { - Self { network_graph, next_routes: Mutex::new(VecDeque::new()), scorer } + pub fn new( + network_graph: Arc>, logger: &'a TestLogger, + scorer: &'a RwLock + ) -> Self { + Self { + router: DefaultRouter::new(network_graph.clone(), logger, [42u8; 32], scorer, ()), + network_graph, + next_routes: Mutex::new(VecDeque::new()), + scorer, + } } pub fn expect_find_route(&self, query: RouteParameters, result: Result) { @@ -185,38 +200,36 @@ impl<'a> Router for TestRouter<'a> { } return find_route_res; } - let logger = TestLogger::new(); - find_route( - payer, params, &self.network_graph, first_hops, &logger, - &ScorerAccountingForInFlightHtlcs::new(self.scorer.read().unwrap(), &inflight_htlcs), &Default::default(), - &[42; 32] - ) + + self.router.find_route(payer, params, first_hops, inflight_htlcs) } fn create_blinded_payment_paths< ES: EntropySource + ?Sized, T: secp256k1::Signing + secp256k1::Verification >( - &self, _recipient: PublicKey, _first_hops: Vec, _tlvs: ReceiveTlvs, - _amount_msats: u64, _entropy_source: &ES, _secp_ctx: &Secp256k1 + &self, recipient: PublicKey, first_hops: Vec, tlvs: ReceiveTlvs, + amount_msats: u64, entropy_source: &ES, secp_ctx: &Secp256k1 ) -> Result, ()> { - unreachable!() + self.router.create_blinded_payment_paths( + recipient, first_hops, tlvs, amount_msats, entropy_source, secp_ctx + ) } } impl<'a> MessageRouter for TestRouter<'a> { fn find_path( - &self, _sender: PublicKey, _peers: Vec, _destination: Destination + &self, sender: PublicKey, peers: Vec, destination: Destination ) -> Result { - unreachable!() + self.router.find_path(sender, peers, destination) } fn create_blinded_paths< ES: EntropySource + ?Sized, T: secp256k1::Signing + secp256k1::Verification >( - &self, _recipient: PublicKey, _peers: Vec, _entropy_source: &ES, - _secp_ctx: &Secp256k1 + &self, recipient: PublicKey, peers: Vec, entropy_source: &ES, + secp_ctx: &Secp256k1 ) -> Result, ()> { - unreachable!() + self.router.create_blinded_paths(recipient, peers, entropy_source, secp_ctx) } } From 30c07021d965aab45dcf450b94522d4737eabf66 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Thu, 4 Jan 2024 10:30:51 -0600 Subject: [PATCH 06/19] Remove "no-std" feature checks An upcoming rust-bitcoin release will remove the "no-std" feature. Replace "no-std" in feature checks with "std", negating as needed. Using a single feature flag makes the checks more consistent across modules. --- lightning/src/ln/channelmanager.rs | 16 +++++++-------- lightning/src/ln/outbound_payment.rs | 30 ++++++++++++++-------------- lightning/src/ln/payment_tests.rs | 4 ++-- lightning/src/routing/gossip.rs | 2 +- lightning/src/routing/router.rs | 10 +++++----- lightning/src/util/time.rs | 10 +++++----- 6 files changed, 36 insertions(+), 36 deletions(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 01888b202..d0badb8b2 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -7779,15 +7779,15 @@ where let payment_paths = self.create_blinded_payment_paths(amount_msats, payment_secret) .map_err(|_| Bolt12SemanticError::MissingPaths)?; - #[cfg(not(feature = "no-std"))] + #[cfg(feature = "std")] let builder = refund.respond_using_derived_keys( payment_paths, payment_hash, expanded_key, entropy )?; - #[cfg(feature = "no-std")] + #[cfg(not(feature = "std"))] let created_at = Duration::from_secs( self.highest_seen_timestamp.load(Ordering::Acquire) as u64 ); - #[cfg(feature = "no-std")] + #[cfg(not(feature = "std"))] let builder = refund.respond_using_derived_keys_no_std( payment_paths, payment_hash, created_at, expanded_key, entropy )?; @@ -9224,17 +9224,17 @@ where }, }; - #[cfg(feature = "no-std")] + #[cfg(not(feature = "std"))] let created_at = Duration::from_secs( self.highest_seen_timestamp.load(Ordering::Acquire) as u64 ); if invoice_request.keys.is_some() { - #[cfg(not(feature = "no-std"))] + #[cfg(feature = "std")] let builder = invoice_request.respond_using_derived_keys( payment_paths, payment_hash ); - #[cfg(feature = "no-std")] + #[cfg(not(feature = "std"))] let builder = invoice_request.respond_using_derived_keys_no_std( payment_paths, payment_hash, created_at ); @@ -9243,9 +9243,9 @@ where Err(error) => Some(OffersMessage::InvoiceError(error.into())), } } else { - #[cfg(not(feature = "no-std"))] + #[cfg(feature = "std")] let builder = invoice_request.respond_with(payment_paths, payment_hash); - #[cfg(feature = "no-std")] + #[cfg(not(feature = "std"))] let builder = invoice_request.respond_with_no_std( payment_paths, payment_hash, created_at ); diff --git a/lightning/src/ln/outbound_payment.rs b/lightning/src/ln/outbound_payment.rs index 46ec29770..66c9ee62e 100644 --- a/lightning/src/ln/outbound_payment.rs +++ b/lightning/src/ln/outbound_payment.rs @@ -23,7 +23,7 @@ use crate::routing::router::{InFlightHtlcs, Path, PaymentParameters, Route, Rout use crate::util::errors::APIError; use crate::util::logger::Logger; use crate::util::time::Time; -#[cfg(all(not(feature = "no-std"), test))] +#[cfg(all(feature = "std", test))] use crate::util::time::tests::SinceEpoch; use crate::util::ser::ReadableArgs; @@ -282,7 +282,7 @@ pub enum Retry { /// retry, and may retry multiple failed HTLCs at once if they failed around the same time and /// were retried along a route from a single call to [`Router::find_route_with_id`]. Attempts(u32), - #[cfg(not(feature = "no-std"))] + #[cfg(feature = "std")] /// Time elapsed before abandoning retries for a payment. At least one attempt at payment is made; /// see [`PaymentParameters::expiry_time`] to avoid any attempt at payment after a specific time. /// @@ -290,13 +290,13 @@ pub enum Retry { Timeout(core::time::Duration), } -#[cfg(feature = "no-std")] +#[cfg(not(feature = "std"))] impl_writeable_tlv_based_enum!(Retry, ; (0, Attempts) ); -#[cfg(not(feature = "no-std"))] +#[cfg(feature = "std")] impl_writeable_tlv_based_enum!(Retry, ; (0, Attempts), @@ -309,10 +309,10 @@ impl Retry { (Retry::Attempts(max_retry_count), PaymentAttempts { count, .. }) => { max_retry_count > count }, - #[cfg(all(not(feature = "no-std"), not(test)))] + #[cfg(all(feature = "std", not(test)))] (Retry::Timeout(max_duration), PaymentAttempts { first_attempted_at, .. }) => *max_duration >= crate::util::time::MonotonicTime::now().duration_since(*first_attempted_at), - #[cfg(all(not(feature = "no-std"), test))] + #[cfg(all(feature = "std", test))] (Retry::Timeout(max_duration), PaymentAttempts { first_attempted_at, .. }) => *max_duration >= SinceEpoch::now().duration_since(*first_attempted_at), } @@ -338,27 +338,27 @@ pub(crate) struct PaymentAttemptsUsingTime { /// it means the result of the first attempt is not known yet. pub(crate) count: u32, /// This field is only used when retry is `Retry::Timeout` which is only build with feature std - #[cfg(not(feature = "no-std"))] + #[cfg(feature = "std")] first_attempted_at: T, - #[cfg(feature = "no-std")] + #[cfg(not(feature = "std"))] phantom: core::marker::PhantomData, } -#[cfg(not(any(feature = "no-std", test)))] +#[cfg(not(any(not(feature = "std"), test)))] type ConfiguredTime = crate::util::time::MonotonicTime; -#[cfg(feature = "no-std")] +#[cfg(not(feature = "std"))] type ConfiguredTime = crate::util::time::Eternity; -#[cfg(all(not(feature = "no-std"), test))] +#[cfg(all(feature = "std", test))] type ConfiguredTime = SinceEpoch; impl PaymentAttemptsUsingTime { pub(crate) fn new() -> Self { PaymentAttemptsUsingTime { count: 0, - #[cfg(not(feature = "no-std"))] + #[cfg(feature = "std")] first_attempted_at: T::now(), - #[cfg(feature = "no-std")] + #[cfg(not(feature = "std"))] phantom: core::marker::PhantomData, } } @@ -366,9 +366,9 @@ impl PaymentAttemptsUsingTime { impl Display for PaymentAttemptsUsingTime { fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { - #[cfg(feature = "no-std")] + #[cfg(not(feature = "std"))] return write!(f, "attempts: {}", self.count); - #[cfg(not(feature = "no-std"))] + #[cfg(feature = "std")] return write!( f, "attempts: {}, duration: {}s", diff --git a/lightning/src/ln/payment_tests.rs b/lightning/src/ln/payment_tests.rs index 73cdf59bb..15870dc57 100644 --- a/lightning/src/ln/payment_tests.rs +++ b/lightning/src/ln/payment_tests.rs @@ -44,7 +44,7 @@ use crate::ln::functional_test_utils::*; use crate::routing::gossip::NodeId; #[cfg(feature = "std")] use std::time::{SystemTime, Instant, Duration}; -#[cfg(not(feature = "no-std"))] +#[cfg(feature = "std")] use crate::util::time::tests::SinceEpoch; #[test] @@ -2313,7 +2313,7 @@ fn do_automatic_retries(test: AutoRetry) { let mut msg_events = nodes[0].node.get_and_clear_pending_msg_events(); assert_eq!(msg_events.len(), 0); } else if test == AutoRetry::FailTimeout { - #[cfg(not(feature = "no-std"))] { + #[cfg(feature = "std")] { // Ensure ChannelManager will not retry a payment if it times out due to Retry::Timeout. nodes[0].node.send_payment(payment_hash, RecipientOnionFields::secret_only(payment_secret), PaymentId(payment_hash.0), route_params, Retry::Timeout(Duration::from_secs(60))).unwrap(); diff --git a/lightning/src/routing/gossip.rs b/lightning/src/routing/gossip.rs index 9b4e41ae1..9c8fd40af 100644 --- a/lightning/src/routing/gossip.rs +++ b/lightning/src/routing/gossip.rs @@ -1845,7 +1845,7 @@ impl NetworkGraph where L::Target: Logger { // NOTE: In the case of no-std, we won't have access to the current UNIX time at the time of removal, // so we'll just set the removal time here to the current UNIX time on the very next invocation // of this function. - #[cfg(feature = "no-std")] + #[cfg(not(feature = "std"))] { let mut tracked_time = Some(current_time_unix); core::mem::swap(time, &mut tracked_time); diff --git a/lightning/src/routing/router.rs b/lightning/src/routing/router.rs index 485fd2391..4a32a5e48 100644 --- a/lightning/src/routing/router.rs +++ b/lightning/src/routing/router.rs @@ -6902,7 +6902,7 @@ mod tests { (route.paths[1].hops[1].short_channel_id == 4 && route.paths[0].hops[1].short_channel_id == 13)); } - #[cfg(not(feature = "no-std"))] + #[cfg(feature = "std")] pub(super) fn random_init_seed() -> u64 { // Because the default HashMap in std pulls OS randomness, we can use it as a (bad) RNG. use core::hash::{BuildHasher, Hasher}; @@ -6912,7 +6912,7 @@ mod tests { } #[test] - #[cfg(not(feature = "no-std"))] + #[cfg(feature = "std")] fn generate_routes() { use crate::routing::scoring::{ProbabilisticScorer, ProbabilisticScoringFeeParameters}; @@ -6933,7 +6933,7 @@ mod tests { } #[test] - #[cfg(not(feature = "no-std"))] + #[cfg(feature = "std")] fn generate_routes_mpp() { use crate::routing::scoring::{ProbabilisticScorer, ProbabilisticScoringFeeParameters}; @@ -6954,7 +6954,7 @@ mod tests { } #[test] - #[cfg(not(feature = "no-std"))] + #[cfg(feature = "std")] fn generate_large_mpp_routes() { use crate::routing::scoring::{ProbabilisticScorer, ProbabilisticScoringFeeParameters}; @@ -8290,7 +8290,7 @@ mod tests { } } -#[cfg(all(any(test, ldk_bench), not(feature = "no-std")))] +#[cfg(all(any(test, ldk_bench), feature = "std"))] pub(crate) mod bench_utils { use super::*; use std::fs::File; diff --git a/lightning/src/util/time.rs b/lightning/src/util/time.rs index 0d969e747..a6e6f4d1f 100644 --- a/lightning/src/util/time.rs +++ b/lightning/src/util/time.rs @@ -59,15 +59,15 @@ impl Sub for Eternity { } #[derive(Clone, Copy, Debug, PartialEq, Eq)] -#[cfg(not(feature = "no-std"))] +#[cfg(feature = "std")] pub struct MonotonicTime(std::time::Instant); /// The amount of time to shift `Instant` forward to prevent overflow when subtracting a `Duration` /// from `Instant::now` on some operating systems (e.g., iOS representing `Instance` as `u64`). -#[cfg(not(feature = "no-std"))] +#[cfg(feature = "std")] const SHIFT: Duration = Duration::from_secs(10 * 365 * 24 * 60 * 60); // 10 years. -#[cfg(not(feature = "no-std"))] +#[cfg(feature = "std")] impl Time for MonotonicTime { fn now() -> Self { let instant = std::time::Instant::now().checked_add(SHIFT).expect("Overflow on MonotonicTime instantiation"); @@ -93,7 +93,7 @@ impl Time for MonotonicTime { } } -#[cfg(not(feature = "no-std"))] +#[cfg(feature = "std")] impl Sub for MonotonicTime { type Output = Self; @@ -177,7 +177,7 @@ pub mod tests { } #[test] - #[cfg(not(feature = "no-std"))] + #[cfg(feature = "std")] fn monotonic_time_subtracts() { let now = super::MonotonicTime::now(); assert!(now.elapsed() < Duration::from_secs(10)); From a1c30041360905e5edcb343447cf1d5872188914 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Tue, 9 Jan 2024 14:59:30 -0600 Subject: [PATCH 07/19] Remove redundant negations from feature check --- lightning/src/ln/outbound_payment.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lightning/src/ln/outbound_payment.rs b/lightning/src/ln/outbound_payment.rs index 66c9ee62e..b82cc436a 100644 --- a/lightning/src/ln/outbound_payment.rs +++ b/lightning/src/ln/outbound_payment.rs @@ -345,10 +345,10 @@ pub(crate) struct PaymentAttemptsUsingTime { } -#[cfg(not(any(not(feature = "std"), test)))] -type ConfiguredTime = crate::util::time::MonotonicTime; #[cfg(not(feature = "std"))] type ConfiguredTime = crate::util::time::Eternity; +#[cfg(all(feature = "std", not(test)))] +type ConfiguredTime = crate::util::time::MonotonicTime; #[cfg(all(feature = "std", test))] type ConfiguredTime = SinceEpoch; From aba77480ac8e1e63857b99e3dee50f7700abeae2 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Tue, 9 Jan 2024 15:05:29 -0600 Subject: [PATCH 08/19] Consolidate conditional imports --- lightning/src/ln/payment_tests.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lightning/src/ln/payment_tests.rs b/lightning/src/ln/payment_tests.rs index 15870dc57..0db815a70 100644 --- a/lightning/src/ln/payment_tests.rs +++ b/lightning/src/ln/payment_tests.rs @@ -42,10 +42,12 @@ use crate::prelude::*; use crate::ln::functional_test_utils; use crate::ln::functional_test_utils::*; use crate::routing::gossip::NodeId; + #[cfg(feature = "std")] -use std::time::{SystemTime, Instant, Duration}; -#[cfg(feature = "std")] -use crate::util::time::tests::SinceEpoch; +use { + crate::util::time::tests::SinceEpoch, + std::time::{SystemTime, Instant, Duration}, +}; #[test] fn mpp_failure() { From 1059ac3c28d0f7bac70bdc9be6d85e936fa6a358 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Tue, 31 Oct 2023 14:27:29 -0500 Subject: [PATCH 09/19] Functional tests for BOLT 12 Offers payment flow ChannelManager provides utilities to create offers and refunds along with utilities to initiate and request payment for them, respectively. It also manages the payment flow via implementing OffersMessageHandler. Test that functionality, including the resulting event generation. --- lightning/src/ln/mod.rs | 3 + lightning/src/ln/offers_tests.rs | 215 +++++++++++++++++++++++++++++++ 2 files changed, 218 insertions(+) create mode 100644 lightning/src/ln/offers_tests.rs diff --git a/lightning/src/ln/mod.rs b/lightning/src/ln/mod.rs index 43ec34eaf..e79f4bbd2 100644 --- a/lightning/src/ln/mod.rs +++ b/lightning/src/ln/mod.rs @@ -79,6 +79,9 @@ mod shutdown_tests; #[cfg(all(test, async_signing))] #[allow(unused_mut)] mod async_signer_tests; +#[cfg(test)] +#[allow(unused_mut)] +mod offers_tests; pub use self::peer_channel_encryptor::LN_MAX_MSG_LEN; diff --git a/lightning/src/ln/offers_tests.rs b/lightning/src/ln/offers_tests.rs new file mode 100644 index 000000000..5a8211899 --- /dev/null +++ b/lightning/src/ln/offers_tests.rs @@ -0,0 +1,215 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// 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. + +//! Functional tests for the BOLT 12 Offers payment flow. +//! +//! [`ChannelManager`] provides utilities to create [`Offer`]s and [`Refund`]s along with utilities +//! to initiate and request payment for them, respectively. It also manages the payment flow via +//! implementing [`OffersMessageHandler`]. This module tests that functionality, including the +//! resulting [`Event`] generation. + +use core::time::Duration; +use crate::blinded_path::BlindedPath; +use crate::events::{Event, MessageSendEventsProvider, PaymentPurpose}; +use crate::ln::channelmanager::{PaymentId, RecentPaymentDetails, Retry}; +use crate::ln::functional_test_utils::*; +use crate::ln::msgs::{OnionMessage, OnionMessageHandler}; +use crate::offers::invoice::Bolt12Invoice; +use crate::offers::invoice_request::InvoiceRequest; +use crate::onion_message::messenger::PeeledOnion; +use crate::onion_message::offers::OffersMessage; +use crate::onion_message::packet::ParsedOnionMessageContents; + +use crate::prelude::*; + +macro_rules! expect_recent_payment { + ($node: expr, $payment_state: path, $payment_id: expr) => { + match $node.node.list_recent_payments().first() { + Some(&$payment_state { payment_id: actual_payment_id, .. }) => { + assert_eq!($payment_id, actual_payment_id); + }, + Some(_) => panic!("Unexpected recent payment state"), + None => panic!("No recent payments"), + } + } +} + +fn route_bolt12_payment<'a, 'b, 'c>( + node: &Node<'a, 'b, 'c>, path: &[&Node<'a, 'b, 'c>], invoice: &Bolt12Invoice +) { + // Monitor added when handling the invoice onion message. + check_added_monitors(node, 1); + + let mut events = node.node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + let ev = remove_first_msg_event_to_node(&path[0].node.get_our_node_id(), &mut events); + + // Use a fake payment_hash and bypass checking for the PaymentClaimable event since the + // invoice contains the payment_hash but it was encrypted inside an onion message. + let amount_msats = invoice.amount_msats(); + let payment_hash = invoice.payment_hash(); + do_pass_along_path( + node, path, amount_msats, payment_hash, None, ev, false, false, None, false + ); +} + +fn claim_bolt12_payment<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, path: &[&Node<'a, 'b, 'c>]) { + let recipient = &path[path.len() - 1]; + match get_event!(recipient, Event::PaymentClaimable) { + Event::PaymentClaimable { + purpose: PaymentPurpose::InvoicePayment { + payment_preimage: Some(payment_preimage), .. + }, .. + } => claim_payment(node, path, payment_preimage), + _ => panic!(), + }; +} + +fn extract_invoice_request<'a, 'b, 'c>( + node: &Node<'a, 'b, 'c>, message: &OnionMessage +) -> (InvoiceRequest, Option) { + match node.onion_messenger.peel_onion_message(message) { + Ok(PeeledOnion::Receive(message, _, reply_path)) => match message { + ParsedOnionMessageContents::Offers(offers_message) => match offers_message { + OffersMessage::InvoiceRequest(invoice_request) => (invoice_request, reply_path), + OffersMessage::Invoice(invoice) => panic!("Unexpected invoice: {:?}", invoice), + OffersMessage::InvoiceError(error) => panic!("Unexpected invoice_error: {:?}", error), + }, + ParsedOnionMessageContents::Custom(message) => panic!("Unexpected custom message: {:?}", message), + }, + Ok(PeeledOnion::Forward(_, _)) => panic!("Unexpected onion message forward"), + Err(e) => panic!("Failed to process onion message {:?}", e), + } +} + +fn extract_invoice<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, message: &OnionMessage) -> Bolt12Invoice { + match node.onion_messenger.peel_onion_message(message) { + Ok(PeeledOnion::Receive(message, _, _)) => match message { + ParsedOnionMessageContents::Offers(offers_message) => match offers_message { + OffersMessage::InvoiceRequest(invoice_request) => panic!("Unexpected invoice_request: {:?}", invoice_request), + OffersMessage::Invoice(invoice) => invoice, + OffersMessage::InvoiceError(error) => panic!("Unexpected invoice_error: {:?}", error), + }, + ParsedOnionMessageContents::Custom(message) => panic!("Unexpected custom message: {:?}", message), + }, + Ok(PeeledOnion::Forward(_, _)) => panic!("Unexpected onion message forward"), + Err(e) => panic!("Failed to process onion message {:?}", e), + } +} + +/// Checks that an offer can be paid through a one-hop blinded path and that ephemeral pubkeys are +/// used rather than exposing a node's pubkey. However, the node's pubkey is still used as the +/// introduction node of the blinded path. +#[test] +fn creates_and_pays_for_offer_using_one_hop_blinded_path() { + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000); + + let alice = &nodes[0]; + let alice_id = alice.node.get_our_node_id(); + let bob = &nodes[1]; + let bob_id = bob.node.get_our_node_id(); + + let offer = alice.node + .create_offer_builder("coffee".to_string()).unwrap() + .amount_msats(10_000_000) + .build().unwrap(); + assert_ne!(offer.signing_pubkey(), alice_id); + assert!(!offer.paths().is_empty()); + for path in offer.paths() { + assert_eq!(path.introduction_node_id, alice_id); + } + + let payment_id = PaymentId([1; 32]); + bob.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None).unwrap(); + expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id); + + let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap(); + alice.onion_messenger.handle_onion_message(&bob_id, &onion_message); + + let (invoice_request, reply_path) = extract_invoice_request(alice, &onion_message); + assert_eq!(invoice_request.amount_msats(), None); + assert_ne!(invoice_request.payer_id(), bob_id); + assert_eq!(reply_path.unwrap().introduction_node_id, bob_id); + + let onion_message = alice.onion_messenger.next_onion_message_for_peer(bob_id).unwrap(); + bob.onion_messenger.handle_onion_message(&alice_id, &onion_message); + + let invoice = extract_invoice(bob, &onion_message); + assert_eq!(invoice.amount_msats(), 10_000_000); + assert_ne!(invoice.signing_pubkey(), alice_id); + assert!(!invoice.payment_paths().is_empty()); + for (_, path) in invoice.payment_paths() { + assert_eq!(path.introduction_node_id, alice_id); + } + + route_bolt12_payment(bob, &[alice], &invoice); + expect_recent_payment!(bob, RecentPaymentDetails::Pending, payment_id); + + claim_bolt12_payment(bob, &[alice]); + expect_recent_payment!(bob, RecentPaymentDetails::Fulfilled, payment_id); +} + +/// Checks that a refund can be paid through a one-hop blinded path and that ephemeral pubkeys are +/// used rather than exposing a node's pubkey. However, the node's pubkey is still used as the +/// introduction node of the blinded path. +#[test] +fn creates_and_pays_for_refund_using_one_hop_blinded_path() { + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000); + + let alice = &nodes[0]; + let alice_id = alice.node.get_our_node_id(); + let bob = &nodes[1]; + let bob_id = bob.node.get_our_node_id(); + + let absolute_expiry = Duration::from_secs(u64::MAX); + let payment_id = PaymentId([1; 32]); + let refund = bob.node + .create_refund_builder( + "refund".to_string(), 10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), None + ) + .unwrap() + .build().unwrap(); + assert_eq!(refund.amount_msats(), 10_000_000); + assert_eq!(refund.absolute_expiry(), Some(absolute_expiry)); + assert_ne!(refund.payer_id(), bob_id); + assert!(!refund.paths().is_empty()); + for path in refund.paths() { + assert_eq!(path.introduction_node_id, bob_id); + } + expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id); + + alice.node.request_refund_payment(&refund).unwrap(); + + let onion_message = alice.onion_messenger.next_onion_message_for_peer(bob_id).unwrap(); + bob.onion_messenger.handle_onion_message(&alice_id, &onion_message); + + let invoice = extract_invoice(bob, &onion_message); + assert_eq!(invoice.amount_msats(), 10_000_000); + assert_ne!(invoice.signing_pubkey(), alice_id); + assert!(!invoice.payment_paths().is_empty()); + for (_, path) in invoice.payment_paths() { + assert_eq!(path.introduction_node_id, alice_id); + } + + route_bolt12_payment(bob, &[alice], &invoice); + expect_recent_payment!(bob, RecentPaymentDetails::Pending, payment_id); + + claim_bolt12_payment(bob, &[alice]); + expect_recent_payment!(bob, RecentPaymentDetails::Fulfilled, payment_id); +} From 6c67e3e2797262e608a1477e11cabc3ee9ca372d Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Fri, 3 Nov 2023 14:01:57 -0500 Subject: [PATCH 10/19] Functional tests for offers without blinded paths --- lightning/src/ln/offers_tests.rs | 85 ++++++++++++++++++++++++++++++++ lightning/src/offers/offer.rs | 5 ++ lightning/src/offers/refund.rs | 5 ++ 3 files changed, 95 insertions(+) diff --git a/lightning/src/ln/offers_tests.rs b/lightning/src/ln/offers_tests.rs index 5a8211899..ddae4acca 100644 --- a/lightning/src/ln/offers_tests.rs +++ b/lightning/src/ln/offers_tests.rs @@ -213,3 +213,88 @@ fn creates_and_pays_for_refund_using_one_hop_blinded_path() { claim_bolt12_payment(bob, &[alice]); expect_recent_payment!(bob, RecentPaymentDetails::Fulfilled, payment_id); } + +/// Checks that an invoice for an offer without any blinded paths can be requested. Note that while +/// the requested is sent directly using the node's pubkey, the response and the payment still use +/// blinded paths as required by the spec. +#[test] +fn pays_for_offer_without_blinded_paths() { + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000); + + let alice = &nodes[0]; + let alice_id = alice.node.get_our_node_id(); + let bob = &nodes[1]; + let bob_id = bob.node.get_our_node_id(); + + let offer = alice.node + .create_offer_builder("coffee".to_string()).unwrap() + .clear_paths() + .amount_msats(10_000_000) + .build().unwrap(); + assert_eq!(offer.signing_pubkey(), alice_id); + assert!(offer.paths().is_empty()); + + let payment_id = PaymentId([1; 32]); + bob.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None).unwrap(); + expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id); + + let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap(); + alice.onion_messenger.handle_onion_message(&bob_id, &onion_message); + + let onion_message = alice.onion_messenger.next_onion_message_for_peer(bob_id).unwrap(); + bob.onion_messenger.handle_onion_message(&alice_id, &onion_message); + + let invoice = extract_invoice(bob, &onion_message); + route_bolt12_payment(bob, &[alice], &invoice); + expect_recent_payment!(bob, RecentPaymentDetails::Pending, payment_id); + + claim_bolt12_payment(bob, &[alice]); + expect_recent_payment!(bob, RecentPaymentDetails::Fulfilled, payment_id); +} + +/// Checks that a refund without any blinded paths can be paid. Note that while the invoice is sent +/// directly using the node's pubkey, the payment still use blinded paths as required by the spec. +#[test] +fn pays_for_refund_without_blinded_paths() { + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000); + + let alice = &nodes[0]; + let alice_id = alice.node.get_our_node_id(); + let bob = &nodes[1]; + let bob_id = bob.node.get_our_node_id(); + + let absolute_expiry = Duration::from_secs(u64::MAX); + let payment_id = PaymentId([1; 32]); + let refund = bob.node + .create_refund_builder( + "refund".to_string(), 10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), None + ) + .unwrap() + .clear_paths() + .build().unwrap(); + assert_eq!(refund.payer_id(), bob_id); + assert!(refund.paths().is_empty()); + expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id); + + alice.node.request_refund_payment(&refund).unwrap(); + + let onion_message = alice.onion_messenger.next_onion_message_for_peer(bob_id).unwrap(); + bob.onion_messenger.handle_onion_message(&alice_id, &onion_message); + + let invoice = extract_invoice(bob, &onion_message); + route_bolt12_payment(bob, &[alice], &invoice); + expect_recent_payment!(bob, RecentPaymentDetails::Pending, payment_id); + + claim_bolt12_payment(bob, &[alice]); + expect_recent_payment!(bob, RecentPaymentDetails::Fulfilled, payment_id); +} diff --git a/lightning/src/offers/offer.rs b/lightning/src/offers/offer.rs index ab7fe62cb..802813fd1 100644 --- a/lightning/src/offers/offer.rs +++ b/lightning/src/offers/offer.rs @@ -339,6 +339,11 @@ impl<'a, M: MetadataStrategy, T: secp256k1::Signing> OfferBuilder<'a, M, T> { self } + pub(crate) fn clear_paths(mut self) -> Self { + self.offer.paths = None; + self + } + pub(super) fn build_unchecked(self) -> Offer { self.build_without_checks() } diff --git a/lightning/src/offers/refund.rs b/lightning/src/offers/refund.rs index ce39841df..ba3ab1d1e 100644 --- a/lightning/src/offers/refund.rs +++ b/lightning/src/offers/refund.rs @@ -297,6 +297,11 @@ impl<'a, T: secp256k1::Signing> RefundBuilder<'a, T> { #[cfg(test)] impl<'a, T: secp256k1::Signing> RefundBuilder<'a, T> { + pub(crate) fn clear_paths(mut self) -> Self { + self.refund.paths = None; + self + } + fn features_unchecked(mut self, features: InvoiceRequestFeatures) -> Self { self.refund.features = features; self From 533c3cc868fefc83a533525f4b55237b0eddbeb5 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Thu, 14 Dec 2023 23:46:12 -0600 Subject: [PATCH 11/19] Functional tests for offers with blinded paths --- lightning/src/ln/offers_tests.rs | 203 ++++++++++++++++++++++++++++++- 1 file changed, 201 insertions(+), 2 deletions(-) diff --git a/lightning/src/ln/offers_tests.rs b/lightning/src/ln/offers_tests.rs index ddae4acca..09239dff6 100644 --- a/lightning/src/ln/offers_tests.rs +++ b/lightning/src/ln/offers_tests.rs @@ -17,9 +17,9 @@ use core::time::Duration; use crate::blinded_path::BlindedPath; use crate::events::{Event, MessageSendEventsProvider, PaymentPurpose}; -use crate::ln::channelmanager::{PaymentId, RecentPaymentDetails, Retry}; +use crate::ln::channelmanager::{PaymentId, RecentPaymentDetails, Retry, self}; use crate::ln::functional_test_utils::*; -use crate::ln::msgs::{OnionMessage, OnionMessageHandler}; +use crate::ln::msgs::{ChannelMessageHandler, Init, OnionMessage, OnionMessageHandler}; use crate::offers::invoice::Bolt12Invoice; use crate::offers::invoice_request::InvoiceRequest; use crate::onion_message::messenger::PeeledOnion; @@ -40,6 +40,36 @@ macro_rules! expect_recent_payment { } } +fn connect_peers<'a, 'b, 'c>(node_a: &Node<'a, 'b, 'c>, node_b: &Node<'a, 'b, 'c>) { + let node_id_a = node_a.node.get_our_node_id(); + let node_id_b = node_b.node.get_our_node_id(); + + let init_a = Init { + features: node_a.init_features(&node_id_b), + networks: None, + remote_network_address: None, + }; + let init_b = Init { + features: node_b.init_features(&node_id_a), + networks: None, + remote_network_address: None, + }; + + node_a.node.peer_connected(&node_id_b, &init_b, true).unwrap(); + node_b.node.peer_connected(&node_id_a, &init_a, false).unwrap(); + node_a.onion_messenger.peer_connected(&node_id_b, &init_b, true).unwrap(); + node_b.onion_messenger.peer_connected(&node_id_a, &init_a, false).unwrap(); +} + +fn disconnect_peers<'a, 'b, 'c>(node_a: &Node<'a, 'b, 'c>, peers: &[&Node<'a, 'b, 'c>]) { + for node_b in peers { + node_a.node.peer_disconnected(&node_b.node.get_our_node_id()); + node_b.node.peer_disconnected(&node_a.node.get_our_node_id()); + node_a.onion_messenger.peer_disconnected(&node_b.node.get_our_node_id()); + node_b.onion_messenger.peer_disconnected(&node_a.node.get_our_node_id()); + } +} + fn route_bolt12_payment<'a, 'b, 'c>( node: &Node<'a, 'b, 'c>, path: &[&Node<'a, 'b, 'c>], invoice: &Bolt12Invoice ) { @@ -103,6 +133,175 @@ fn extract_invoice<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, message: &OnionMessage) } } +/// Checks that an offer can be paid through blinded paths and that ephemeral pubkeys are used +/// rather than exposing a node's pubkey. +#[test] +fn creates_and_pays_for_offer_using_two_hop_blinded_path() { + let mut accept_forward_cfg = test_default_channel_config(); + accept_forward_cfg.accept_forwards_to_priv_channels = true; + + let mut features = channelmanager::provided_init_features(&accept_forward_cfg); + features.set_onion_messages_optional(); + features.set_route_blinding_optional(); + + let chanmon_cfgs = create_chanmon_cfgs(6); + let node_cfgs = create_node_cfgs(6, &chanmon_cfgs); + + *node_cfgs[1].override_init_features.borrow_mut() = Some(features); + + let node_chanmgrs = create_node_chanmgrs( + 6, &node_cfgs, &[None, Some(accept_forward_cfg), None, None, None, None] + ); + let nodes = create_network(6, &node_cfgs, &node_chanmgrs); + + create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000); + create_unannounced_chan_between_nodes_with_value(&nodes, 2, 3, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 1, 4, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 1, 5, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 2, 4, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 2, 5, 10_000_000, 1_000_000_000); + + let (alice, bob, charlie, david) = (&nodes[0], &nodes[1], &nodes[2], &nodes[3]); + let alice_id = alice.node.get_our_node_id(); + let bob_id = bob.node.get_our_node_id(); + let charlie_id = charlie.node.get_our_node_id(); + let david_id = david.node.get_our_node_id(); + + disconnect_peers(alice, &[charlie, david, &nodes[4], &nodes[5]]); + disconnect_peers(david, &[bob, &nodes[4], &nodes[5]]); + + let offer = alice.node + .create_offer_builder("coffee".to_string()).unwrap() + .amount_msats(10_000_000) + .build().unwrap(); + assert_ne!(offer.signing_pubkey(), alice_id); + assert!(!offer.paths().is_empty()); + for path in offer.paths() { + assert_eq!(path.introduction_node_id, bob_id); + } + + let payment_id = PaymentId([1; 32]); + david.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None) + .unwrap(); + expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id); + + connect_peers(david, bob); + + let onion_message = david.onion_messenger.next_onion_message_for_peer(bob_id).unwrap(); + bob.onion_messenger.handle_onion_message(&david_id, &onion_message); + + connect_peers(alice, charlie); + + let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap(); + alice.onion_messenger.handle_onion_message(&bob_id, &onion_message); + + let (invoice_request, reply_path) = extract_invoice_request(alice, &onion_message); + assert_eq!(invoice_request.amount_msats(), None); + assert_ne!(invoice_request.payer_id(), david_id); + assert_eq!(reply_path.unwrap().introduction_node_id, charlie_id); + + let onion_message = alice.onion_messenger.next_onion_message_for_peer(charlie_id).unwrap(); + charlie.onion_messenger.handle_onion_message(&alice_id, &onion_message); + + let onion_message = charlie.onion_messenger.next_onion_message_for_peer(david_id).unwrap(); + david.onion_messenger.handle_onion_message(&charlie_id, &onion_message); + + let invoice = extract_invoice(david, &onion_message); + assert_eq!(invoice.amount_msats(), 10_000_000); + assert_ne!(invoice.signing_pubkey(), alice_id); + assert!(!invoice.payment_paths().is_empty()); + for (_, path) in invoice.payment_paths() { + assert_eq!(path.introduction_node_id, bob_id); + } + + route_bolt12_payment(david, &[charlie, bob, alice], &invoice); + expect_recent_payment!(david, RecentPaymentDetails::Pending, payment_id); + + claim_bolt12_payment(david, &[charlie, bob, alice]); + expect_recent_payment!(david, RecentPaymentDetails::Fulfilled, payment_id); +} + +/// Checks that a refund can be paid through blinded paths and that ephemeral pubkeys are used +/// rather than exposing a node's pubkey. +#[test] +fn creates_and_pays_for_refund_using_two_hop_blinded_path() { + let mut accept_forward_cfg = test_default_channel_config(); + accept_forward_cfg.accept_forwards_to_priv_channels = true; + + let mut features = channelmanager::provided_init_features(&accept_forward_cfg); + features.set_onion_messages_optional(); + features.set_route_blinding_optional(); + + let chanmon_cfgs = create_chanmon_cfgs(6); + let node_cfgs = create_node_cfgs(6, &chanmon_cfgs); + + *node_cfgs[1].override_init_features.borrow_mut() = Some(features); + + let node_chanmgrs = create_node_chanmgrs( + 6, &node_cfgs, &[None, Some(accept_forward_cfg), None, None, None, None] + ); + let nodes = create_network(6, &node_cfgs, &node_chanmgrs); + + create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000); + create_unannounced_chan_between_nodes_with_value(&nodes, 2, 3, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 1, 4, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 1, 5, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 2, 4, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 2, 5, 10_000_000, 1_000_000_000); + + let (alice, bob, charlie, david) = (&nodes[0], &nodes[1], &nodes[2], &nodes[3]); + let alice_id = alice.node.get_our_node_id(); + let bob_id = bob.node.get_our_node_id(); + let charlie_id = charlie.node.get_our_node_id(); + let david_id = david.node.get_our_node_id(); + + disconnect_peers(alice, &[charlie, david, &nodes[4], &nodes[5]]); + disconnect_peers(david, &[bob, &nodes[4], &nodes[5]]); + + let absolute_expiry = Duration::from_secs(u64::MAX); + let payment_id = PaymentId([1; 32]); + let refund = david.node + .create_refund_builder( + "refund".to_string(), 10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), None + ) + .unwrap() + .build().unwrap(); + assert_eq!(refund.amount_msats(), 10_000_000); + assert_eq!(refund.absolute_expiry(), Some(absolute_expiry)); + assert_ne!(refund.payer_id(), david_id); + assert!(!refund.paths().is_empty()); + for path in refund.paths() { + assert_eq!(path.introduction_node_id, charlie_id); + } + expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id); + + alice.node.request_refund_payment(&refund).unwrap(); + + connect_peers(alice, charlie); + + let onion_message = alice.onion_messenger.next_onion_message_for_peer(charlie_id).unwrap(); + charlie.onion_messenger.handle_onion_message(&alice_id, &onion_message); + + let onion_message = charlie.onion_messenger.next_onion_message_for_peer(david_id).unwrap(); + david.onion_messenger.handle_onion_message(&charlie_id, &onion_message); + + let invoice = extract_invoice(david, &onion_message); + assert_eq!(invoice.amount_msats(), 10_000_000); + assert_ne!(invoice.signing_pubkey(), alice_id); + assert!(!invoice.payment_paths().is_empty()); + for (_, path) in invoice.payment_paths() { + assert_eq!(path.introduction_node_id, bob_id); + } + + route_bolt12_payment(david, &[charlie, bob, alice], &invoice); + expect_recent_payment!(david, RecentPaymentDetails::Pending, payment_id); + + claim_bolt12_payment(david, &[charlie, bob, alice]); + expect_recent_payment!(david, RecentPaymentDetails::Fulfilled, payment_id); +} + /// Checks that an offer can be paid through a one-hop blinded path and that ephemeral pubkeys are /// used rather than exposing a node's pubkey. However, the node's pubkey is still used as the /// introduction node of the blinded path. From 757315076aa0253bff055ebd7c34a64406abb8a5 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Tue, 19 Dec 2023 16:06:55 -0600 Subject: [PATCH 12/19] Functional tests for failing without blinded paths --- lightning/src/ln/offers_tests.rs | 75 ++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/lightning/src/ln/offers_tests.rs b/lightning/src/ln/offers_tests.rs index 09239dff6..fcbd4c22e 100644 --- a/lightning/src/ln/offers_tests.rs +++ b/lightning/src/ln/offers_tests.rs @@ -22,6 +22,7 @@ use crate::ln::functional_test_utils::*; use crate::ln::msgs::{ChannelMessageHandler, Init, OnionMessage, OnionMessageHandler}; use crate::offers::invoice::Bolt12Invoice; use crate::offers::invoice_request::InvoiceRequest; +use crate::offers::parse::Bolt12SemanticError; use crate::onion_message::messenger::PeeledOnion; use crate::onion_message::offers::OffersMessage; use crate::onion_message::packet::ParsedOnionMessageContents; @@ -497,3 +498,77 @@ fn pays_for_refund_without_blinded_paths() { claim_bolt12_payment(bob, &[alice]); expect_recent_payment!(bob, RecentPaymentDetails::Fulfilled, payment_id); } + +/// Fails creating an offer when a blinded path cannot be created without exposing the node's id. +#[test] +fn fails_creating_offer_without_blinded_paths() { + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000); + + match nodes[0].node.create_offer_builder("coffee".to_string()) { + Ok(_) => panic!("Expected error"), + Err(e) => assert_eq!(e, Bolt12SemanticError::MissingPaths), + } +} + +/// Fails creating a refund when a blinded path cannot be created without exposing the node's id. +#[test] +fn fails_creating_refund_without_blinded_paths() { + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000); + + let absolute_expiry = Duration::from_secs(u64::MAX); + let payment_id = PaymentId([1; 32]); + + match nodes[0].node.create_refund_builder( + "refund".to_string(), 10_000, absolute_expiry, payment_id, Retry::Attempts(0), None + ) { + Ok(_) => panic!("Expected error"), + Err(e) => assert_eq!(e, Bolt12SemanticError::MissingPaths), + } + + assert!(nodes[0].node.list_recent_payments().is_empty()); +} + +/// Fails creating an invoice request when a blinded reply path cannot be created without exposing +/// the node's id. +#[test] +fn fails_creating_invoice_request_without_blinded_reply_path() { + let chanmon_cfgs = create_chanmon_cfgs(6); + let node_cfgs = create_node_cfgs(6, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(6, &node_cfgs, &[None, None, None, None, None, None]); + let nodes = create_network(6, &node_cfgs, &node_chanmgrs); + + create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000); + create_unannounced_chan_between_nodes_with_value(&nodes, 2, 3, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 1, 4, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 1, 5, 10_000_000, 1_000_000_000); + + let (alice, bob, charlie, david) = (&nodes[0], &nodes[1], &nodes[2], &nodes[3]); + + disconnect_peers(alice, &[charlie, david, &nodes[4], &nodes[5]]); + disconnect_peers(david, &[bob, &nodes[4], &nodes[5]]); + + let offer = alice.node + .create_offer_builder("coffee".to_string()).unwrap() + .amount_msats(10_000_000) + .build().unwrap(); + + let payment_id = PaymentId([1; 32]); + + match david.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None) { + Ok(_) => panic!("Expected error"), + Err(e) => assert_eq!(e, Bolt12SemanticError::MissingPaths), + } + + assert!(nodes[0].node.list_recent_payments().is_empty()); +} From 0a7a90a78f9222a84dd84f3baa3868684fcc5a27 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Tue, 19 Dec 2023 17:24:11 -0600 Subject: [PATCH 13/19] Offers functional tests for duplicated payment ids --- lightning/src/ln/offers_tests.rs | 68 ++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/lightning/src/ln/offers_tests.rs b/lightning/src/ln/offers_tests.rs index fcbd4c22e..4b4997242 100644 --- a/lightning/src/ln/offers_tests.rs +++ b/lightning/src/ln/offers_tests.rs @@ -572,3 +572,71 @@ fn fails_creating_invoice_request_without_blinded_reply_path() { assert!(nodes[0].node.list_recent_payments().is_empty()); } + +#[test] +fn fails_creating_invoice_request_with_duplicate_payment_id() { + let chanmon_cfgs = create_chanmon_cfgs(6); + let node_cfgs = create_node_cfgs(6, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(6, &node_cfgs, &[None, None, None, None, None, None]); + let nodes = create_network(6, &node_cfgs, &node_chanmgrs); + + create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000); + create_unannounced_chan_between_nodes_with_value(&nodes, 2, 3, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 1, 4, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 1, 5, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 2, 4, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 2, 5, 10_000_000, 1_000_000_000); + + let (alice, _bob, charlie, david) = (&nodes[0], &nodes[1], &nodes[2], &nodes[3]); + + disconnect_peers(alice, &[charlie, david, &nodes[4], &nodes[5]]); + + let offer = alice.node + .create_offer_builder("coffee".to_string()).unwrap() + .amount_msats(10_000_000) + .build().unwrap(); + + let payment_id = PaymentId([1; 32]); + assert!( + david.node.pay_for_offer( + &offer, None, None, None, payment_id, Retry::Attempts(0), None + ).is_ok() + ); + expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id); + + match david.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None) { + Ok(_) => panic!("Expected error"), + Err(e) => assert_eq!(e, Bolt12SemanticError::DuplicatePaymentId), + } + + expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id); +} + +#[test] +fn fails_creating_refund_with_duplicate_payment_id() { + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000); + + let absolute_expiry = Duration::from_secs(u64::MAX); + let payment_id = PaymentId([1; 32]); + assert!( + nodes[0].node.create_refund_builder( + "refund".to_string(), 10_000, absolute_expiry, payment_id, Retry::Attempts(0), None + ).is_ok() + ); + expect_recent_payment!(nodes[0], RecentPaymentDetails::AwaitingInvoice, payment_id); + + match nodes[0].node.create_refund_builder( + "refund".to_string(), 10_000, absolute_expiry, payment_id, Retry::Attempts(0), None + ) { + Ok(_) => panic!("Expected error"), + Err(e) => assert_eq!(e, Bolt12SemanticError::DuplicatePaymentId), + } + + expect_recent_payment!(nodes[0], RecentPaymentDetails::AwaitingInvoice, payment_id); +} From e40387fc856d9b62bb89d37df139d0310459eb82 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Wed, 20 Dec 2023 14:32:53 -0600 Subject: [PATCH 14/19] Add Features::clear_route_blinding for testing --- lightning/src/ln/features.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lightning/src/ln/features.rs b/lightning/src/ln/features.rs index 2e732b17a..79f869a4c 100644 --- a/lightning/src/ln/features.rs +++ b/lightning/src/ln/features.rs @@ -932,6 +932,13 @@ impl Features { } } +impl Features { + #[cfg(test)] + pub(crate) fn clear_route_blinding(&mut self) { + ::clear_bits(&mut self.flags); + } +} + #[cfg(test)] impl Features { pub(crate) fn unknown() -> Self { From 1c51aa17f3a6df190a0998e696075801d1eb57cf Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Wed, 20 Dec 2023 14:37:31 -0600 Subject: [PATCH 15/19] Functional tests for failing without payment paths --- lightning/src/ln/offers_tests.rs | 132 +++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) diff --git a/lightning/src/ln/offers_tests.rs b/lightning/src/ln/offers_tests.rs index 4b4997242..d745f0731 100644 --- a/lightning/src/ln/offers_tests.rs +++ b/lightning/src/ln/offers_tests.rs @@ -21,6 +21,7 @@ use crate::ln::channelmanager::{PaymentId, RecentPaymentDetails, Retry, self}; use crate::ln::functional_test_utils::*; use crate::ln::msgs::{ChannelMessageHandler, Init, OnionMessage, OnionMessageHandler}; use crate::offers::invoice::Bolt12Invoice; +use crate::offers::invoice_error::InvoiceError; use crate::offers::invoice_request::InvoiceRequest; use crate::offers::parse::Bolt12SemanticError; use crate::onion_message::messenger::PeeledOnion; @@ -134,6 +135,23 @@ fn extract_invoice<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, message: &OnionMessage) } } +fn extract_invoice_error<'a, 'b, 'c>( + node: &Node<'a, 'b, 'c>, message: &OnionMessage +) -> InvoiceError { + match node.onion_messenger.peel_onion_message(message) { + Ok(PeeledOnion::Receive(message, _, _)) => match message { + ParsedOnionMessageContents::Offers(offers_message) => match offers_message { + OffersMessage::InvoiceRequest(invoice_request) => panic!("Unexpected invoice_request: {:?}", invoice_request), + OffersMessage::Invoice(invoice) => panic!("Unexpected invoice: {:?}", invoice), + OffersMessage::InvoiceError(error) => error, + }, + ParsedOnionMessageContents::Custom(message) => panic!("Unexpected custom message: {:?}", message), + }, + Ok(PeeledOnion::Forward(_, _)) => panic!("Unexpected onion message forward"), + Err(e) => panic!("Failed to process onion message {:?}", e), + } +} + /// Checks that an offer can be paid through blinded paths and that ephemeral pubkeys are used /// rather than exposing a node's pubkey. #[test] @@ -640,3 +658,117 @@ fn fails_creating_refund_with_duplicate_payment_id() { expect_recent_payment!(nodes[0], RecentPaymentDetails::AwaitingInvoice, payment_id); } + +#[test] +fn fails_sending_invoice_without_blinded_payment_paths_for_offer() { + let mut accept_forward_cfg = test_default_channel_config(); + accept_forward_cfg.accept_forwards_to_priv_channels = true; + + // Clearing route_blinding prevents forming any payment paths since the node is unannounced. + let mut features = channelmanager::provided_init_features(&accept_forward_cfg); + features.set_onion_messages_optional(); + features.clear_route_blinding(); + + let chanmon_cfgs = create_chanmon_cfgs(6); + let node_cfgs = create_node_cfgs(6, &chanmon_cfgs); + + *node_cfgs[1].override_init_features.borrow_mut() = Some(features); + + let node_chanmgrs = create_node_chanmgrs( + 6, &node_cfgs, &[None, Some(accept_forward_cfg), None, None, None, None] + ); + let nodes = create_network(6, &node_cfgs, &node_chanmgrs); + + create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000); + create_unannounced_chan_between_nodes_with_value(&nodes, 2, 3, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 1, 4, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 1, 5, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 2, 4, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 2, 5, 10_000_000, 1_000_000_000); + + let (alice, bob, charlie, david) = (&nodes[0], &nodes[1], &nodes[2], &nodes[3]); + let alice_id = alice.node.get_our_node_id(); + let bob_id = bob.node.get_our_node_id(); + let charlie_id = charlie.node.get_our_node_id(); + let david_id = david.node.get_our_node_id(); + + disconnect_peers(alice, &[charlie, david, &nodes[4], &nodes[5]]); + disconnect_peers(david, &[bob, &nodes[4], &nodes[5]]); + + let offer = alice.node + .create_offer_builder("coffee".to_string()).unwrap() + .amount_msats(10_000_000) + .build().unwrap(); + + let payment_id = PaymentId([1; 32]); + david.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None) + .unwrap(); + + connect_peers(david, bob); + + let onion_message = david.onion_messenger.next_onion_message_for_peer(bob_id).unwrap(); + bob.onion_messenger.handle_onion_message(&david_id, &onion_message); + + connect_peers(alice, charlie); + + let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap(); + alice.onion_messenger.handle_onion_message(&bob_id, &onion_message); + + let onion_message = alice.onion_messenger.next_onion_message_for_peer(charlie_id).unwrap(); + charlie.onion_messenger.handle_onion_message(&alice_id, &onion_message); + + let onion_message = charlie.onion_messenger.next_onion_message_for_peer(david_id).unwrap(); + david.onion_messenger.handle_onion_message(&charlie_id, &onion_message); + + let invoice_error = extract_invoice_error(david, &onion_message); + assert_eq!(invoice_error, InvoiceError::from(Bolt12SemanticError::MissingPaths)); +} + +#[test] +fn fails_sending_invoice_without_blinded_payment_paths_for_refund() { + let mut accept_forward_cfg = test_default_channel_config(); + accept_forward_cfg.accept_forwards_to_priv_channels = true; + + // Clearing route_blinding prevents forming any payment paths since the node is unannounced. + let mut features = channelmanager::provided_init_features(&accept_forward_cfg); + features.set_onion_messages_optional(); + features.clear_route_blinding(); + + let chanmon_cfgs = create_chanmon_cfgs(6); + let node_cfgs = create_node_cfgs(6, &chanmon_cfgs); + + *node_cfgs[1].override_init_features.borrow_mut() = Some(features); + + let node_chanmgrs = create_node_chanmgrs( + 6, &node_cfgs, &[None, Some(accept_forward_cfg), None, None, None, None] + ); + let nodes = create_network(6, &node_cfgs, &node_chanmgrs); + + create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000); + create_unannounced_chan_between_nodes_with_value(&nodes, 2, 3, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 1, 4, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 1, 5, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 2, 4, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 2, 5, 10_000_000, 1_000_000_000); + + let (alice, bob, charlie, david) = (&nodes[0], &nodes[1], &nodes[2], &nodes[3]); + + disconnect_peers(alice, &[charlie, david, &nodes[4], &nodes[5]]); + disconnect_peers(david, &[bob, &nodes[4], &nodes[5]]); + + let absolute_expiry = Duration::from_secs(u64::MAX); + let payment_id = PaymentId([1; 32]); + let refund = david.node + .create_refund_builder( + "refund".to_string(), 10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), None + ) + .unwrap() + .build().unwrap(); + + match alice.node.request_refund_payment(&refund) { + Ok(_) => panic!("Expected error"), + Err(e) => assert_eq!(e, Bolt12SemanticError::MissingPaths), + } +} From aef46d8c821c0c9561f974a4fad72f5ad69c9041 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Wed, 20 Dec 2023 17:52:09 -0600 Subject: [PATCH 16/19] Functional test for failing duplicate payments --- lightning/src/ln/offers_tests.rs | 94 ++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/lightning/src/ln/offers_tests.rs b/lightning/src/ln/offers_tests.rs index d745f0731..c8381f2a1 100644 --- a/lightning/src/ln/offers_tests.rs +++ b/lightning/src/ln/offers_tests.rs @@ -772,3 +772,97 @@ fn fails_sending_invoice_without_blinded_payment_paths_for_refund() { Err(e) => assert_eq!(e, Bolt12SemanticError::MissingPaths), } } + +#[test] +fn fails_paying_invoice_more_than_once() { + let mut accept_forward_cfg = test_default_channel_config(); + accept_forward_cfg.accept_forwards_to_priv_channels = true; + + let mut features = channelmanager::provided_init_features(&accept_forward_cfg); + features.set_onion_messages_optional(); + features.set_route_blinding_optional(); + + let chanmon_cfgs = create_chanmon_cfgs(6); + let node_cfgs = create_node_cfgs(6, &chanmon_cfgs); + + *node_cfgs[1].override_init_features.borrow_mut() = Some(features); + + let node_chanmgrs = create_node_chanmgrs( + 6, &node_cfgs, &[None, Some(accept_forward_cfg), None, None, None, None] + ); + let nodes = create_network(6, &node_cfgs, &node_chanmgrs); + + create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000); + create_unannounced_chan_between_nodes_with_value(&nodes, 2, 3, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 1, 4, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 1, 5, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 2, 4, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 2, 5, 10_000_000, 1_000_000_000); + + let (alice, bob, charlie, david) = (&nodes[0], &nodes[1], &nodes[2], &nodes[3]); + let alice_id = alice.node.get_our_node_id(); + let bob_id = bob.node.get_our_node_id(); + let charlie_id = charlie.node.get_our_node_id(); + let david_id = david.node.get_our_node_id(); + + disconnect_peers(alice, &[charlie, david, &nodes[4], &nodes[5]]); + disconnect_peers(david, &[bob, &nodes[4], &nodes[5]]); + + let absolute_expiry = Duration::from_secs(u64::MAX); + let payment_id = PaymentId([1; 32]); + let refund = david.node + .create_refund_builder( + "refund".to_string(), 10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), None + ) + .unwrap() + .build().unwrap(); + expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id); + + // Alice sends the first invoice + alice.node.request_refund_payment(&refund).unwrap(); + + connect_peers(alice, charlie); + + let onion_message = alice.onion_messenger.next_onion_message_for_peer(charlie_id).unwrap(); + charlie.onion_messenger.handle_onion_message(&alice_id, &onion_message); + + let onion_message = charlie.onion_messenger.next_onion_message_for_peer(david_id).unwrap(); + david.onion_messenger.handle_onion_message(&charlie_id, &onion_message); + + // David pays the first invoice + let invoice1 = extract_invoice(david, &onion_message); + + route_bolt12_payment(david, &[charlie, bob, alice], &invoice1); + expect_recent_payment!(david, RecentPaymentDetails::Pending, payment_id); + + claim_bolt12_payment(david, &[charlie, bob, alice]); + expect_recent_payment!(david, RecentPaymentDetails::Fulfilled, payment_id); + + disconnect_peers(alice, &[charlie]); + + // Alice sends the second invoice + alice.node.request_refund_payment(&refund).unwrap(); + + connect_peers(alice, charlie); + connect_peers(david, bob); + + let onion_message = alice.onion_messenger.next_onion_message_for_peer(charlie_id).unwrap(); + charlie.onion_messenger.handle_onion_message(&alice_id, &onion_message); + + let onion_message = charlie.onion_messenger.next_onion_message_for_peer(david_id).unwrap(); + david.onion_messenger.handle_onion_message(&charlie_id, &onion_message); + + let invoice2 = extract_invoice(david, &onion_message); + assert_eq!(invoice1.payer_metadata(), invoice2.payer_metadata()); + + // David sends an error instead of paying the second invoice + let onion_message = david.onion_messenger.next_onion_message_for_peer(bob_id).unwrap(); + bob.onion_messenger.handle_onion_message(&david_id, &onion_message); + + let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap(); + alice.onion_messenger.handle_onion_message(&bob_id, &onion_message); + + let invoice_error = extract_invoice_error(alice, &onion_message); + assert_eq!(invoice_error, InvoiceError::from_string("DuplicateInvoice".to_string())); +} From ffc24c3af9a274557434a64f74ad72f338c78419 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Wed, 20 Dec 2023 18:05:42 -0600 Subject: [PATCH 17/19] Add ASCII art diagram explaining offers_tests --- lightning/src/ln/offers_tests.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/lightning/src/ln/offers_tests.rs b/lightning/src/ln/offers_tests.rs index c8381f2a1..0038bdd79 100644 --- a/lightning/src/ln/offers_tests.rs +++ b/lightning/src/ln/offers_tests.rs @@ -13,6 +13,32 @@ //! to initiate and request payment for them, respectively. It also manages the payment flow via //! implementing [`OffersMessageHandler`]. This module tests that functionality, including the //! resulting [`Event`] generation. +//! +//! Two-node success tests use an announced channel: +//! +//! Alice --- Bob +//! +//! While two-node failure tests use an unannounced channel: +//! +//! Alice ... Bob +//! +//! Six-node tests use unannounced channels for the sender and recipient and announced channels for +//! the rest of the network. +//! +//! nodes[4] +//! / \ +//! / \ +//! / \ +//! Alice ... Bob -------- Charlie ... David +//! \ / +//! \ / +//! \ / +//! nodes[5] +//! +//! Unnamed nodes are needed to ensure unannounced nodes can create two-hop blinded paths. +//! +//! Nodes without channels are disconnected and connected as needed to ensure that deterministic +//! blinded paths are used. use core::time::Duration; use crate::blinded_path::BlindedPath; From 5aeb6c5167b42a151b25e27308c9efda7b46b2e9 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Thu, 4 Jan 2024 17:39:18 -0600 Subject: [PATCH 18/19] Reorganize #[cfg(not(c_bindings))] imports --- lightning/src/ln/peer_handler.rs | 6 ++---- lightning/src/onion_message/messenger.rs | 3 +-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/lightning/src/ln/peer_handler.rs b/lightning/src/ln/peer_handler.rs index 9a9aea9ae..efc5a970e 100644 --- a/lightning/src/ln/peer_handler.rs +++ b/lightning/src/ln/peer_handler.rs @@ -24,14 +24,10 @@ use crate::ln::ChannelId; use crate::ln::features::{InitFeatures, NodeFeatures}; use crate::ln::msgs; use crate::ln::msgs::{ChannelMessageHandler, LightningError, SocketAddress, OnionMessageHandler, RoutingMessageHandler}; -#[cfg(not(c_bindings))] -use crate::ln::channelmanager::{SimpleArcChannelManager, SimpleRefChannelManager}; use crate::util::ser::{VecWriter, Writeable, Writer}; use crate::ln::peer_channel_encryptor::{PeerChannelEncryptor, NextNoiseStep, MessageBuf, MSG_BUF_ALLOC_SIZE}; use crate::ln::wire; use crate::ln::wire::{Encode, Type}; -#[cfg(not(c_bindings))] -use crate::onion_message::messenger::{SimpleArcOnionMessenger, SimpleRefOnionMessenger}; use crate::onion_message::messenger::{CustomOnionMessageHandler, PendingOnionMessage}; use crate::onion_message::offers::{OffersMessage, OffersMessageHandler}; use crate::onion_message::packet::OnionMessageContents; @@ -52,6 +48,8 @@ use core::convert::Infallible; use std::error; #[cfg(not(c_bindings))] use { + crate::ln::channelmanager::{SimpleArcChannelManager, SimpleRefChannelManager}, + crate::onion_message::messenger::{SimpleArcOnionMessenger, SimpleRefOnionMessenger}, crate::routing::gossip::{NetworkGraph, P2PGossipSync}, crate::sign::KeysManager, crate::sync::Arc, diff --git a/lightning/src/onion_message/messenger.rs b/lightning/src/onion_message/messenger.rs index 350fed49d..3678dea84 100644 --- a/lightning/src/onion_message/messenger.rs +++ b/lightning/src/onion_message/messenger.rs @@ -20,8 +20,6 @@ use crate::blinded_path::message::{advance_path_by_one, ForwardTlvs, ReceiveTlvs use crate::blinded_path::utils; use crate::events::{Event, EventHandler, EventsProvider}; use crate::sign::{EntropySource, NodeSigner, Recipient}; -#[cfg(not(c_bindings))] -use crate::ln::channelmanager::{SimpleArcChannelManager, SimpleRefChannelManager}; use crate::ln::features::{InitFeatures, NodeFeatures}; use crate::ln::msgs::{self, OnionMessage, OnionMessageHandler, SocketAddress}; use crate::ln::onion_utils; @@ -42,6 +40,7 @@ use crate::prelude::*; #[cfg(not(c_bindings))] use { crate::sign::KeysManager, + crate::ln::channelmanager::{SimpleArcChannelManager, SimpleRefChannelManager}, crate::ln::peer_handler::IgnoringMessageHandler, crate::sync::Arc, }; From 4532fb52f0d8665642d055e7c86e824f96a2d48b Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Thu, 4 Jan 2024 23:40:43 -0600 Subject: [PATCH 19/19] Fix bench build warning --- lightning/src/ln/channelmanager.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index d0badb8b2..b7521e32c 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -12487,7 +12487,7 @@ pub mod bench { use bitcoin::blockdata::locktime::absolute::LockTime; use bitcoin::hashes::Hash; use bitcoin::hashes::sha256::Hash as Sha256; - use bitcoin::{Block, Transaction, TxOut}; + use bitcoin::{Transaction, TxOut}; use crate::sync::{Arc, Mutex, RwLock};