Skip to content

Commit 5f28580

Browse files
committed
Bump hashbrown dependency to 0.13
While this isn't expected to materially improve performance, it does get us ahash 0.8, which allows us to reduce fuzzing randomness, making our fuzzers much happier. Sadly, by default `ahash` no longer tries to autodetect a randomness source, so we cannot simply rely on `hashbrown` to do randomization for us, but rather have to also explicitly depend on `ahash`.
1 parent 2c3ebbb commit 5f28580

File tree

11 files changed

+108
-35
lines changed

11 files changed

+108
-35
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ jobs:
110110
run: |
111111
cd lightning
112112
RUSTFLAGS="--cfg=require_route_graph_test" cargo test
113-
RUSTFLAGS="--cfg=require_route_graph_test" cargo test --features hashbrown
113+
RUSTFLAGS="--cfg=require_route_graph_test" cargo test --features hashbrown,ahash
114114
cd ..
115115
- name: Run benchmarks on Rust ${{ matrix.toolchain }}
116116
run: |

bench/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ name = "bench"
99
harness = false
1010

1111
[features]
12-
hashbrown = ["lightning/hashbrown"]
12+
hashbrown = ["lightning/hashbrown", "lightning/ahash"]
1313

1414
[dependencies]
1515
lightning = { path = "../lightning", features = ["_test_utils", "criterion"] }

ci/check-cfg-flags.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ def check_feature(feature):
1313
pass
1414
elif feature == "no-std":
1515
pass
16+
elif feature == "ahash":
17+
pass
1618
elif feature == "hashbrown":
1719
pass
1820
elif feature == "backtrace":

fuzz/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ lightning = { path = "../lightning", features = ["regex", "hashbrown", "_test_ut
2222
lightning-rapid-gossip-sync = { path = "../lightning-rapid-gossip-sync" }
2323
bitcoin = { version = "0.30.2", features = ["secp-lowmemory"] }
2424
hex = { package = "hex-conservative", version = "0.1.1", default-features = false }
25-
hashbrown = "0.8"
25+
hashbrown = "0.13"
2626

2727
afl = { version = "0.12", optional = true }
2828
honggfuzz = { version = "0.5", optional = true, default-features = false }

lightning-invoice/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ bech32 = { version = "0.9.0", default-features = false }
2424
lightning = { version = "0.0.119", path = "../lightning", default-features = false }
2525
secp256k1 = { version = "0.27.0", default-features = false, features = ["recovery", "alloc"] }
2626
num-traits = { version = "0.2.8", default-features = false }
27-
hashbrown = { version = "0.8", optional = true }
27+
hashbrown = { version = "0.13", optional = true }
2828
serde = { version = "1.0.118", optional = true }
2929
bitcoin = { version = "0.30.2", default-features = false }
3030

lightning/Cargo.toml

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ unsafe_revoked_tx_signing = []
3131
# Override signing to not include randomness when generating signatures for test vectors.
3232
_test_vectors = []
3333

34-
no-std = ["hashbrown", "bitcoin/no-std", "core2/alloc", "libm"]
34+
no-std = ["hashbrown", "ahash", "bitcoin/no-std", "core2/alloc", "libm"]
3535
std = ["bitcoin/std"]
3636

3737
# Generates low-r bitcoin signatures, which saves 1 byte in 50% of the cases
@@ -42,14 +42,25 @@ default = ["std", "grind_signatures"]
4242
[dependencies]
4343
bitcoin = { version = "0.30.2", default-features = false, features = ["secp-recovery"] }
4444

45-
hashbrown = { version = "0.8", optional = true }
45+
hashbrown = { version = "0.13", optional = true }
46+
ahash = { version = "0.8", optional = true, default-features = false }
4647
hex = { package = "hex-conservative", version = "0.1.1", default-features = false }
4748
regex = { version = "1.5.6", optional = true }
4849
backtrace = { version = "0.3", optional = true }
4950

5051
core2 = { version = "0.3.0", optional = true, default-features = false }
5152
libm = { version = "0.2", optional = true, default-features = false }
5253

54+
# Because ahash no longer (kinda poorly) does it for us, (roughly) list out the targets that
55+
# getrandom supports and turn on ahash's `runtime-rng` feature for them.
56+
[target.'cfg(not(any(target_os = "unknown", target_os = "none")))'.dependencies]
57+
ahash = { version = "0.8", optional = true, default-features = false, features = ["runtime-rng"] }
58+
59+
# Not sure what target_os gets set to for sgx, so to be safe always enable runtime-rng for x86_64
60+
# platforms (assuming LDK isn't being used on embedded x86-64 running directly on metal).
61+
[target.'cfg(target_arch = "x86_64")'.dependencies]
62+
ahash = { version = "0.8", optional = true, default-features = false, features = ["runtime-rng"] }
63+
5364
[dev-dependencies]
5465
regex = "1.5.6"
5566

lightning/src/chain/onchaintx.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -686,7 +686,7 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
686686
if let Some(claim_id) = claim_id {
687687
if let Some(claim) = self.pending_claim_requests.remove(&claim_id) {
688688
for outpoint in claim.outpoints() {
689-
self.claimable_outpoints.remove(&outpoint);
689+
self.claimable_outpoints.remove(outpoint);
690690
}
691691
}
692692
} else {

lightning/src/lib.rs

Lines changed: 80 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -165,9 +165,19 @@ mod io_extras {
165165
mod prelude {
166166
#[cfg(feature = "hashbrown")]
167167
extern crate hashbrown;
168+
#[cfg(feature = "ahash")]
169+
extern crate ahash;
168170

169171
pub use alloc::{vec, vec::Vec, string::String, collections::VecDeque, boxed::Box};
170172

173+
pub use alloc::borrow::ToOwned;
174+
pub use alloc::string::ToString;
175+
176+
// For no-std builds, we need to use hashbrown, however, by default, it doesn't randomize the
177+
// hashing and is vulnerable to HashDoS attacks. Thus, when not fuzzing, we use its default
178+
// ahash hashing algorithm but randomize, opting to not randomize when fuzzing to avoid false
179+
// positive branch coverage.
180+
171181
#[cfg(not(feature = "hashbrown"))]
172182
mod std_hashtables {
173183
pub(crate) use std::collections::{HashMap, HashSet, hash_map};
@@ -181,35 +191,85 @@ mod prelude {
181191
pub(crate) use std_hashtables::*;
182192

183193
#[cfg(feature = "hashbrown")]
184-
mod hashbrown_tables {
185-
pub(crate) use hashbrown::{HashMap, HashSet, hash_map};
194+
pub(crate) use self::hashbrown::hash_map;
195+
196+
#[cfg(all(feature = "hashbrown", fuzzing))]
197+
mod nonrandomized_hashbrown {
198+
pub(crate) use hashbrown::{HashMap, HashSet};
186199

187200
pub(crate) type OccupiedHashMapEntry<'a, K, V> =
188-
hashbrown::hash_map::OccupiedEntry<'a, K, V, hash_map::DefaultHashBuilder>;
201+
hashbrown::hash_map::OccupiedEntry<'a, K, V, hashbrown::hash_map::DefaultHashBuilder>;
189202
pub(crate) type VacantHashMapEntry<'a, K, V> =
190-
hashbrown::hash_map::VacantEntry<'a, K, V, hash_map::DefaultHashBuilder>;
203+
hashbrown::hash_map::VacantEntry<'a, K, V, hashbrown::hash_map::DefaultHashBuilder>;
191204
}
192-
#[cfg(feature = "hashbrown")]
193-
pub(crate) use hashbrown_tables::*;
205+
#[cfg(all(feature = "hashbrown", fuzzing))]
206+
pub(crate) use nonrandomized_hashbrown::*;
194207

195-
pub(crate) fn new_hash_map<K: core::hash::Hash + Eq, V>() -> HashMap<K, V> { HashMap::new() }
196-
pub(crate) fn hash_map_with_capacity<K: core::hash::Hash + Eq, V>(cap: usize) -> HashMap<K, V> {
197-
HashMap::with_capacity(cap)
198-
}
199-
pub(crate) fn hash_map_from_iter<K: core::hash::Hash + Eq, V, I: IntoIterator<Item = (K, V)>>(iter: I) -> HashMap<K, V> {
200-
HashMap::from_iter(iter)
201-
}
202208

203-
pub(crate) fn new_hash_set<K: core::hash::Hash + Eq>() -> HashSet<K> { HashSet::new() }
204-
pub(crate) fn hash_set_with_capacity<K: core::hash::Hash + Eq>(cap: usize) -> HashSet<K> {
205-
HashSet::with_capacity(cap)
209+
#[cfg(all(feature = "hashbrown", not(fuzzing)))]
210+
mod randomized_hashtables {
211+
use super::*;
212+
use ahash::RandomState;
213+
214+
pub(crate) type HashMap<K, V> = hashbrown::HashMap<K, V, RandomState>;
215+
pub(crate) type HashSet<K> = hashbrown::HashSet<K, RandomState>;
216+
217+
pub(crate) type OccupiedHashMapEntry<'a, K, V> =
218+
hashbrown::hash_map::OccupiedEntry<'a, K, V, RandomState>;
219+
pub(crate) type VacantHashMapEntry<'a, K, V> =
220+
hashbrown::hash_map::VacantEntry<'a, K, V, RandomState>;
221+
222+
pub(crate) fn new_hash_map<K, V>() -> HashMap<K, V> {
223+
HashMap::with_hasher(RandomState::new())
224+
}
225+
pub(crate) fn hash_map_with_capacity<K, V>(cap: usize) -> HashMap<K, V> {
226+
HashMap::with_capacity_and_hasher(cap, RandomState::new())
227+
}
228+
pub(crate) fn hash_map_from_iter<K: core::hash::Hash + Eq, V, I: IntoIterator<Item=(K, V)>>(iter: I) -> HashMap<K, V> {
229+
let iter = iter.into_iter();
230+
let min_size = iter.size_hint().0;
231+
let mut res = HashMap::with_capacity_and_hasher(min_size, RandomState::new());
232+
res.extend(iter);
233+
res
234+
}
235+
236+
pub(crate) fn new_hash_set<K>() -> HashSet<K> {
237+
HashSet::with_hasher(RandomState::new())
238+
}
239+
pub(crate) fn hash_set_with_capacity<K>(cap: usize) -> HashSet<K> {
240+
HashSet::with_capacity_and_hasher(cap, RandomState::new())
241+
}
242+
pub(crate) fn hash_set_from_iter<K: core::hash::Hash + Eq, I: IntoIterator<Item=K>>(iter: I) -> HashSet<K> {
243+
let iter = iter.into_iter();
244+
let min_size = iter.size_hint().0;
245+
let mut res = HashSet::with_capacity_and_hasher(min_size, RandomState::new());
246+
res.extend(iter);
247+
res
248+
}
206249
}
207-
pub(crate) fn hash_set_from_iter<K: core::hash::Hash + Eq, I: IntoIterator<Item = K>>(iter: I) -> HashSet<K> {
208-
HashSet::from_iter(iter)
250+
251+
#[cfg(any(not(feature = "hashbrown"), fuzzing))]
252+
mod randomized_hashtables {
253+
use super::*;
254+
255+
pub(crate) fn new_hash_map<K, V>() -> HashMap<K, V> { HashMap::new() }
256+
pub(crate) fn hash_map_with_capacity<K, V>(cap: usize) -> HashMap<K, V> {
257+
HashMap::with_capacity(cap)
258+
}
259+
pub(crate) fn hash_map_from_iter<K: core::hash::Hash + Eq, V, I: IntoIterator<Item=(K, V)>>(iter: I) -> HashMap<K, V> {
260+
HashMap::from_iter(iter)
261+
}
262+
263+
pub(crate) fn new_hash_set<K>() -> HashSet<K> { HashSet::new() }
264+
pub(crate) fn hash_set_with_capacity<K>(cap: usize) -> HashSet<K> {
265+
HashSet::with_capacity(cap)
266+
}
267+
pub(crate) fn hash_set_from_iter<K: core::hash::Hash + Eq, I: IntoIterator<Item=K>>(iter: I) -> HashSet<K> {
268+
HashSet::from_iter(iter)
269+
}
209270
}
210271

211-
pub use alloc::borrow::ToOwned;
212-
pub use alloc::string::ToString;
272+
pub(crate) use randomized_hashtables::*;
213273
}
214274

215275
#[cfg(all(not(ldk_bench), feature = "backtrace", feature = "std", test))]

lightning/src/ln/channelmanager.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5923,7 +5923,7 @@ where
59235923
// TODO: Once we can rely on the counterparty_node_id from the
59245924
// monitor event, this and the outpoint_to_peer map should be removed.
59255925
let outpoint_to_peer = self.outpoint_to_peer.lock().unwrap();
5926-
match outpoint_to_peer.get(&funding_txo) {
5926+
match outpoint_to_peer.get(funding_txo) {
59275927
Some(cp_id) => cp_id.clone(),
59285928
None => return,
59295929
}
@@ -11031,7 +11031,7 @@ where
1103111031
downstream_counterparty_and_funding_outpoint:
1103211032
Some((blocked_node_id, blocked_channel_outpoint, blocking_action)), ..
1103311033
} = action {
11034-
if let Some(blocked_peer_state) = per_peer_state.get(&blocked_node_id) {
11034+
if let Some(blocked_peer_state) = per_peer_state.get(blocked_node_id) {
1103511035
log_trace!(logger,
1103611036
"Holding the next revoke_and_ack from {} until the preimage is durably persisted in the inbound edge's ChannelMonitor",
1103711037
blocked_channel_outpoint.to_channel_id());

lightning/src/routing/router.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2527,9 +2527,9 @@ where L::Target: Logger {
25272527
let mut aggregate_path_contribution_msat = path_value_msat;
25282528

25292529
for (idx, (hop, prev_hop_id)) in hop_iter.zip(prev_hop_iter).enumerate() {
2530-
let target = private_hop_key_cache.get(&prev_hop_id).unwrap();
2530+
let target = private_hop_key_cache.get(prev_hop_id).unwrap();
25312531

2532-
if let Some(first_channels) = first_hop_targets.get(&target) {
2532+
if let Some(first_channels) = first_hop_targets.get(target) {
25332533
if first_channels.iter().any(|d| d.outbound_scid_alias == Some(hop.short_channel_id)) {
25342534
log_trace!(logger, "Ignoring route hint with SCID {} (and any previous) due to it being a direct channel of ours.",
25352535
hop.short_channel_id);
@@ -2539,7 +2539,7 @@ where L::Target: Logger {
25392539

25402540
let candidate = network_channels
25412541
.get(&hop.short_channel_id)
2542-
.and_then(|channel| channel.as_directed_to(&target))
2542+
.and_then(|channel| channel.as_directed_to(target))
25432543
.map(|(info, _)| CandidateRouteHop::PublicHop(PublicHopCandidate {
25442544
info,
25452545
short_channel_id: hop.short_channel_id,
@@ -2580,7 +2580,7 @@ where L::Target: Logger {
25802580
.saturating_add(1);
25812581

25822582
// Searching for a direct channel between last checked hop and first_hop_targets
2583-
if let Some(first_channels) = first_hop_targets.get_mut(&target) {
2583+
if let Some(first_channels) = first_hop_targets.get_mut(target) {
25842584
sort_first_hop_channels(first_channels, &used_liquidities,
25852585
recommended_value_msat, our_node_pubkey);
25862586
for details in first_channels {

lightning/src/routing/scoring.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1330,7 +1330,7 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref> ScoreLookUp for Probabilistic
13301330
_ => return 0,
13311331
};
13321332
let source = candidate.source();
1333-
if let Some(penalty) = score_params.manual_node_penalties.get(&target) {
1333+
if let Some(penalty) = score_params.manual_node_penalties.get(target) {
13341334
return *penalty;
13351335
}
13361336

@@ -1360,7 +1360,7 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref> ScoreLookUp for Probabilistic
13601360
let amount_msat = usage.amount_msat.saturating_add(usage.inflight_htlc_msat);
13611361
let capacity_msat = usage.effective_capacity.as_msat();
13621362
self.channel_liquidities
1363-
.get(&scid)
1363+
.get(scid)
13641364
.unwrap_or(&ChannelLiquidity::new(Duration::ZERO))
13651365
.as_directed(&source, &target, capacity_msat)
13661366
.penalty_msat(amount_msat, score_params)

0 commit comments

Comments
 (0)