Skip to content

Commit 8ee1165

Browse files
committed
Add a new NodeCounters utility to track counters when routing
In the next commit we'll stop using `NodeId`s to look up nodes when routing, instead using the new per-node counters. Here we take the first step, adding a local struct which tracks temporary counters for route hints/source/destination nodes. Because we must ensure we have a 1-to-1 mapping from node ids to `node_counter`s, even across first-hop and last-hop hints, we have to be careful to check the network graph first, then a new `private_node_id_to_node_counter` map to ensure we only ever end up with one counter per node id.
1 parent a17a159 commit 8ee1165

File tree

2 files changed

+104
-2
lines changed

2 files changed

+104
-2
lines changed

lightning/src/routing/gossip.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ pub struct NetworkGraph<L: Deref> where L::Target: Logger {
204204
pub struct ReadOnlyNetworkGraph<'a> {
205205
channels: RwLockReadGuard<'a, IndexedMap<u64, ChannelInfo>>,
206206
nodes: RwLockReadGuard<'a, IndexedMap<NodeId, NodeInfo>>,
207+
max_node_counter: u32,
207208
}
208209

209210
/// Update to the [`NetworkGraph`] based on payment failure information conveyed via the Onion
@@ -1653,6 +1654,7 @@ impl<L: Deref> NetworkGraph<L> where L::Target: Logger {
16531654
ReadOnlyNetworkGraph {
16541655
channels,
16551656
nodes,
1657+
max_node_counter: (self.next_node_counter.load(Ordering::Acquire) as u32).saturating_sub(1),
16561658
}
16571659
}
16581660

@@ -2348,6 +2350,11 @@ impl ReadOnlyNetworkGraph<'_> {
23482350
self.nodes.get(&NodeId::from_pubkey(&pubkey))
23492351
.and_then(|node| node.announcement_info.as_ref().map(|ann| ann.addresses().to_vec()))
23502352
}
2353+
2354+
/// Gets the maximum possible node_counter for a node in this graph
2355+
pub(crate) fn max_node_counter(&self) -> u32 {
2356+
self.max_node_counter
2357+
}
23512358
}
23522359

23532360
#[cfg(test)]

lightning/src/routing/router.rs

Lines changed: 97 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1499,6 +1499,83 @@ enum CandidateHopId {
14991499
Blinded(usize),
15001500
}
15011501

1502+
/// To avoid doing [`PublicKey`] -> [`PathBuildingHop`] hashtable lookups, we assign each
1503+
/// [`PublicKey`]/node a `usize` index and simply keep a `Vec` of values.
1504+
///
1505+
/// While this is easy for gossip-originating nodes (the [`DirectedChannelInfo`] exposes "counters"
1506+
/// for us for this purpose) we have to have our own indexes for nodes originating from invoice
1507+
/// hints, local channels, or blinded path fake nodes.
1508+
///
1509+
/// This wrapper handles all this for us, allowing look-up of counters from the various contexts.
1510+
///
1511+
/// It is first built by passing all [`NodeId`]s that we'll ever care about (which are not in our
1512+
/// [`NetworkGraph`], e.g. those from first- and last-hop hints and blinded path introduction
1513+
/// points) either though [`NodeCountersBuilder::node_counter_from_pubkey`] or
1514+
/// [`NodeCountersBuilder::node_counter_from_id`], then calling [`NodeCountersBuilder::build`] and
1515+
/// using the resulting [`NodeCounters`] to look up any counters.
1516+
///
1517+
/// [`NodeCounters::private_node_counter_from_pubkey`], specifically, will return `Some` iff
1518+
/// [`NodeCountersBuilder::node_counter_from_pubkey`] was called on the same key (not
1519+
/// [`NodeCountersBuilder::node_counter_from_id`]). It will also return a cached copy of the
1520+
/// [`PublicKey`] -> [`NodeId`] conversion.
1521+
struct NodeCounters<'a> {
1522+
network_graph: &'a ReadOnlyNetworkGraph<'a>,
1523+
private_node_id_to_node_counter: HashMap<NodeId, u32>,
1524+
private_hop_key_cache: HashMap<PublicKey, (NodeId, u32)>,
1525+
}
1526+
1527+
struct NodeCountersBuilder<'a>(NodeCounters<'a>);
1528+
1529+
impl<'a> NodeCountersBuilder<'a> {
1530+
fn new(network_graph: &'a ReadOnlyNetworkGraph) -> Self {
1531+
Self(NodeCounters {
1532+
network_graph,
1533+
private_node_id_to_node_counter: new_hash_map(),
1534+
private_hop_key_cache: new_hash_map(),
1535+
})
1536+
}
1537+
1538+
fn node_counter_from_pubkey(&mut self, pubkey: PublicKey) -> u32 {
1539+
let id = NodeId::from_pubkey(&pubkey);
1540+
let counter = self.node_counter_from_id(id);
1541+
self.0.private_hop_key_cache.insert(pubkey, (id, counter));
1542+
counter
1543+
}
1544+
1545+
fn node_counter_from_id(&mut self, node_id: NodeId) -> u32 {
1546+
// For any node_id, we first have to check if its in the existing network graph, and then
1547+
// ensure that we always look up in our internal map first.
1548+
self.0.network_graph.nodes().get(&node_id)
1549+
.map(|node| node.node_counter)
1550+
.unwrap_or_else(|| {
1551+
let next_node_counter = self.0.network_graph.max_node_counter() + 1 +
1552+
self.0.private_node_id_to_node_counter.len() as u32;
1553+
*self.0.private_node_id_to_node_counter.entry(node_id).or_insert(next_node_counter)
1554+
})
1555+
}
1556+
1557+
fn build(self) -> NodeCounters<'a> { self.0 }
1558+
}
1559+
1560+
impl<'a> NodeCounters<'a> {
1561+
fn max_counter(&self) -> u32 {
1562+
self.network_graph.max_node_counter() +
1563+
self.private_node_id_to_node_counter.len() as u32
1564+
}
1565+
1566+
fn private_node_counter_from_pubkey(&self, pubkey: &PublicKey) -> Option<&(NodeId, u32)> {
1567+
self.private_hop_key_cache.get(pubkey)
1568+
}
1569+
1570+
fn node_counter_from_id(&self, node_id: &NodeId) -> Option<(&NodeId, u32)> {
1571+
self.private_node_id_to_node_counter.get_key_value(node_id).map(|(a, b)| (a, *b))
1572+
.or_else(|| {
1573+
self.network_graph.nodes().get_key_value(node_id)
1574+
.map(|(node_id, node)| (node_id, node.node_counter))
1575+
})
1576+
}
1577+
}
1578+
15021579
#[inline]
15031580
fn max_htlc_from_capacity(capacity: EffectiveCapacity, max_channel_saturation_power_of_half: u8) -> u64 {
15041581
let saturation_shift: u32 = max_channel_saturation_power_of_half as u32;
@@ -2051,6 +2128,17 @@ where L::Target: Logger {
20512128
}
20522129
}
20532130

2131+
let mut node_counters = NodeCountersBuilder::new(&network_graph);
2132+
2133+
let payer_node_counter = node_counters.node_counter_from_pubkey(*our_node_pubkey);
2134+
let payee_node_counter = node_counters.node_counter_from_pubkey(maybe_dummy_payee_pk);
2135+
2136+
for route in payment_params.payee.unblinded_route_hints().iter() {
2137+
for hop in route.0.iter() {
2138+
node_counters.node_counter_from_pubkey(hop.src_node_id);
2139+
}
2140+
}
2141+
20542142
// Step (1).
20552143
// Prepare the data we'll use for payee-to-payer search by
20562144
// inserting first hops suggested by the caller as targets.
@@ -2065,9 +2153,14 @@ where L::Target: Logger {
20652153
if chan.counterparty.node_id == *our_node_pubkey {
20662154
return Err(LightningError{err: "First hop cannot have our_node_pubkey as a destination.".to_owned(), action: ErrorAction::IgnoreError});
20672155
}
2156+
let counterparty_id = NodeId::from_pubkey(&chan.counterparty.node_id);
20682157
first_hop_targets
2069-
.entry(NodeId::from_pubkey(&chan.counterparty.node_id))
2070-
.or_insert(Vec::new())
2158+
.entry(counterparty_id)
2159+
.or_insert_with(|| {
2160+
// Make sure there's a counter assigned for the counterparty
2161+
node_counters.node_counter_from_id(counterparty_id);
2162+
Vec::new()
2163+
})
20712164
.push(chan);
20722165
}
20732166
if first_hop_targets.is_empty() {
@@ -2089,6 +2182,8 @@ where L::Target: Logger {
20892182
}
20902183
}
20912184

2185+
let node_counters = node_counters.build();
2186+
20922187
// The main heap containing all candidate next-hops sorted by their score (max(fee,
20932188
// htlc_minimum)). Ideally this would be a heap which allowed cheap score reduction instead of
20942189
// adding duplicate entries when we find a better path to a given node.

0 commit comments

Comments
 (0)