Merge pull request #2111 from TheBlueMatt/2023-03-sent-persist-order-prep
[rust-lightning] / lightning-invoice / src / utils.rs
index 90d5c626bff4344838421612525a87f7d79022c3..fac9989497b6cfc8e78b10398599ca19e1136f77 100644 (file)
@@ -7,7 +7,7 @@ use bech32::ToBase32;
 use bitcoin_hashes::Hash;
 use lightning::chain;
 use lightning::chain::chaininterface::{BroadcasterInterface, FeeEstimator};
-use lightning::chain::keysinterface::{Recipient, NodeSigner, SignerProvider, EntropySource};
+use lightning::sign::{Recipient, NodeSigner, SignerProvider, EntropySource};
 use lightning::ln::{PaymentHash, PaymentSecret};
 use lightning::ln::channelmanager::{ChannelDetails, ChannelManager, MIN_FINAL_CLTV_EXPIRY_DELTA};
 use lightning::ln::channelmanager::{PhantomRouteHints, MIN_CLTV_EXPIRY_DELTA};
@@ -50,7 +50,7 @@ use core::time::Duration;
 /// invoices in its `sign_invoice` implementation ([`PhantomKeysManager`] satisfies this
 /// requirement).
 ///
-/// [`PhantomKeysManager`]: lightning::chain::keysinterface::PhantomKeysManager
+/// [`PhantomKeysManager`]: lightning::sign::PhantomKeysManager
 /// [`ChannelManager::get_phantom_route_hints`]: lightning::ln::channelmanager::ChannelManager::get_phantom_route_hints
 /// [`ChannelManager::create_inbound_payment`]: lightning::ln::channelmanager::ChannelManager::create_inbound_payment
 /// [`ChannelManager::create_inbound_payment_for_hash`]: lightning::ln::channelmanager::ChannelManager::create_inbound_payment_for_hash
@@ -86,9 +86,11 @@ where
 ///   participating node
 /// * It is fine to cache `phantom_route_hints` and reuse it across invoices, as long as the data is
 ///   updated when a channel becomes disabled or closes
-/// * Note that if too many channels are included in [`PhantomRouteHints::channels`], the invoice
-///   may be too long for QR code scanning. To fix this, `PhantomRouteHints::channels` may be pared
-///   down
+/// * Note that the route hints generated from `phantom_route_hints` will be limited to a maximum
+///   of 3 hints to ensure that the invoice can be scanned in a QR code. These hints are selected
+///   in the order that the nodes in `PhantomRouteHints` are specified, selecting one hint per node
+///   until the maximum is hit. Callers may provide as many `PhantomRouteHints::channels` as
+///   desired, but note that some nodes will be trimmed if more than 3 nodes are provided.
 ///
 /// `description_hash` is a SHA-256 hash of the description text
 ///
@@ -105,7 +107,7 @@ where
 /// invoices in its `sign_invoice` implementation ([`PhantomKeysManager`] satisfies this
 /// requirement).
 ///
-/// [`PhantomKeysManager`]: lightning::chain::keysinterface::PhantomKeysManager
+/// [`PhantomKeysManager`]: lightning::sign::PhantomKeysManager
 /// [`ChannelManager::get_phantom_route_hints`]: lightning::ln::channelmanager::ChannelManager::get_phantom_route_hints
 /// [`ChannelManager::create_inbound_payment`]: lightning::ln::channelmanager::ChannelManager::create_inbound_payment
 /// [`ChannelManager::create_inbound_payment_for_hash`]: lightning::ln::channelmanager::ChannelManager::create_inbound_payment_for_hash
@@ -200,6 +202,39 @@ where
                invoice = invoice.amount_milli_satoshis(amt);
        }
 
+       for route_hint in select_phantom_hints(amt_msat, phantom_route_hints, logger) {
+               invoice = invoice.private_route(route_hint);
+       }
+
+       let raw_invoice = match invoice.build_raw() {
+               Ok(inv) => inv,
+               Err(e) => return Err(SignOrCreationError::CreationError(e))
+       };
+       let hrp_str = raw_invoice.hrp.to_string();
+       let hrp_bytes = hrp_str.as_bytes();
+       let data_without_signature = raw_invoice.data.to_base32();
+       let signed_raw_invoice = raw_invoice.sign(|_| node_signer.sign_invoice(hrp_bytes, &data_without_signature, Recipient::PhantomNode));
+       match signed_raw_invoice {
+               Ok(inv) => Ok(Invoice::from_signed(inv).unwrap()),
+               Err(e) => Err(SignOrCreationError::SignError(e))
+       }
+}
+
+/// Utility to select route hints for phantom invoices.
+/// See [`PhantomKeysManager`] for more information on phantom node payments.
+///
+/// To ensure that the phantom invoice is still readable by QR code, we limit to 3 hints per invoice:
+/// * Select up to three channels per node.
+/// * Select one hint from each node, up to three hints or until we run out of hints.
+///
+/// [`PhantomKeysManager`]: lightning::sign::PhantomKeysManager
+fn select_phantom_hints<L: Deref>(amt_msat: Option<u64>, phantom_route_hints: Vec<PhantomRouteHints>,
+       logger: L) -> Vec<RouteHint>
+where
+       L::Target: Logger,
+{
+       let mut phantom_hints: Vec<Vec<RouteHint>> = Vec::new();
+
        for PhantomRouteHints { channels, phantom_scid, real_node_pubkey } in phantom_route_hints {
                log_trace!(logger, "Generating phantom route hints for node {}",
                        log_pubkey!(real_node_pubkey));
@@ -212,7 +247,7 @@ where
                if route_hints.is_empty() {
                        route_hints.push(RouteHint(vec![]))
                }
-               for mut route_hint in route_hints {
+               for route_hint in &mut route_hints {
                        route_hint.0.push(RouteHintHop {
                                src_node_id: real_node_pubkey,
                                short_channel_id: phantom_scid,
@@ -223,21 +258,37 @@ where
                                cltv_expiry_delta: MIN_CLTV_EXPIRY_DELTA,
                                htlc_minimum_msat: None,
                                htlc_maximum_msat: None,});
-                       invoice = invoice.private_route(route_hint.clone());
                }
+
+               phantom_hints.push(route_hints);
        }
 
-       let raw_invoice = match invoice.build_raw() {
-               Ok(inv) => inv,
-               Err(e) => return Err(SignOrCreationError::CreationError(e))
-       };
-       let hrp_str = raw_invoice.hrp.to_string();
-       let hrp_bytes = hrp_str.as_bytes();
-       let data_without_signature = raw_invoice.data.to_base32();
-       let signed_raw_invoice = raw_invoice.sign(|_| node_signer.sign_invoice(hrp_bytes, &data_without_signature, Recipient::PhantomNode));
-       match signed_raw_invoice {
-               Ok(inv) => Ok(Invoice::from_signed(inv).unwrap()),
-               Err(e) => Err(SignOrCreationError::SignError(e))
+       // We have one vector per real node involved in creating the phantom invoice. To distribute
+       // the hints across our real nodes we add one hint from each in turn until no node has any hints
+       // left (if one node has more hints than any other, these will accumulate at the end of the
+       // vector).
+       let mut invoice_hints: Vec<RouteHint> = Vec::new();
+       let mut hint_idx = 0;
+
+       loop {
+               let mut remaining_hints = false;
+
+               for hints in phantom_hints.iter() {
+                       if invoice_hints.len() == 3 {
+                               return invoice_hints
+                       }
+
+                       if hint_idx < hints.len() {
+                               invoice_hints.push(hints[hint_idx].clone());
+                               remaining_hints = true
+                       }
+               }
+
+               if !remaining_hints {
+                       return invoice_hints
+               }
+
+               hint_idx +=1;
        }
 }
 
@@ -578,7 +629,7 @@ fn sort_and_filter_channels<L: Deref>(
                                // previous channel to avoid announcing non-public channels.
                                let new_now_public = channel.is_public && !entry.get().is_public;
                                // Decide whether we prefer the currently selected channel with the node to the new one,
-                               // based on their inbound capacity. 
+                               // based on their inbound capacity.
                                let prefer_current = prefer_current_channel(min_inbound_capacity_msat, current_max_capacity,
                                        channel.inbound_capacity_msat);
                                // If the public-ness of the channel has not changed (in which case simply defer to
@@ -717,13 +768,13 @@ mod test {
        use crate::{Currency, Description, InvoiceDescription, SignOrCreationError, CreationError};
        use bitcoin_hashes::{Hash, sha256};
        use bitcoin_hashes::sha256::Hash as Sha256;
-       use lightning::chain::keysinterface::{EntropySource, PhantomKeysManager};
+       use lightning::sign::PhantomKeysManager;
        use lightning::events::{MessageSendEvent, MessageSendEventsProvider, Event};
        use lightning::ln::{PaymentPreimage, PaymentHash};
-       use lightning::ln::channelmanager::{PhantomRouteHints, MIN_FINAL_CLTV_EXPIRY_DELTA, PaymentId};
+       use lightning::ln::channelmanager::{PhantomRouteHints, MIN_FINAL_CLTV_EXPIRY_DELTA, PaymentId, RecipientOnionFields, Retry};
        use lightning::ln::functional_test_utils::*;
        use lightning::ln::msgs::ChannelMessageHandler;
-       use lightning::routing::router::{PaymentParameters, RouteParameters, find_route};
+       use lightning::routing::router::{PaymentParameters, RouteParameters};
        use lightning::util::test_utils;
        use lightning::util::config::UserConfig;
        use crate::utils::create_invoice_from_channelmanager_and_duration_since_epoch;
@@ -742,10 +793,10 @@ mod test {
 
                // Minimum set, prefer candidate channel over minimum + buffer.
                assert_eq!(crate::utils::prefer_current_channel(Some(100), 105, 125), false);
-               
+
                // Minimum set, both channels sufficient, prefer smaller current channel.
                assert_eq!(crate::utils::prefer_current_channel(Some(100), 115, 125), true);
-               
+
                // Minimum set, both channels sufficient, prefer smaller candidate channel.
                assert_eq!(crate::utils::prefer_current_channel(Some(100), 200, 160), false);
 
@@ -793,20 +844,12 @@ mod test {
                        payment_params,
                        final_value_msat: invoice.amount_milli_satoshis().unwrap(),
                };
-               let first_hops = nodes[0].node.list_usable_channels();
-               let network_graph = &node_cfgs[0].network_graph;
-               let logger = test_utils::TestLogger::new();
-               let scorer = test_utils::TestScorer::new();
-               let random_seed_bytes = chanmon_cfgs[1].keys_manager.get_secure_random_bytes();
-               let route = find_route(
-                       &nodes[0].node.get_our_node_id(), &route_params, network_graph,
-                       Some(&first_hops.iter().collect::<Vec<_>>()), &logger, &scorer, &random_seed_bytes
-               ).unwrap();
-
                let payment_event = {
                        let mut payment_hash = PaymentHash([0; 32]);
                        payment_hash.0.copy_from_slice(&invoice.payment_hash().as_ref()[0..32]);
-                       nodes[0].node.send_payment(&route, payment_hash, &Some(*invoice.payment_secret()), PaymentId(payment_hash.0)).unwrap();
+                       nodes[0].node.send_payment(payment_hash,
+                               RecipientOnionFields::secret_only(*invoice.payment_secret()),
+                               PaymentId(payment_hash.0), route_params, Retry::Attempts(0)).unwrap();
                        let mut added_monitors = nodes[0].chain_monitor.added_monitors.lock().unwrap();
                        assert_eq!(added_monitors.len(), 1);
                        added_monitors.clear();
@@ -1257,19 +1300,12 @@ mod test {
                        payment_params,
                        final_value_msat: invoice.amount_milli_satoshis().unwrap(),
                };
-               let first_hops = nodes[0].node.list_usable_channels();
-               let network_graph = &node_cfgs[0].network_graph;
-               let logger = test_utils::TestLogger::new();
-               let scorer = test_utils::TestScorer::new();
-               let random_seed_bytes = chanmon_cfgs[1].keys_manager.get_secure_random_bytes();
-               let route = find_route(
-                       &nodes[0].node.get_our_node_id(), &params, network_graph,
-                       Some(&first_hops.iter().collect::<Vec<_>>()), &logger, &scorer, &random_seed_bytes
-               ).unwrap();
                let (payment_event, fwd_idx) = {
                        let mut payment_hash = PaymentHash([0; 32]);
                        payment_hash.0.copy_from_slice(&invoice.payment_hash().as_ref()[0..32]);
-                       nodes[0].node.send_payment(&route, payment_hash, &Some(*invoice.payment_secret()), PaymentId(payment_hash.0)).unwrap();
+                       nodes[0].node.send_payment(payment_hash,
+                               RecipientOnionFields::secret_only(*invoice.payment_secret()),
+                               PaymentId(payment_hash.0), params, Retry::Attempts(0)).unwrap();
                        let mut added_monitors = nodes[0].chain_monitor.added_monitors.lock().unwrap();
                        assert_eq!(added_monitors.len(), 1);
                        added_monitors.clear();
@@ -1298,7 +1334,7 @@ mod test {
                nodes[fwd_idx].node.process_pending_htlc_forwards();
 
                let payment_preimage_opt = if user_generated_pmt_hash { None } else { Some(payment_preimage) };
-               expect_payment_claimable!(&nodes[fwd_idx], payment_hash, payment_secret, payment_amt, payment_preimage_opt, route.paths[0].last().unwrap().pubkey);
+               expect_payment_claimable!(&nodes[fwd_idx], payment_hash, payment_secret, payment_amt, payment_preimage_opt, invoice.recover_payee_pub_key());
                do_claim_payment_along_route(&nodes[0], &[&vec!(&nodes[fwd_idx])[..]], false, payment_preimage);
                let events = nodes[0].node.get_and_clear_pending_events();
                assert_eq!(events.len(), 2);
@@ -1702,7 +1738,98 @@ mod test {
                );
        }
 
-       #[cfg(feature = "std")]
+       #[test]
+       fn test_multi_node_hints_limited_to_3() {
+               let mut chanmon_cfgs = create_chanmon_cfgs(6);
+               let seed_1 = [42 as u8; 32];
+               let seed_2 = [43 as u8; 32];
+               let seed_3 = [44 as u8; 32];
+               let seed_4 = [45 as u8; 32];
+               let cross_node_seed = [44 as u8; 32];
+               chanmon_cfgs[2].keys_manager.backing = PhantomKeysManager::new(&seed_1, 43, 44, &cross_node_seed);
+               chanmon_cfgs[3].keys_manager.backing = PhantomKeysManager::new(&seed_2, 43, 44, &cross_node_seed);
+               chanmon_cfgs[4].keys_manager.backing = PhantomKeysManager::new(&seed_3, 43, 44, &cross_node_seed);
+               chanmon_cfgs[5].keys_manager.backing = PhantomKeysManager::new(&seed_4, 43, 44, &cross_node_seed);
+               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);
+
+               // Setup each phantom node with two channels from distinct peers.
+               let chan_0_2 = create_unannounced_chan_between_nodes_with_value(&nodes, 0, 2, 10_000, 0);
+               let chan_1_2 = create_unannounced_chan_between_nodes_with_value(&nodes, 1, 2, 20_000, 0);
+               let chan_0_3 = create_unannounced_chan_between_nodes_with_value(&nodes, 0, 3, 20_000, 0);
+               let _chan_1_3 = create_unannounced_chan_between_nodes_with_value(&nodes, 1, 3, 10_000, 0);
+               let chan_0_4 = create_unannounced_chan_between_nodes_with_value(&nodes, 0, 4, 20_000, 0);
+               let _chan_1_4 = create_unannounced_chan_between_nodes_with_value(&nodes, 1, 4, 10_000, 0);
+               let _chan_0_5 = create_unannounced_chan_between_nodes_with_value(&nodes, 0, 5, 20_000, 0);
+               let _chan_1_5 = create_unannounced_chan_between_nodes_with_value(&nodes, 1, 5, 10_000, 0);
+
+               // Set invoice amount > all channels inbound so that every one is eligible for inclusion
+               // and hints will be sorted by largest inbound capacity.
+               let invoice_amt = Some(100_000_000);
+
+               // With 4 phantom nodes, assert that we include 1 hint per node, up to 3 nodes.
+               let mut scid_aliases = HashSet::new();
+               scid_aliases.insert(chan_1_2.0.short_channel_id_alias.unwrap());
+               scid_aliases.insert(chan_0_3.0.short_channel_id_alias.unwrap());
+               scid_aliases.insert(chan_0_4.0.short_channel_id_alias.unwrap());
+
+               match_multi_node_invoice_routes(
+                       invoice_amt,
+                       &nodes[3],
+                       vec![&nodes[2], &nodes[3], &nodes[4], &nodes[5]],
+                       scid_aliases,
+                       false,
+               );
+
+               // With 2 phantom nodes, assert that we include no more than 3 hints.
+               let mut scid_aliases = HashSet::new();
+               scid_aliases.insert(chan_1_2.0.short_channel_id_alias.unwrap());
+               scid_aliases.insert(chan_0_3.0.short_channel_id_alias.unwrap());
+               scid_aliases.insert(chan_0_2.0.short_channel_id_alias.unwrap());
+
+               match_multi_node_invoice_routes(
+                       invoice_amt,
+                       &nodes[3],
+                       vec![&nodes[2], &nodes[3]],
+                       scid_aliases,
+                       false,
+               );
+       }
+
+       #[test]
+       fn test_multi_node_hints_at_least_3() {
+               let mut chanmon_cfgs = create_chanmon_cfgs(5);
+               let seed_1 = [42 as u8; 32];
+               let seed_2 = [43 as u8; 32];
+               let cross_node_seed = [44 as u8; 32];
+               chanmon_cfgs[1].keys_manager.backing = PhantomKeysManager::new(&seed_1, 43, 44, &cross_node_seed);
+               chanmon_cfgs[2].keys_manager.backing = PhantomKeysManager::new(&seed_2, 43, 44, &cross_node_seed);
+               let node_cfgs = create_node_cfgs(5, &chanmon_cfgs);
+               let node_chanmgrs = create_node_chanmgrs(5, &node_cfgs, &[None, None, None, None, None]);
+               let nodes = create_network(5, &node_cfgs, &node_chanmgrs);
+
+               let _chan_0_3 = create_unannounced_chan_between_nodes_with_value(&nodes, 0, 3, 10_000, 0);
+               let chan_1_3 = create_unannounced_chan_between_nodes_with_value(&nodes, 1, 3, 20_000, 0);
+               let chan_2_3 = create_unannounced_chan_between_nodes_with_value(&nodes, 2, 3, 30_000, 0);
+               let chan_0_4 = create_unannounced_chan_between_nodes_with_value(&nodes, 0, 4, 10_000, 0);
+
+               // Since the invoice amount is above all channels inbound, all four are eligible. Test that
+               // we still include 3 hints from 2 distinct nodes sorted by inbound.
+               let mut scid_aliases = HashSet::new();
+               scid_aliases.insert(chan_1_3.0.short_channel_id_alias.unwrap());
+               scid_aliases.insert(chan_2_3.0.short_channel_id_alias.unwrap());
+               scid_aliases.insert(chan_0_4.0.short_channel_id_alias.unwrap());
+
+               match_multi_node_invoice_routes(
+                       Some(100_000_000),
+                       &nodes[3],
+                       vec![&nodes[3], &nodes[4],],
+                       scid_aliases,
+                       false,
+               );
+       }
+
        fn match_multi_node_invoice_routes<'a, 'b: 'a, 'c: 'b>(
                invoice_amt: Option<u64>,
                invoice_node: &Node<'a, 'b, 'c>,