Skip to content

Commit 94cc358

Browse files
authored
Merge pull request #4296 from joostjager/connect-style-var
Improve test determinism with configurable connect style and deterministic hash maps
2 parents 5236dba + 075bc34 commit 94cc358

File tree

3 files changed

+105
-2
lines changed

3 files changed

+105
-2
lines changed

CONTRIBUTING.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,22 @@ Fuzzing is heavily encouraged: you will find all related material under `fuzz/`
176176
Mutation testing is work-in-progress; any contribution there would be warmly
177177
welcomed.
178178

179+
### Environment Variables
180+
181+
* `LDK_TEST_CONNECT_STYLE` - Override the random block connect style used in tests for deterministic runs. Valid values:
182+
* `BEST_BLOCK_FIRST`
183+
* `BEST_BLOCK_FIRST_SKIPPING_BLOCKS`
184+
* `BEST_BLOCK_FIRST_REORGS_ONLY_TIP`
185+
* `TRANSACTIONS_FIRST`
186+
* `TRANSACTIONS_FIRST_SKIPPING_BLOCKS`
187+
* `TRANSACTIONS_DUPLICATIVELY_FIRST_SKIPPING_BLOCKS`
188+
* `HIGHLY_REDUNDANT_TRANSACTIONS_FIRST_SKIPPING_BLOCKS`
189+
* `TRANSACTIONS_FIRST_REORGS_ONLY_TIP`
190+
* `FULL_BLOCK_VIA_LISTEN`
191+
* `FULL_BLOCK_DISCONNECTIONS_SKIPPING_VIA_LISTEN`
192+
193+
* `LDK_TEST_DETERMINISTIC_HASHES` - When set to `1`, uses deterministic hash map iteration order in tests. This ensures consistent test output across runs, useful for comparing logs before and after changes.
194+
179195
C/C++ Bindings
180196
--------------
181197

lightning/src/ln/functional_test_utils.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4563,7 +4563,29 @@ pub fn create_network<'a, 'b: 'a, 'c: 'b>(
45634563
let mut nodes = Vec::new();
45644564
let chan_count = Rc::new(RefCell::new(0));
45654565
let payment_count = Rc::new(RefCell::new(0));
4566-
let connect_style = Rc::new(RefCell::new(ConnectStyle::random_style()));
4566+
4567+
let connect_style = Rc::new(RefCell::new(match std::env::var("LDK_TEST_CONNECT_STYLE") {
4568+
Ok(val) => match val.as_str() {
4569+
"BEST_BLOCK_FIRST" => ConnectStyle::BestBlockFirst,
4570+
"BEST_BLOCK_FIRST_SKIPPING_BLOCKS" => ConnectStyle::BestBlockFirstSkippingBlocks,
4571+
"BEST_BLOCK_FIRST_REORGS_ONLY_TIP" => ConnectStyle::BestBlockFirstReorgsOnlyTip,
4572+
"TRANSACTIONS_FIRST" => ConnectStyle::TransactionsFirst,
4573+
"TRANSACTIONS_FIRST_SKIPPING_BLOCKS" => ConnectStyle::TransactionsFirstSkippingBlocks,
4574+
"TRANSACTIONS_DUPLICATIVELY_FIRST_SKIPPING_BLOCKS" => {
4575+
ConnectStyle::TransactionsDuplicativelyFirstSkippingBlocks
4576+
},
4577+
"HIGHLY_REDUNDANT_TRANSACTIONS_FIRST_SKIPPING_BLOCKS" => {
4578+
ConnectStyle::HighlyRedundantTransactionsFirstSkippingBlocks
4579+
},
4580+
"TRANSACTIONS_FIRST_REORGS_ONLY_TIP" => ConnectStyle::TransactionsFirstReorgsOnlyTip,
4581+
"FULL_BLOCK_VIA_LISTEN" => ConnectStyle::FullBlockViaListen,
4582+
"FULL_BLOCK_DISCONNECTIONS_SKIPPING_VIA_LISTEN" => {
4583+
ConnectStyle::FullBlockDisconnectionsSkippingViaListen
4584+
},
4585+
_ => panic!("Unknown ConnectStyle '{}'", val),
4586+
},
4587+
Err(_) => ConnectStyle::random_style(),
4588+
}));
45674589

45684590
for i in 0..node_count {
45694591
let dedicated_entropy = DedicatedEntropy(RandomBytes::new([i as u8; 32]));

lightning/src/util/hash_tables.rs

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,75 @@
66
pub use hashbrown::hash_map;
77

88
mod hashbrown_tables {
9-
#[cfg(feature = "std")]
9+
#[cfg(all(feature = "std", not(test)))]
1010
mod hasher {
1111
pub use std::collections::hash_map::RandomState;
1212
}
13+
#[cfg(all(feature = "std", test))]
14+
mod hasher {
15+
#![allow(deprecated)] // hash::SipHasher was deprecated in favor of something only in std.
16+
use core::hash::{BuildHasher, Hasher};
17+
18+
/// A [`BuildHasher`] for tests that supports deterministic behavior via environment variable.
19+
///
20+
/// When `LDK_TEST_DETERMINISTIC_HASHES` is set, uses fixed keys for deterministic iteration.
21+
/// Otherwise, delegates to std's RandomState for random hashing.
22+
#[derive(Clone)]
23+
pub enum RandomState {
24+
Std(std::collections::hash_map::RandomState),
25+
Deterministic,
26+
}
27+
28+
impl RandomState {
29+
pub fn new() -> RandomState {
30+
if std::env::var("LDK_TEST_DETERMINISTIC_HASHES").map(|v| v == "1").unwrap_or(false)
31+
{
32+
RandomState::Deterministic
33+
} else {
34+
RandomState::Std(std::collections::hash_map::RandomState::new())
35+
}
36+
}
37+
}
38+
39+
impl Default for RandomState {
40+
fn default() -> RandomState {
41+
RandomState::new()
42+
}
43+
}
44+
45+
/// A hasher wrapper that delegates to either std's DefaultHasher or a deterministic SipHasher.
46+
pub enum RandomStateHasher {
47+
Std(std::collections::hash_map::DefaultHasher),
48+
Deterministic(core::hash::SipHasher),
49+
}
50+
51+
impl Hasher for RandomStateHasher {
52+
fn finish(&self) -> u64 {
53+
match self {
54+
RandomStateHasher::Std(h) => h.finish(),
55+
RandomStateHasher::Deterministic(h) => h.finish(),
56+
}
57+
}
58+
fn write(&mut self, bytes: &[u8]) {
59+
match self {
60+
RandomStateHasher::Std(h) => h.write(bytes),
61+
RandomStateHasher::Deterministic(h) => h.write(bytes),
62+
}
63+
}
64+
}
65+
66+
impl BuildHasher for RandomState {
67+
type Hasher = RandomStateHasher;
68+
fn build_hasher(&self) -> RandomStateHasher {
69+
match self {
70+
RandomState::Std(s) => RandomStateHasher::Std(s.build_hasher()),
71+
RandomState::Deterministic => {
72+
RandomStateHasher::Deterministic(core::hash::SipHasher::new_with_keys(0, 0))
73+
},
74+
}
75+
}
76+
}
77+
}
1378
#[cfg(not(feature = "std"))]
1479
mod hasher {
1580
#![allow(deprecated)] // hash::SipHasher was deprecated in favor of something only in std.

0 commit comments

Comments
 (0)