Skip to content

Commit ee9f4f2

Browse files
committed
Add a new IndexedMap type and use it in network graph storage
Our network graph has to be iterable in a deterministic order and with the ability to iterate over a specific range. Thus, historically, we've used a `BTreeMap` to do the iteration. This is fine, except our map needs to also provide high performance lookups in order to make route-finding fast. Sadly, `BTreeMap`s are quite slow due to the branching penalty. Here we replace the `BTreeMap`s in the scorer with a dummy wrapper. In the next commit the internals thereof will be replaced with a `HashMap`-based implementation.
1 parent a3f7b79 commit ee9f4f2

File tree

4 files changed

+194
-34
lines changed

4 files changed

+194
-34
lines changed

lightning/src/routing/gossip.rs

+28-28
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,11 @@ use crate::util::logger::{Logger, Level};
3232
use crate::util::events::{MessageSendEvent, MessageSendEventsProvider};
3333
use crate::util::scid_utils::{block_from_scid, scid_from_parts, MAX_SCID_BLOCK};
3434
use crate::util::string::PrintableString;
35+
use crate::util::indexed_map::{IndexedMap, Entry as IndexedMapEntry};
3536

3637
use crate::io;
3738
use crate::io_extras::{copy, sink};
3839
use crate::prelude::*;
39-
use alloc::collections::{BTreeMap, btree_map::Entry as BtreeEntry};
4040
use core::{cmp, fmt};
4141
use crate::sync::{RwLock, RwLockReadGuard};
4242
#[cfg(feature = "std")]
@@ -133,8 +133,8 @@ pub struct NetworkGraph<L: Deref> where L::Target: Logger {
133133
genesis_hash: BlockHash,
134134
logger: L,
135135
// Lock order: channels -> nodes
136-
channels: RwLock<BTreeMap<u64, ChannelInfo>>,
137-
nodes: RwLock<BTreeMap<NodeId, NodeInfo>>,
136+
channels: RwLock<IndexedMap<u64, ChannelInfo>>,
137+
nodes: RwLock<IndexedMap<NodeId, NodeInfo>>,
138138
// Lock order: removed_channels -> removed_nodes
139139
//
140140
// NOTE: In the following `removed_*` maps, we use seconds since UNIX epoch to track time instead
@@ -158,8 +158,8 @@ pub struct NetworkGraph<L: Deref> where L::Target: Logger {
158158

159159
/// A read-only view of [`NetworkGraph`].
160160
pub struct ReadOnlyNetworkGraph<'a> {
161-
channels: RwLockReadGuard<'a, BTreeMap<u64, ChannelInfo>>,
162-
nodes: RwLockReadGuard<'a, BTreeMap<NodeId, NodeInfo>>,
161+
channels: RwLockReadGuard<'a, IndexedMap<u64, ChannelInfo>>,
162+
nodes: RwLockReadGuard<'a, IndexedMap<NodeId, NodeInfo>>,
163163
}
164164

165165
/// Update to the [`NetworkGraph`] based on payment failure information conveyed via the Onion
@@ -1131,13 +1131,13 @@ impl<L: Deref> Writeable for NetworkGraph<L> where L::Target: Logger {
11311131
self.genesis_hash.write(writer)?;
11321132
let channels = self.channels.read().unwrap();
11331133
(channels.len() as u64).write(writer)?;
1134-
for (ref chan_id, ref chan_info) in channels.iter() {
1134+
for (ref chan_id, ref chan_info) in channels.unordered_iter() {
11351135
(*chan_id).write(writer)?;
11361136
chan_info.write(writer)?;
11371137
}
11381138
let nodes = self.nodes.read().unwrap();
11391139
(nodes.len() as u64).write(writer)?;
1140-
for (ref node_id, ref node_info) in nodes.iter() {
1140+
for (ref node_id, ref node_info) in nodes.unordered_iter() {
11411141
node_id.write(writer)?;
11421142
node_info.write(writer)?;
11431143
}
@@ -1156,14 +1156,14 @@ impl<L: Deref> ReadableArgs<L> for NetworkGraph<L> where L::Target: Logger {
11561156

11571157
let genesis_hash: BlockHash = Readable::read(reader)?;
11581158
let channels_count: u64 = Readable::read(reader)?;
1159-
let mut channels: BTreeMap<u64, ChannelInfo> = BTreeMap::new();
1159+
let mut channels = IndexedMap::new();
11601160
for _ in 0..channels_count {
11611161
let chan_id: u64 = Readable::read(reader)?;
11621162
let chan_info = Readable::read(reader)?;
11631163
channels.insert(chan_id, chan_info);
11641164
}
11651165
let nodes_count: u64 = Readable::read(reader)?;
1166-
let mut nodes: BTreeMap<NodeId, NodeInfo> = BTreeMap::new();
1166+
let mut nodes = IndexedMap::new();
11671167
for _ in 0..nodes_count {
11681168
let node_id = Readable::read(reader)?;
11691169
let node_info = Readable::read(reader)?;
@@ -1191,11 +1191,11 @@ impl<L: Deref> ReadableArgs<L> for NetworkGraph<L> where L::Target: Logger {
11911191
impl<L: Deref> fmt::Display for NetworkGraph<L> where L::Target: Logger {
11921192
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
11931193
writeln!(f, "Network map\n[Channels]")?;
1194-
for (key, val) in self.channels.read().unwrap().iter() {
1194+
for (key, val) in self.channels.read().unwrap().unordered_iter() {
11951195
writeln!(f, " {}: {}", key, val)?;
11961196
}
11971197
writeln!(f, "[Nodes]")?;
1198-
for (&node_id, val) in self.nodes.read().unwrap().iter() {
1198+
for (&node_id, val) in self.nodes.read().unwrap().unordered_iter() {
11991199
writeln!(f, " {}: {}", log_bytes!(node_id.as_slice()), val)?;
12001200
}
12011201
Ok(())
@@ -1218,8 +1218,8 @@ impl<L: Deref> NetworkGraph<L> where L::Target: Logger {
12181218
secp_ctx: Secp256k1::verification_only(),
12191219
genesis_hash,
12201220
logger,
1221-
channels: RwLock::new(BTreeMap::new()),
1222-
nodes: RwLock::new(BTreeMap::new()),
1221+
channels: RwLock::new(IndexedMap::new()),
1222+
nodes: RwLock::new(IndexedMap::new()),
12231223
last_rapid_gossip_sync_timestamp: Mutex::new(None),
12241224
removed_channels: Mutex::new(HashMap::new()),
12251225
removed_nodes: Mutex::new(HashMap::new()),
@@ -1252,7 +1252,7 @@ impl<L: Deref> NetworkGraph<L> where L::Target: Logger {
12521252
/// purposes.
12531253
#[cfg(test)]
12541254
pub fn clear_nodes_announcement_info(&self) {
1255-
for node in self.nodes.write().unwrap().iter_mut() {
1255+
for node in self.nodes.write().unwrap().unordered_iter_mut() {
12561256
node.1.announcement_info = None;
12571257
}
12581258
}
@@ -1382,7 +1382,7 @@ impl<L: Deref> NetworkGraph<L> where L::Target: Logger {
13821382
let node_id_b = channel_info.node_two.clone();
13831383

13841384
match channels.entry(short_channel_id) {
1385-
BtreeEntry::Occupied(mut entry) => {
1385+
IndexedMapEntry::Occupied(mut entry) => {
13861386
//TODO: because asking the blockchain if short_channel_id is valid is only optional
13871387
//in the blockchain API, we need to handle it smartly here, though it's unclear
13881388
//exactly how...
@@ -1401,17 +1401,17 @@ impl<L: Deref> NetworkGraph<L> where L::Target: Logger {
14011401
return Err(LightningError{err: "Already have knowledge of channel".to_owned(), action: ErrorAction::IgnoreDuplicateGossip});
14021402
}
14031403
},
1404-
BtreeEntry::Vacant(entry) => {
1404+
IndexedMapEntry::Vacant(entry) => {
14051405
entry.insert(channel_info);
14061406
}
14071407
};
14081408

14091409
for current_node_id in [node_id_a, node_id_b].iter() {
14101410
match nodes.entry(current_node_id.clone()) {
1411-
BtreeEntry::Occupied(node_entry) => {
1411+
IndexedMapEntry::Occupied(node_entry) => {
14121412
node_entry.into_mut().channels.push(short_channel_id);
14131413
},
1414-
BtreeEntry::Vacant(node_entry) => {
1414+
IndexedMapEntry::Vacant(node_entry) => {
14151415
node_entry.insert(NodeInfo {
14161416
channels: vec!(short_channel_id),
14171417
announcement_info: None,
@@ -1585,7 +1585,7 @@ impl<L: Deref> NetworkGraph<L> where L::Target: Logger {
15851585
for scid in node.channels.iter() {
15861586
if let Some(chan_info) = channels.remove(scid) {
15871587
let other_node_id = if node_id == chan_info.node_one { chan_info.node_two } else { chan_info.node_one };
1588-
if let BtreeEntry::Occupied(mut other_node_entry) = nodes.entry(other_node_id) {
1588+
if let IndexedMapEntry::Occupied(mut other_node_entry) = nodes.entry(other_node_id) {
15891589
other_node_entry.get_mut().channels.retain(|chan_id| {
15901590
*scid != *chan_id
15911591
});
@@ -1644,7 +1644,7 @@ impl<L: Deref> NetworkGraph<L> where L::Target: Logger {
16441644
// Sadly BTreeMap::retain was only stabilized in 1.53 so we can't switch to it for some
16451645
// time.
16461646
let mut scids_to_remove = Vec::new();
1647-
for (scid, info) in channels.iter_mut() {
1647+
for (scid, info) in channels.unordered_iter_mut() {
16481648
if info.one_to_two.is_some() && info.one_to_two.as_ref().unwrap().last_update < min_time_unix {
16491649
info.one_to_two = None;
16501650
}
@@ -1813,10 +1813,10 @@ impl<L: Deref> NetworkGraph<L> where L::Target: Logger {
18131813
Ok(())
18141814
}
18151815

1816-
fn remove_channel_in_nodes(nodes: &mut BTreeMap<NodeId, NodeInfo>, chan: &ChannelInfo, short_channel_id: u64) {
1816+
fn remove_channel_in_nodes(nodes: &mut IndexedMap<NodeId, NodeInfo>, chan: &ChannelInfo, short_channel_id: u64) {
18171817
macro_rules! remove_from_node {
18181818
($node_id: expr) => {
1819-
if let BtreeEntry::Occupied(mut entry) = nodes.entry($node_id) {
1819+
if let IndexedMapEntry::Occupied(mut entry) = nodes.entry($node_id) {
18201820
entry.get_mut().channels.retain(|chan_id| {
18211821
short_channel_id != *chan_id
18221822
});
@@ -1837,8 +1837,8 @@ impl<L: Deref> NetworkGraph<L> where L::Target: Logger {
18371837
impl ReadOnlyNetworkGraph<'_> {
18381838
/// Returns all known valid channels' short ids along with announced channel info.
18391839
///
1840-
/// (C-not exported) because we have no mapping for `BTreeMap`s
1841-
pub fn channels(&self) -> &BTreeMap<u64, ChannelInfo> {
1840+
/// (C-not exported) because we don't want to return lifetime'd references
1841+
pub fn channels(&self) -> &IndexedMap<u64, ChannelInfo> {
18421842
&*self.channels
18431843
}
18441844

@@ -1850,13 +1850,13 @@ impl ReadOnlyNetworkGraph<'_> {
18501850
#[cfg(c_bindings)] // Non-bindings users should use `channels`
18511851
/// Returns the list of channels in the graph
18521852
pub fn list_channels(&self) -> Vec<u64> {
1853-
self.channels.keys().map(|c| *c).collect()
1853+
self.channels.unordered_keys().map(|c| *c).collect()
18541854
}
18551855

18561856
/// Returns all known nodes' public keys along with announced node info.
18571857
///
1858-
/// (C-not exported) because we have no mapping for `BTreeMap`s
1859-
pub fn nodes(&self) -> &BTreeMap<NodeId, NodeInfo> {
1858+
/// (C-not exported) because we don't want to return lifetime'd references
1859+
pub fn nodes(&self) -> &IndexedMap<NodeId, NodeInfo> {
18601860
&*self.nodes
18611861
}
18621862

@@ -1868,7 +1868,7 @@ impl ReadOnlyNetworkGraph<'_> {
18681868
#[cfg(c_bindings)] // Non-bindings users should use `nodes`
18691869
/// Returns the list of nodes in the graph
18701870
pub fn list_nodes(&self) -> Vec<NodeId> {
1871-
self.nodes.keys().map(|n| *n).collect()
1871+
self.nodes.unordered_keys().map(|n| *n).collect()
18721872
}
18731873

18741874
/// Get network addresses by node id.

lightning/src/routing/router.rs

+6-6
Original file line numberDiff line numberDiff line change
@@ -5507,9 +5507,9 @@ mod tests {
55075507
'load_endpoints: for _ in 0..10 {
55085508
loop {
55095509
seed = seed.overflowing_mul(0xdeadbeef).0;
5510-
let src = &PublicKey::from_slice(nodes.keys().skip(seed % nodes.len()).next().unwrap().as_slice()).unwrap();
5510+
let src = &PublicKey::from_slice(nodes.unordered_keys().skip(seed % nodes.len()).next().unwrap().as_slice()).unwrap();
55115511
seed = seed.overflowing_mul(0xdeadbeef).0;
5512-
let dst = PublicKey::from_slice(nodes.keys().skip(seed % nodes.len()).next().unwrap().as_slice()).unwrap();
5512+
let dst = PublicKey::from_slice(nodes.unordered_keys().skip(seed % nodes.len()).next().unwrap().as_slice()).unwrap();
55135513
let payment_params = PaymentParameters::from_node_id(dst);
55145514
let amt = seed as u64 % 200_000_000;
55155515
let params = ProbabilisticScoringParameters::default();
@@ -5545,9 +5545,9 @@ mod tests {
55455545
'load_endpoints: for _ in 0..10 {
55465546
loop {
55475547
seed = seed.overflowing_mul(0xdeadbeef).0;
5548-
let src = &PublicKey::from_slice(nodes.keys().skip(seed % nodes.len()).next().unwrap().as_slice()).unwrap();
5548+
let src = &PublicKey::from_slice(nodes.unordered_keys().skip(seed % nodes.len()).next().unwrap().as_slice()).unwrap();
55495549
seed = seed.overflowing_mul(0xdeadbeef).0;
5550-
let dst = PublicKey::from_slice(nodes.keys().skip(seed % nodes.len()).next().unwrap().as_slice()).unwrap();
5550+
let dst = PublicKey::from_slice(nodes.unordered_keys().skip(seed % nodes.len()).next().unwrap().as_slice()).unwrap();
55515551
let payment_params = PaymentParameters::from_node_id(dst).with_features(channelmanager::provided_invoice_features(&config));
55525552
let amt = seed as u64 % 200_000_000;
55535553
let params = ProbabilisticScoringParameters::default();
@@ -5745,9 +5745,9 @@ mod benches {
57455745
'load_endpoints: for _ in 0..150 {
57465746
loop {
57475747
seed *= 0xdeadbeef;
5748-
let src = PublicKey::from_slice(nodes.keys().skip(seed % nodes.len()).next().unwrap().as_slice()).unwrap();
5748+
let src = PublicKey::from_slice(nodes.unordered_keys().skip(seed % nodes.len()).next().unwrap().as_slice()).unwrap();
57495749
seed *= 0xdeadbeef;
5750-
let dst = PublicKey::from_slice(nodes.keys().skip(seed % nodes.len()).next().unwrap().as_slice()).unwrap();
5750+
let dst = PublicKey::from_slice(nodes.unordered_keys().skip(seed % nodes.len()).next().unwrap().as_slice()).unwrap();
57515751
let params = PaymentParameters::from_node_id(dst).with_features(features.clone());
57525752
let first_hop = first_hop(src);
57535753
let amt = seed as u64 % 1_000_000;

lightning/src/util/indexed_map.rs

+158
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
//! This module has a map which can be iterated in a deterministic order. See the [`IndexedMap`].
2+
3+
use alloc::collections::{BTreeMap, btree_map};
4+
use core::cmp::Ord;
5+
use core::ops::RangeBounds;
6+
7+
/// A map which can be iterated in a deterministic order.
8+
///
9+
/// This would traditionally be accomplished by simply using a [`BTreeMap`], however B-Trees
10+
/// generally have very slow lookups. Because we use a nodes+channels map while finding routes
11+
/// across the network graph, our network graph backing map must be as performant as possible.
12+
/// However, because peers expect to sync the network graph from us (and we need to support that
13+
/// without holding a lock on the graph for the duration of the sync or dumping the entire graph
14+
/// into our outbound message queue), we need an iterable map with a consistent iteration order we
15+
/// can jump to a starting point on.
16+
///
17+
/// Thus, we have a custom data structure here - its API mimics that of Rust's [`BTreeMap`], but is
18+
/// actually backed by a [`HashMap`], with some additional tracking to ensure we can iterate over
19+
/// keys in the order defined by [`Ord`].
20+
///
21+
/// [`BTreeMap`]: alloc::collections::BTreeMap
22+
#[derive(Clone, PartialEq, Eq)]
23+
pub struct IndexedMap<K: Ord, V> {
24+
map: BTreeMap<K, V>,
25+
}
26+
27+
impl<K: Ord, V> IndexedMap<K, V> {
28+
/// Constructs a new, empty map
29+
pub fn new() -> Self {
30+
Self {
31+
map: BTreeMap::new(),
32+
}
33+
}
34+
35+
#[inline(always)]
36+
/// Fetches the element with the given `key`, if one exists.
37+
pub fn get(&self, key: &K) -> Option<&V> {
38+
self.map.get(key)
39+
}
40+
41+
/// Fetches a mutable reference to the element with the given `key`, if one exists.
42+
pub fn get_mut(&mut self, key: &K) -> Option<&mut V> {
43+
self.map.get_mut(key)
44+
}
45+
46+
#[inline]
47+
/// Returns true if an element with the given `key` exists in the map.
48+
pub fn contains_key(&self, key: &K) -> bool {
49+
self.map.contains_key(key)
50+
}
51+
52+
/// Removes the element with the given `key`, returning it, if one exists.
53+
pub fn remove(&mut self, key: &K) -> Option<V> {
54+
self.map.remove(key)
55+
}
56+
57+
/// Inserts the given `key`/`value` pair into the map, returning the element that was
58+
/// previously stored at the given `key`, if one exists.
59+
pub fn insert(&mut self, key: K, value: V) -> Option<V> {
60+
self.map.insert(key, value)
61+
}
62+
63+
/// Returns an [`Entry`] for the given `key` in the map, allowing access to the value.
64+
pub fn entry(&mut self, key: K) -> Entry<'_, K, V> {
65+
match self.map.entry(key) {
66+
btree_map::Entry::Vacant(entry) => {
67+
Entry::Vacant(VacantEntry {
68+
underlying_entry: entry
69+
})
70+
},
71+
btree_map::Entry::Occupied(entry) => {
72+
Entry::Occupied(OccupiedEntry {
73+
underlying_entry: entry
74+
})
75+
}
76+
}
77+
}
78+
79+
/// Returns an iterator which iterates over the keys in the map, in a random order.
80+
pub fn unordered_keys(&self) -> impl Iterator<Item = &K> {
81+
self.map.keys()
82+
}
83+
84+
/// Returns an iterator which iterates over the `key`/`value` pairs in a random order.
85+
pub fn unordered_iter(&self) -> impl Iterator<Item = (&K, &V)> {
86+
self.map.iter()
87+
}
88+
89+
/// Returns an iterator which iterates over the `key`s and mutable references to `value`s in a
90+
/// random order.
91+
pub fn unordered_iter_mut(&mut self) -> impl Iterator<Item = (&K, &mut V)> {
92+
self.map.iter_mut()
93+
}
94+
95+
/// Returns an iterator which iterates over the `key`/`value` pairs in a given range.
96+
pub fn range<R: RangeBounds<K>>(&self, range: R) -> btree_map::Range<K, V> {
97+
self.map.range(range)
98+
}
99+
100+
/// Returns the number of `key`/`value` pairs in the map
101+
pub fn len(&self) -> usize {
102+
self.map.len()
103+
}
104+
105+
/// Returns true if there are no elements in the map
106+
pub fn is_empty(&self) -> bool {
107+
self.map.is_empty()
108+
}
109+
}
110+
111+
/// An [`Entry`] for a key which currently has no value
112+
pub struct VacantEntry<'a, K: Ord, V> {
113+
underlying_entry: btree_map::VacantEntry<'a, K, V>,
114+
}
115+
116+
/// An [`Entry`] for an existing key-value pair
117+
pub struct OccupiedEntry<'a, K: Ord, V> {
118+
underlying_entry: btree_map::OccupiedEntry<'a, K, V>,
119+
}
120+
121+
/// A mutable reference to a position in the map. This can be used to reference, add, or update the
122+
/// value at a fixed key.
123+
pub enum Entry<'a, K: Ord, V> {
124+
/// A mutable reference to a position within the map where there is no value.
125+
Vacant(VacantEntry<'a, K, V>),
126+
/// A mutable reference to a position within the map where there is currently a value.
127+
Occupied(OccupiedEntry<'a, K, V>),
128+
}
129+
130+
impl<'a, K: Ord, V> VacantEntry<'a, K, V> {
131+
/// Insert a value into the position described by this entry.
132+
pub fn insert(self, value: V) -> &'a mut V {
133+
self.underlying_entry.insert(value)
134+
}
135+
}
136+
137+
impl<'a, K: Ord, V> OccupiedEntry<'a, K, V> {
138+
/// Remove the value at the position described by this entry.
139+
pub fn remove_entry(self) -> (K, V) {
140+
self.underlying_entry.remove_entry()
141+
}
142+
143+
/// Get a reference to the value at the position described by this entry.
144+
pub fn get(&self) -> &V {
145+
self.underlying_entry.get()
146+
}
147+
148+
/// Get a mutable reference to the value at the position described by this entry.
149+
pub fn get_mut(&mut self) -> &mut V {
150+
self.underlying_entry.get_mut()
151+
}
152+
153+
/// Consume this entry, returning a mutable reference to the value at the position described by
154+
/// this entry.
155+
pub fn into_mut(self) -> &'a mut V {
156+
self.underlying_entry.into_mut()
157+
}
158+
}

0 commit comments

Comments
 (0)