Skip to content

Commit 3028e88

Browse files
committed
Include a route hint for public, not-yet-announced channels
If we have a public channel which doesn't yet have six confirmations the network can't possibly know about it as we cannot have announced it yet. However, because we refuse to include route-hints if we have any public channels, we will generate invoices that no one can pay. Thus, if we have any public, not-yet-announced channels, include them as a route-hint.
1 parent 137b77c commit 3028e88

File tree

2 files changed

+104
-18
lines changed

2 files changed

+104
-18
lines changed

lightning-invoice/src/utils.rs

Lines changed: 95 additions & 16 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 {
@@ -559,19 +569,26 @@ fn filter_channels<L: Deref>(
559569
match filtered_channels.entry(channel.counterparty.node_id) {
560570
hash_map::Entry::Occupied(mut entry) => {
561571
let current_max_capacity = entry.get().inbound_capacity_msat;
562-
if channel.inbound_capacity_msat < current_max_capacity {
572+
let new_now_public = channel.is_public && !entry.get().is_public;
573+
let new_higher_capacity = channel.is_public == entry.get().is_public &&
574+
channel.inbound_capacity_msat > current_max_capacity;
575+
if !new_now_public && !new_higher_capacity {
563576
log_trace!(logger,
564-
"Preferring counterparty {} channel {} ({} msats) over {} ({} msats) for invoice route hints",
577+
"Preferring counterparty {} channel {} (SCID {:?}, {} msats) over {} (SCID {:?}, {} msats) for invoice route hints",
565578
log_pubkey!(channel.counterparty.node_id),
566-
log_bytes!(entry.get().channel_id), current_max_capacity,
567-
log_bytes!(channel.channel_id), channel.inbound_capacity_msat);
579+
log_bytes!(entry.get().channel_id), entry.get().short_channel_id,
580+
current_max_capacity,
581+
log_bytes!(channel.channel_id), channel.short_channel_id,
582+
channel.inbound_capacity_msat);
568583
continue;
569584
}
570585
log_trace!(logger,
571-
"Preferring counterparty {} channel {} ({} msats) over {} ({} msats) for invoice route hints",
586+
"Preferring counterparty {} channel {} (SCID {:?}, {} msats) over {} (SCID {:?}, {} msats) for invoice route hints",
572587
log_pubkey!(channel.counterparty.node_id),
573-
log_bytes!(channel.channel_id), channel.inbound_capacity_msat,
574-
log_bytes!(entry.get().channel_id), current_max_capacity);
588+
log_bytes!(channel.channel_id), channel.short_channel_id,
589+
channel.inbound_capacity_msat,
590+
log_bytes!(entry.get().channel_id), entry.get().short_channel_id,
591+
current_max_capacity);
575592
entry.insert(channel);
576593
}
577594
hash_map::Entry::Vacant(entry) => {
@@ -602,7 +619,12 @@ fn filter_channels<L: Deref>(
602619
.map(|(_, channel)| channel)
603620
.filter(|channel| {
604621
let has_enough_capacity = channel.inbound_capacity_msat >= min_inbound_capacity;
605-
let include_channel = if online_min_capacity_channel_exists {
622+
let include_channel = if has_pub_unconf_chan {
623+
// If we have a public channel, but it doesn't have enough confirmations to (yet)
624+
// be in the public network graph (and have gotten a chance to propagate), include
625+
// route hints but only for public channels to protect private channel privacy.
626+
channel.is_public
627+
} else if online_min_capacity_channel_exists {
606628
has_enough_capacity && channel.is_usable
607629
} else if min_capacity_channel_exists && online_channel_exists {
608630
// If there are some online channels and some min_capacity channels, but no
@@ -622,7 +644,7 @@ fn filter_channels<L: Deref>(
622644
log_trace!(logger, "Ignoring channel {} without enough capacity for invoice route hints",
623645
log_bytes!(channel.channel_id));
624646
} else {
625-
debug_assert!(!channel.is_usable);
647+
debug_assert!(!channel.is_usable || (has_pub_unconf_chan && !channel.is_public));
626648
log_trace!(logger, "Ignoring channel {} with disconnected peer",
627649
log_bytes!(channel.channel_id));
628650
}
@@ -792,6 +814,63 @@ mod test {
792814
assert_eq!(invoice.payment_hash(), &sha256::Hash::from_slice(&payment_hash.0[..]).unwrap());
793815
}
794816

817+
#[test]
818+
fn test_hints_has_only_public_confd_channels() {
819+
let chanmon_cfgs = create_chanmon_cfgs(2);
820+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
821+
let mut config = test_default_channel_config();
822+
config.channel_handshake_config.minimum_depth = 1;
823+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(config), Some(config)]);
824+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
825+
826+
// Create a private channel with lots of capacity and a lower value public channel (without
827+
// confirming the funding tx yet).
828+
let unannounced_scid = create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 0);
829+
let conf_tx = create_chan_between_nodes_with_value_init(&nodes[0], &nodes[1], 10_000, 0);
830+
831+
// Before the channel is available, we should include the unannounced_scid.
832+
let mut scid_aliases = HashSet::new();
833+
scid_aliases.insert(unannounced_scid.0.short_channel_id_alias.unwrap());
834+
match_invoice_routes(Some(5000), &nodes[1], scid_aliases.clone());
835+
836+
// However after we mine the funding tx and exchange channel_ready messages for the public
837+
// channel we'll immediately switch to including it as a route hint, even though isn't yet
838+
// announced.
839+
let pub_channel_scid = mine_transaction(&nodes[0], &conf_tx);
840+
let node_a_pub_channel_ready = get_event_msg!(nodes[0], MessageSendEvent::SendChannelReady, nodes[1].node.get_our_node_id());
841+
nodes[1].node.handle_channel_ready(&nodes[0].node.get_our_node_id(), &node_a_pub_channel_ready);
842+
843+
assert_eq!(mine_transaction(&nodes[1], &conf_tx), pub_channel_scid);
844+
let events = nodes[1].node.get_and_clear_pending_msg_events();
845+
assert_eq!(events.len(), 2);
846+
if let MessageSendEvent::SendChannelReady { msg, .. } = &events[0] {
847+
nodes[0].node.handle_channel_ready(&nodes[1].node.get_our_node_id(), msg);
848+
} else { panic!(); }
849+
if let MessageSendEvent::SendChannelUpdate { msg, .. } = &events[1] {
850+
nodes[0].node.handle_channel_update(&nodes[1].node.get_our_node_id(), msg);
851+
} else { panic!(); }
852+
853+
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()));
854+
855+
expect_channel_ready_event(&nodes[0], &nodes[1].node.get_our_node_id());
856+
expect_channel_ready_event(&nodes[1], &nodes[0].node.get_our_node_id());
857+
858+
scid_aliases.clear();
859+
scid_aliases.insert(node_a_pub_channel_ready.short_channel_id_alias.unwrap());
860+
match_invoice_routes(Some(5000), &nodes[1], scid_aliases.clone());
861+
// This also applies even if the amount is more than the payment amount, to ensure users
862+
// dont screw up their privacy.
863+
match_invoice_routes(Some(50_000_000), &nodes[1], scid_aliases.clone());
864+
865+
// The same remains true until the channel has 7 confirmations, at which point we include
866+
// no hints.
867+
connect_blocks(&nodes[1], 5);
868+
match_invoice_routes(Some(5000), &nodes[1], scid_aliases.clone());
869+
connect_blocks(&nodes[1], 1);
870+
get_event_msg!(nodes[1], MessageSendEvent::SendAnnouncementSignatures, nodes[0].node.get_our_node_id());
871+
match_invoice_routes(Some(5000), &nodes[1], HashSet::new());
872+
}
873+
795874
#[test]
796875
fn test_hints_includes_single_channels_to_nodes() {
797876
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
@@ -62,9 +62,12 @@ pub fn confirm_transaction<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, tx: &Tran
6262
scid
6363
}
6464
/// Mine a single block containing the given transaction
65-
pub fn mine_transaction<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, tx: &Transaction) {
65+
///
66+
/// Returns the SCID a channel confirmed in the given transaction will have, assuming the funding
67+
/// output is the 1st output in the transaction.
68+
pub fn mine_transaction<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, tx: &Transaction) -> u64 {
6669
let height = node.best_block_info().1 + 1;
67-
confirm_transaction_at(node, tx, height);
70+
confirm_transaction_at(node, tx, height)
6871
}
6972
/// Mine a single block containing the given transactions
7073
pub fn mine_transactions<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, txn: &[&Transaction]) {
@@ -1062,6 +1065,10 @@ pub fn create_unannounced_chan_between_nodes_with_value<'a, 'b, 'c, 'd>(nodes: &
10621065
nodes[a].node.handle_funding_signed(&nodes[b].node.get_our_node_id(), &cs_funding_signed);
10631066
check_added_monitors!(nodes[a], 1);
10641067

1068+
assert_eq!(nodes[a].tx_broadcaster.txn_broadcasted.lock().unwrap().len(), 1);
1069+
assert_eq!(nodes[a].tx_broadcaster.txn_broadcasted.lock().unwrap()[0], tx);
1070+
nodes[a].tx_broadcaster.txn_broadcasted.lock().unwrap().clear();
1071+
10651072
let conf_height = core::cmp::max(nodes[a].best_block_info().1 + 1, nodes[b].best_block_info().1 + 1);
10661073
confirm_transaction_at(&nodes[a], &tx, conf_height);
10671074
connect_blocks(&nodes[a], CHAN_CONFIRM_DEPTH - 1);

0 commit comments

Comments
 (0)