Skip to content

Commit fb69030

Browse files
Merge pull request #1249 from mintlayer/fix_next_connect_time
Fix next_connect_time
2 parents ef59b6b + c1569e0 commit fb69030

File tree

5 files changed

+64
-16
lines changed

5 files changed

+64
-16
lines changed

crypto/src/random/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ pub mod distributions {
2626
}
2727

2828
pub mod rngs {
29+
pub use rand::rngs::mock::StepRng;
2930
pub use rand::rngs::OsRng;
3031
}
3132

p2p/src/peer_manager/peerdb/address_data/mod.rs

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,14 @@ const PURGE_REACHABLE_TIME: Duration = Duration::from_secs(3600 * 24 * 7 * 4);
3434
const PURGE_REACHABLE_FAIL_COUNT: u32 =
3535
(PURGE_REACHABLE_TIME.as_secs() / MAX_DELAY_REACHABLE.as_secs()) as u32;
3636

37+
/// The maximum value for the random factor by which reconnection delays will be multiplied.
38+
///
39+
/// Note that the value was chosen based on bitcoin's implementation of GetExponentialRand
40+
/// (https://github.com/bitcoin/bitcoin/blob/5bbf735defac20f58133bea95226e13a5d8209bc/src/random.cpp#L689)
41+
/// which they use to scale delays. In their implementation, the maximum scale factor will be
42+
/// -ln(0.0000000000000035527136788) which is about 33.
43+
const MAX_DELAY_FACTOR: u32 = 30;
44+
3745
pub enum AddressState {
3846
Connected {},
3947

@@ -164,20 +172,9 @@ impl AddressData {
164172
}
165173

166174
fn next_connect_time(now: Time, fail_count: u32, reserved: bool, rng: &mut impl Rng) -> Time {
167-
let random_offset = Self::next_connect_delay(fail_count, reserved)
168-
.mul_f64(utils::exp_rand::exponential_rand(rng));
169-
loop {
170-
let res = now + random_offset;
171-
match res {
172-
Some(r) => return r,
173-
None => {
174-
// The random offset is too large, try again
175-
logging::log::debug!(
176-
"next_connect_time: Random_offset too large (now: {now:?}, offset: {random_offset:?}), trying again"
177-
);
178-
}
179-
}
180-
}
175+
let factor = utils::exp_rand::exponential_rand(rng).clamp(0.0, MAX_DELAY_FACTOR as f64);
176+
let offset = Self::next_connect_delay(fail_count, reserved).mul_f64(factor);
177+
(now + offset).expect("Unexpected time addition overflow")
181178
}
182179

183180
pub fn transition_to(

p2p/src/peer_manager/peerdb/address_data/tests.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
use crypto::random::{
1717
distributions::{Distribution, WeightedIndex},
18+
rngs::StepRng,
1819
Rng,
1920
};
2021
use rstest::rstest;
@@ -89,3 +90,33 @@ fn reachable_reconnects(#[case] seed: Seed) {
8990
"invalid time until removed: {time_until_removed:?}"
9091
);
9192
}
93+
94+
fn next_connect_time_test_impl(rng: &mut impl Rng) {
95+
let limit_reserved = MAX_DELAY_RESERVED * MAX_DELAY_FACTOR;
96+
let limit_reachable = MAX_DELAY_REACHABLE * MAX_DELAY_FACTOR;
97+
98+
let start_time = Time::from_secs_since_epoch(0);
99+
let max_time_reserved = (start_time + limit_reserved).unwrap();
100+
let max_time_reachable = (start_time + limit_reachable).unwrap();
101+
102+
let time = AddressData::next_connect_time(start_time, 0, true, rng);
103+
assert!(time <= max_time_reserved);
104+
105+
let time = AddressData::next_connect_time(start_time, 0, false, rng);
106+
assert!(time <= max_time_reachable);
107+
108+
let time = AddressData::next_connect_time(start_time, u32::MAX, true, rng);
109+
assert!(time <= max_time_reserved);
110+
111+
let time = AddressData::next_connect_time(start_time, u32::MAX, false, rng);
112+
assert!(time <= max_time_reachable);
113+
}
114+
115+
#[test]
116+
fn next_connect_time() {
117+
let mut always_zero_rng = StepRng::new(0, 0);
118+
next_connect_time_test_impl(&mut always_zero_rng);
119+
120+
let mut always_max_rng = StepRng::new(u64::MAX, 0);
121+
next_connect_time_test_impl(&mut always_max_rng);
122+
}

utils/src/exp_rand/mod.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,15 @@ use crypto::random::Rng;
1717

1818
/// Returns a value sampled from an exponential distribution with a mean of 1.0
1919
pub fn exponential_rand(rng: &mut impl Rng) -> f64 {
20+
let mut random_f64 = rng.gen::<f64>();
21+
// The generated number will be in the range [0, 1). Turn it into (0, 1) to avoid
22+
// infinity when taking the logarithm.
23+
if random_f64 == 0.0 {
24+
random_f64 = f64::MIN_POSITIVE;
25+
}
26+
2027
#[allow(clippy::float_arithmetic)]
21-
-rng.gen::<f64>().ln()
28+
-random_f64.ln()
2229
}
2330

2431
#[cfg(test)]

utils/src/exp_rand/test.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,29 @@
1515

1616
use super::*;
1717

18+
use crypto::random::rngs::StepRng;
1819
use rstest::rstest;
1920
use test_utils::random::{make_seedable_rng, Seed};
2021

2122
#[rstest]
2223
#[trace]
2324
#[case(Seed::from_entropy())]
24-
fn test_exponential_rand(#[case] seed: Seed) {
25+
fn test_average_value(#[case] seed: Seed) {
2526
let mut rng = make_seedable_rng(seed);
2627

2728
let count = 1000;
2829
let sum: f64 = (0..count).map(|_| exponential_rand(&mut rng)).sum();
2930
let average = sum / count as f64;
3031
assert!(0.8 < average && average < 1.2);
3132
}
33+
34+
#[test]
35+
fn expect_finite_values_in_degenerate_cases() {
36+
let mut always_zero_rng = StepRng::new(0, 0);
37+
let val = exponential_rand(&mut always_zero_rng);
38+
assert!(val.is_finite());
39+
40+
let mut always_max_rng = StepRng::new(u64::MAX, 0);
41+
let val = exponential_rand(&mut always_max_rng);
42+
assert!(val.is_finite());
43+
}

0 commit comments

Comments
 (0)