Skip to content

Commit 48fa2fd

Browse files
authored
Merge pull request #2024 from TheBlueMatt/2023-02-6conf-pub-hints
Include a route hint for public, not-yet-announced channels
2 parents 217c3e0 + cab6c97 commit 48fa2fd

File tree

2 files changed

+113
-22
lines changed

2 files changed

+113
-22
lines changed

lightning-invoice/src/utils.rs

Lines changed: 104 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -512,8 +512,10 @@ fn _create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_has
512512
/// * Always select the channel with the highest inbound capacity per counterparty node
513513
/// * Prefer channels with capacity at least `min_inbound_capacity_msat` and where the channel
514514
/// `is_usable` (i.e. the peer is connected).
515-
/// * If any public channel exists, the returned `RouteHint`s will be empty, and the sender will
516-
/// need to find the path by looking at the public channels instead
515+
/// * If any public channel exists, only public [`RouteHint`]s will be returned.
516+
/// * If any public, announced, channel exists (i.e. a channel with 7+ confs, to ensure the
517+
/// announcement has had a chance to propagate), no [`RouteHint`]s will be returned, as the
518+
/// sender is expected to find the path by looking at the public channels instead.
517519
fn filter_channels<L: Deref>(
518520
channels: Vec<ChannelDetails>, min_inbound_capacity_msat: Option<u64>, logger: &L
519521
) -> Vec<RouteHint> where L::Target: Logger {
@@ -522,6 +524,7 @@ fn filter_channels<L: Deref>(
522524
let mut min_capacity_channel_exists = false;
523525
let mut online_channel_exists = false;
524526
let mut online_min_capacity_channel_exists = false;
527+
let mut has_pub_unconf_chan = false;
525528

526529
log_trace!(logger, "Considering {} channels for invoice route hints", channels.len());
527530
for channel in channels.into_iter().filter(|chan| chan.is_channel_ready) {
@@ -531,11 +534,18 @@ fn filter_channels<L: Deref>(
531534
}
532535

533536
if channel.is_public {
534-
// If any public channel exists, return no hints and let the sender
535-
// look at the public channels instead.
536-
log_trace!(logger, "Not including channels in invoice route hints on account of public channel {}",
537-
log_bytes!(channel.channel_id));
538-
return vec![]
537+
if channel.confirmations.is_some() && channel.confirmations < Some(7) {
538+
// If we have a public channel, but it doesn't have enough confirmations to (yet)
539+
// be in the public network graph (and have gotten a chance to propagate), include
540+
// route hints but only for public channels to protect private channel privacy.
541+
has_pub_unconf_chan = true;
542+
} else {
543+
// If any public channel exists, return no hints and let the sender
544+
// look at the public channels instead.
545+
log_trace!(logger, "Not including channels in invoice route hints on account of public channel {}",
546+
log_bytes!(channel.channel_id));
547+
return vec![]
548+
}
539549
}
540550

541551
if channel.inbound_capacity_msat >= min_inbound_capacity {
@@ -557,20 +567,32 @@ fn filter_channels<L: Deref>(
557567
match filtered_channels.entry(channel.counterparty.node_id) {
558568
hash_map::Entry::Occupied(mut entry) => {
559569
let current_max_capacity = entry.get().inbound_capacity_msat;
560-
if channel.inbound_capacity_msat < current_max_capacity {
570+
// If this channel is public and the previous channel is not, ensure we replace the
571+
// previous channel to avoid announcing non-public channels.
572+
let new_now_public = channel.is_public && !entry.get().is_public;
573+
// If the public-ness of the channel has not changed (in which case simply defer to
574+
// `new_now_public), and this channel has a greater capacity, prefer to announce
575+
// this channel.
576+
let new_higher_capacity = channel.is_public == entry.get().is_public &&
577+
channel.inbound_capacity_msat > current_max_capacity;
578+
if new_now_public || new_higher_capacity {
579+
log_trace!(logger,
580+
"Preferring counterparty {} channel {} (SCID {:?}, {} msats) over {} (SCID {:?}, {} msats) for invoice route hints",
581+
log_pubkey!(channel.counterparty.node_id),
582+
log_bytes!(channel.channel_id), channel.short_channel_id,
583+
channel.inbound_capacity_msat,
584+
log_bytes!(entry.get().channel_id), entry.get().short_channel_id,
585+
current_max_capacity);
586+
entry.insert(channel);
587+
} else {
561588
log_trace!(logger,
562-
"Preferring counterparty {} channel {} ({} msats) over {} ({} msats) for invoice route hints",
589+
"Preferring counterparty {} channel {} (SCID {:?}, {} msats) over {} (SCID {:?}, {} msats) for invoice route hints",
563590
log_pubkey!(channel.counterparty.node_id),
564-
log_bytes!(entry.get().channel_id), current_max_capacity,
565-
log_bytes!(channel.channel_id), channel.inbound_capacity_msat);
566-
continue;
591+
log_bytes!(entry.get().channel_id), entry.get().short_channel_id,
592+
current_max_capacity,
593+
log_bytes!(channel.channel_id), channel.short_channel_id,
594+
channel.inbound_capacity_msat);
567595
}
568-
log_trace!(logger,
569-
"Preferring counterparty {} channel {} ({} msats) over {} ({} msats) for invoice route hints",
570-
log_pubkey!(channel.counterparty.node_id),
571-
log_bytes!(channel.channel_id), channel.inbound_capacity_msat,
572-
log_bytes!(entry.get().channel_id), current_max_capacity);
573-
entry.insert(channel);
574596
}
575597
hash_map::Entry::Vacant(entry) => {
576598
entry.insert(channel);
@@ -600,7 +622,12 @@ fn filter_channels<L: Deref>(
600622
.map(|(_, channel)| channel)
601623
.filter(|channel| {
602624
let has_enough_capacity = channel.inbound_capacity_msat >= min_inbound_capacity;
603-
let include_channel = if online_min_capacity_channel_exists {
625+
let include_channel = if has_pub_unconf_chan {
626+
// If we have a public channel, but it doesn't have enough confirmations to (yet)
627+
// be in the public network graph (and have gotten a chance to propagate), include
628+
// route hints but only for public channels to protect private channel privacy.
629+
channel.is_public
630+
} else if online_min_capacity_channel_exists {
604631
has_enough_capacity && channel.is_usable
605632
} else if min_capacity_channel_exists && online_channel_exists {
606633
// If there are some online channels and some min_capacity channels, but no
@@ -620,7 +647,7 @@ fn filter_channels<L: Deref>(
620647
log_trace!(logger, "Ignoring channel {} without enough capacity for invoice route hints",
621648
log_bytes!(channel.channel_id));
622649
} else {
623-
debug_assert!(!channel.is_usable);
650+
debug_assert!(!channel.is_usable || (has_pub_unconf_chan && !channel.is_public));
624651
log_trace!(logger, "Ignoring channel {} with disconnected peer",
625652
log_bytes!(channel.channel_id));
626653
}
@@ -789,6 +816,63 @@ mod test {
789816
assert_eq!(invoice.payment_hash(), &sha256::Hash::from_slice(&payment_hash.0[..]).unwrap());
790817
}
791818

819+
#[test]
820+
fn test_hints_has_only_public_confd_channels() {
821+
let chanmon_cfgs = create_chanmon_cfgs(2);
822+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
823+
let mut config = test_default_channel_config();
824+
config.channel_handshake_config.minimum_depth = 1;
825+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(config), Some(config)]);
826+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
827+
828+
// Create a private channel with lots of capacity and a lower value public channel (without
829+
// confirming the funding tx yet).
830+
let unannounced_scid = create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 0);
831+
let conf_tx = create_chan_between_nodes_with_value_init(&nodes[0], &nodes[1], 10_000, 0);
832+
833+
// Before the channel is available, we should include the unannounced_scid.
834+
let mut scid_aliases = HashSet::new();
835+
scid_aliases.insert(unannounced_scid.0.short_channel_id_alias.unwrap());
836+
match_invoice_routes(Some(5000), &nodes[1], scid_aliases.clone());
837+
838+
// However after we mine the funding tx and exchange channel_ready messages for the public
839+
// channel we'll immediately switch to including it as a route hint, even though it isn't
840+
// yet announced.
841+
let pub_channel_scid = mine_transaction(&nodes[0], &conf_tx);
842+
let node_a_pub_channel_ready = get_event_msg!(nodes[0], MessageSendEvent::SendChannelReady, nodes[1].node.get_our_node_id());
843+
nodes[1].node.handle_channel_ready(&nodes[0].node.get_our_node_id(), &node_a_pub_channel_ready);
844+
845+
assert_eq!(mine_transaction(&nodes[1], &conf_tx), pub_channel_scid);
846+
let events = nodes[1].node.get_and_clear_pending_msg_events();
847+
assert_eq!(events.len(), 2);
848+
if let MessageSendEvent::SendChannelReady { msg, .. } = &events[0] {
849+
nodes[0].node.handle_channel_ready(&nodes[1].node.get_our_node_id(), msg);
850+
} else { panic!(); }
851+
if let MessageSendEvent::SendChannelUpdate { msg, .. } = &events[1] {
852+
nodes[0].node.handle_channel_update(&nodes[1].node.get_our_node_id(), msg);
853+
} else { panic!(); }
854+
855+
nodes[1].node.handle_channel_update(&nodes[0].node.get_our_node_id(), &get_event_msg!(nodes[0], MessageSendEvent::SendChannelUpdate, nodes[1].node.get_our_node_id()));
856+
857+
expect_channel_ready_event(&nodes[0], &nodes[1].node.get_our_node_id());
858+
expect_channel_ready_event(&nodes[1], &nodes[0].node.get_our_node_id());
859+
860+
scid_aliases.clear();
861+
scid_aliases.insert(node_a_pub_channel_ready.short_channel_id_alias.unwrap());
862+
match_invoice_routes(Some(5000), &nodes[1], scid_aliases.clone());
863+
// This also applies even if the amount is more than the payment amount, to ensure users
864+
// dont screw up their privacy.
865+
match_invoice_routes(Some(50_000_000), &nodes[1], scid_aliases.clone());
866+
867+
// The same remains true until the channel has 7 confirmations, at which point we include
868+
// no hints.
869+
connect_blocks(&nodes[1], 5);
870+
match_invoice_routes(Some(5000), &nodes[1], scid_aliases.clone());
871+
connect_blocks(&nodes[1], 1);
872+
get_event_msg!(nodes[1], MessageSendEvent::SendAnnouncementSignatures, nodes[0].node.get_our_node_id());
873+
match_invoice_routes(Some(5000), &nodes[1], HashSet::new());
874+
}
875+
792876
#[test]
793877
fn test_hints_includes_single_channels_to_nodes() {
794878
let chanmon_cfgs = create_chanmon_cfgs(3);

lightning/src/ln/functional_test_utils.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,12 @@ pub fn confirm_transaction<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, tx: &Tran
6363
scid
6464
}
6565
/// Mine a single block containing the given transaction
66-
pub fn mine_transaction<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, tx: &Transaction) {
66+
///
67+
/// Returns the SCID a channel confirmed in the given transaction will have, assuming the funding
68+
/// output is the 1st output in the transaction.
69+
pub fn mine_transaction<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, tx: &Transaction) -> u64 {
6770
let height = node.best_block_info().1 + 1;
68-
confirm_transaction_at(node, tx, height);
71+
confirm_transaction_at(node, tx, height)
6972
}
7073
/// Mine a single block containing the given transactions
7174
pub fn mine_transactions<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, txn: &[&Transaction]) {
@@ -1098,6 +1101,10 @@ pub fn create_unannounced_chan_between_nodes_with_value<'a, 'b, 'c, 'd>(nodes: &
10981101
nodes[a].node.handle_funding_signed(&nodes[b].node.get_our_node_id(), &cs_funding_signed);
10991102
check_added_monitors!(nodes[a], 1);
11001103

1104+
assert_eq!(nodes[a].tx_broadcaster.txn_broadcasted.lock().unwrap().len(), 1);
1105+
assert_eq!(nodes[a].tx_broadcaster.txn_broadcasted.lock().unwrap()[0], tx);
1106+
nodes[a].tx_broadcaster.txn_broadcasted.lock().unwrap().clear();
1107+
11011108
let conf_height = core::cmp::max(nodes[a].best_block_info().1 + 1, nodes[b].best_block_info().1 + 1);
11021109
confirm_transaction_at(&nodes[a], &tx, conf_height);
11031110
connect_blocks(&nodes[a], CHAN_CONFIRM_DEPTH - 1);

0 commit comments

Comments
 (0)