Skip to content

Commit 05af04c

Browse files
committed
truncate exponential delay to practical limits based on probablity
1 parent 5bae465 commit 05af04c

2 files changed

Lines changed: 35 additions & 5 deletions

File tree

libp2p/protocols/mix/delay_strategy.nim

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,18 +38,26 @@ method generateForIntermediate*(
3838
encodedDelayMs
3939

4040
const DefaultMeanDelayMs* = 100
41+
const DefaultNegligibleProb* = 1e-6
42+
## Probability below which the tail of the exponential distribution is truncated.
43+
## Yields a maximum delay of mean * -ln(negligibleProb) ≈ mean * 13.8.
4144

4245
type ExponentialDelayStrategy* = ref object of DelayStrategy
4346
## Recommended strategy: encodes mean delay, samples from exponential distribution.
47+
## The distribution is truncated at -mean*ln(negligibleProb), discarding the
48+
## impractically long tail while preserving the mixing properties.
4449
meanDelayMs: uint16
50+
negligibleProb: float64
4551

4652
proc new*(
4753
T: typedesc[ExponentialDelayStrategy],
4854
meanDelayMs: uint16 = DefaultMeanDelayMs,
4955
rng: ref HmacDrbgContext,
56+
negligibleProb: float64 = DefaultNegligibleProb,
5057
): T =
5158
doAssert(rng != nil, "random is not set")
52-
T(meanDelayMs: meanDelayMs, rng: rng)
59+
doAssert(negligibleProb > 0.0 and negligibleProb < 1.0, "negligibleProb must be in (0, 1)")
60+
T(meanDelayMs: meanDelayMs, rng: rng, negligibleProb: negligibleProb)
5361

5462
method generateForEntry*(
5563
self: ExponentialDelayStrategy
@@ -59,11 +67,12 @@ method generateForEntry*(
5967
method generateForIntermediate*(
6068
self: ExponentialDelayStrategy, meanDelayMs: uint16
6169
): uint16 {.gcsafe, raises: [].} =
62-
## Samples from exponential distribution: delay = -mean * ln(U)
63-
## Fall back to no delay in case of errors
70+
## Samples from exponential distribution: delay = -mean * ln(U), truncated to
71+
## -mean*ln(negligibleProb) to discard the impractically long tail.
6472
if meanDelayMs == 0:
6573
return 0u16
74+
let maxDelayMs = -float64(meanDelayMs) * ln(self.negligibleProb)
6675
let randVal = self.rng[].generate(uint64)
6776
let u = (float64(randVal) + 1.0) / (float64(high(uint64)) + 1.0)
6877
let delay = -float64(meanDelayMs) * ln(u)
69-
min(delay, float64(high(uint16))).uint16
78+
min(delay, maxDelayMs).uint16

tests/libp2p/mix/test_delay_strategy.nim

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
{.used.}
55

6-
import std/[sets]
6+
import std/[math, sets]
77
import ../../../libp2p/protocols/mix/delay_strategy
88
import ../../tools/[unittest, crypto]
99

@@ -66,3 +66,24 @@ suite "DelayStrategy":
6666
delays.incl(delay)
6767

6868
check delays.len > NumSamples div 2
69+
70+
test "ExponentialDelayStrategy truncates at negligible probability threshold":
71+
let
72+
meanDelayMs: uint16 = 100
73+
negligibleProb = DefaultNegligibleProb
74+
strategy = ExponentialDelayStrategy.new(meanDelayMs, rng(), negligibleProb)
75+
# maxDelay = -mean * ln(negligibleProb)
76+
maxDelayMs = uint16(-float64(meanDelayMs) * ln(negligibleProb))
77+
78+
for _ in 0 ..< 10000:
79+
check strategy.generateForIntermediate(meanDelayMs) <= maxDelayMs
80+
81+
test "ExponentialDelayStrategy respects custom negligibleProb":
82+
let
83+
meanDelayMs: uint16 = 100
84+
negligibleProb = 0.01 # aggressive truncation: max ≈ mean * 4.6
85+
strategy = ExponentialDelayStrategy.new(meanDelayMs, rng(), negligibleProb)
86+
maxDelayMs = uint16(-float64(meanDelayMs) * ln(negligibleProb))
87+
88+
for _ in 0 ..< 10000:
89+
check strategy.generateForIntermediate(meanDelayMs) <= maxDelayMs

0 commit comments

Comments
 (0)