Skip to content

Commit dedc830

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 efbaa41 commit dedc830

File tree

11 files changed

+109
-36
lines changed

11 files changed

+109
-36
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.121", 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
@@ -167,9 +167,19 @@ mod io_extras {
167167
mod prelude {
168168
#[cfg(feature = "hashbrown")]
169169
extern crate hashbrown;
170+
#[cfg(feature = "ahash")]
171+
extern crate ahash;
170172

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

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

185195
#[cfg(feature = "hashbrown")]
186-
mod hashbrown_tables {
187-
pub(crate) use hashbrown::{HashMap, HashSet, hash_map};
196+
pub(crate) use self::hashbrown::hash_map;
197+
198+
#[cfg(all(feature = "hashbrown", fuzzing))]
199+
mod nonrandomized_hashbrown {
200+
pub(crate) use hashbrown::{HashMap, HashSet};
188201

189202
pub(crate) type OccupiedHashMapEntry<'a, K, V> =
190-
hashbrown::hash_map::OccupiedEntry<'a, K, V, hash_map::DefaultHashBuilder>;
203+
hashbrown::hash_map::OccupiedEntry<'a, K, V, hashbrown::hash_map::DefaultHashBuilder>;
191204
pub(crate) type VacantHashMapEntry<'a, K, V> =
192-
hashbrown::hash_map::VacantEntry<'a, K, V, hash_map::DefaultHashBuilder>;
205+
hashbrown::hash_map::VacantEntry<'a, K, V, hashbrown::hash_map::DefaultHashBuilder>;
193206
}
194-
#[cfg(feature = "hashbrown")]
195-
pub(crate) use hashbrown_tables::*;
207+
#[cfg(all(feature = "hashbrown", fuzzing))]
208+
pub(crate) use nonrandomized_hashbrown::*;
196209

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

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

213-
pub use alloc::borrow::ToOwned;
214-
pub use alloc::string::ToString;
274+
pub(crate) use randomized_hashtables::*;
215275
}
216276

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

lightning/src/ln/channelmanager.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5951,7 +5951,7 @@ where
59515951
// TODO: Once we can rely on the counterparty_node_id from the
59525952
// monitor event, this and the outpoint_to_peer map should be removed.
59535953
let outpoint_to_peer = self.outpoint_to_peer.lock().unwrap();
5954-
match outpoint_to_peer.get(&funding_txo) {
5954+
match outpoint_to_peer.get(funding_txo) {
59555955
Some(cp_id) => cp_id.clone(),
59565956
None => return,
59575957
}
@@ -5964,7 +5964,7 @@ where
59645964
peer_state_lock = peer_state_mutex_opt.unwrap().lock().unwrap();
59655965
let peer_state = &mut *peer_state_lock;
59665966
let channel =
5967-
if let Some(ChannelPhase::Funded(chan)) = peer_state.channel_by_id.get_mut(&channel_id) {
5967+
if let Some(ChannelPhase::Funded(chan)) = peer_state.channel_by_id.get_mut(channel_id) {
59685968
chan
59695969
} else {
59705970
let update_actions = peer_state.monitor_update_blocked_actions
@@ -11085,7 +11085,7 @@ where
1108511085
downstream_counterparty_and_funding_outpoint:
1108611086
Some((blocked_node_id, _blocked_channel_outpoint, blocked_channel_id, blocking_action)), ..
1108711087
} = action {
11088-
if let Some(blocked_peer_state) = per_peer_state.get(&blocked_node_id) {
11088+
if let Some(blocked_peer_state) = per_peer_state.get(blocked_node_id) {
1108911089
log_trace!(logger,
1109011090
"Holding the next revoke_and_ack from {} until the preimage is durably persisted in the inbound edge's ChannelMonitor",
1109111091
blocked_channel_id);

lightning/src/routing/router.rs

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

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

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

25792579
let candidate = network_channels
25802580
.get(&hop.short_channel_id)
2581-
.and_then(|channel| channel.as_directed_to(&target))
2581+
.and_then(|channel| channel.as_directed_to(target))
25822582
.map(|(info, _)| CandidateRouteHop::PublicHop(PublicHopCandidate {
25832583
info,
25842584
short_channel_id: hop.short_channel_id,
@@ -2619,7 +2619,7 @@ where L::Target: Logger {
26192619
.saturating_add(1);
26202620

26212621
// Searching for a direct channel between last checked hop and first_hop_targets
2622-
if let Some(first_channels) = first_hop_targets.get_mut(&target) {
2622+
if let Some(first_channels) = first_hop_targets.get_mut(target) {
26232623
sort_first_hop_channels(first_channels, &used_liquidities,
26242624
recommended_value_msat, our_node_pubkey);
26252625
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)