Skip to content

Commit 199c7e5

Browse files
committed
reduce the tau from 1% to 0.5% to account for min required bundles for offline operator detection
1 parent 2ac15de commit 199c7e5

File tree

3 files changed

+55
-44
lines changed

3 files changed

+55
-44
lines changed

crates/pallet-domains/src/staking_epoch.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use parity_scale_codec::{Decode, DecodeWithMemTracking, Encode};
2424
use scale_info::TypeInfo;
2525
use sp_core::Get;
2626
use sp_domains::offline_operators::{
27-
E_BASE, LN_1_OVER_TAU_1_PERCENT, operator_expected_bundles_in_epoch,
27+
E_BASE, LN_1_OVER_TAU_0_5_PERCENT, operator_expected_bundles_in_epoch,
2828
};
2929
use sp_domains::{DomainId, EpochIndex, OperatorId, OperatorRewardSource};
3030
use sp_runtime::traits::{CheckedAdd, CheckedSub, One, Zero};
@@ -271,7 +271,7 @@ pub(crate) fn do_finalize_domain_epoch_staking<T: Config>(
271271
(*operator_stake).saturated_into(),
272272
epoch_total_stake.saturated_into(),
273273
bundle_slot_probability,
274-
LN_1_OVER_TAU_1_PERCENT,
274+
LN_1_OVER_TAU_0_5_PERCENT,
275275
E_BASE,
276276
);
277277

crates/sp-domains/src/offline_operators.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ use sp_core::{DecodeWithMemTracking, U256};
3434
/// Represent in FixedU128 by multiplying by 1e18 and rounding.
3535
pub const LN_1_OVER_TAU_1_PERCENT: FixedU128 = FixedU128::from_inner(4_605_170_185_988_091_904);
3636

37+
// For τ = 0.5%: ln(1/τ) = ln(200) ≈ 5.298317366548036
38+
// Represent in FixedU128 by multiplying by 1e18 and rounding.
39+
pub const LN_1_OVER_TAU_0_5_PERCENT: FixedU128 = FixedU128::from_inner(5_298_317_366_548_036_608);
40+
3741
/// Threshold for number of bundles operator can produce based on their stake share.
3842
/// Operators whose expected bundles from their threshold < E_BASE will not be checked
3943
/// since they are not throughput relevant.

crates/sp-domains/src/offline_operators/tests.rs

Lines changed: 49 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use crate::bundle_producer_election::calculate_threshold;
22
use crate::offline_operators::{
3-
E_BASE, LN_1_OVER_TAU_1_PERCENT, chernoff_threshold_fp, compute_e_relevance,
4-
is_throughput_relevant_fp, operator_expected_bundles_in_epoch, p_from_threshold,
3+
E_BASE, LN_1_OVER_TAU_0_5_PERCENT, LN_1_OVER_TAU_1_PERCENT, chernoff_threshold_fp,
4+
compute_e_relevance, is_throughput_relevant_fp, operator_expected_bundles_in_epoch,
5+
p_from_threshold,
56
};
67
use num_traits::{One, Zero};
78
use prop_test::proptest::prelude::{ProptestConfig, Strategy};
@@ -11,10 +12,6 @@ use prop_test::proptest::{prop_assert, prop_assert_eq, prop_assume, proptest};
1112
use sp_arithmetic::traits::{SaturatedConversion, Saturating};
1213
use sp_arithmetic::{FixedPointNumber, FixedU128};
1314

14-
// For τ = 0.5%: ln(1/τ) = ln(200) ≈ 5.298317366548036
15-
// Represent in FixedU128 by multiplying by 1e18 and rounding.
16-
const LN_1_OVER_TAU_0_5_PERCENT: FixedU128 = FixedU128::from_inner(5_298_317_366_548_036_000);
17-
1815
// For τ = 0.1%: ln(1/τ) = ln(1000) ≈ 6.907755278982137
1916
// Represent in FixedU128 by multiplying by 1e18 and rounding.
2017
const LN_1_OVER_TAU_0_1_PERCENT: FixedU128 = FixedU128::from_inner(6_907_755_278_982_137_000);
@@ -29,12 +26,22 @@ fn test_ln_1_over_tau_1_value() {
2926
assert_eq!(LN_1_OVER_TAU_1_PERCENT, computed_ln_1_over_tau_1,);
3027
}
3128

29+
#[test]
30+
fn test_ln_1_over_tau_0_5_value() {
31+
// Dynamically compute ln(200) in floating point
32+
// Convert to FixedU128 representation (scaled by 1e18)
33+
let computed_ln_1_over_tau_0_5 = FixedU128::from_inner((f64::ln(200.0) * 1e18).round() as u128);
34+
35+
// Check equality of integer fixed-point representation
36+
assert_eq!(LN_1_OVER_TAU_0_5_PERCENT, computed_ln_1_over_tau_0_5,);
37+
}
38+
3239
#[test]
3340
fn test_chernoff_basic() {
34-
// S = 600, p = 0.05 (μ = 30). τ = 1% -> ln(100)
41+
// S = 600, p = 0.05 (μ = 30). τ = 0.5% -> ln(200)
3542
let s = 600u64;
3643
let p = FixedU128::saturating_from_rational(5u128, 100u128);
37-
let r = chernoff_threshold_fp(s, p, LN_1_OVER_TAU_1_PERCENT).unwrap();
44+
let r = chernoff_threshold_fp(s, p, LN_1_OVER_TAU_0_5_PERCENT).unwrap();
3845
// Chernoff is conservative; r will be noticeably below μ.
3946
// Expect r around low/mid-teens.
4047
assert!(r > 0 && r < 30, "r should be between 1 and 29, got {r}");
@@ -66,13 +73,13 @@ fn test_p_from_threshold() {
6673
}
6774

6875
fn e_rel_1pct() -> u64 {
69-
compute_e_relevance(LN_1_OVER_TAU_1_PERCENT, E_BASE)
76+
compute_e_relevance(LN_1_OVER_TAU_0_5_PERCENT, E_BASE)
7077
}
7178

7279
#[test]
73-
fn e_relevance_is_10_for_tau_1pct() {
74-
// 2 * ln(100) ≈ 9.21034 -> ceil = 10; max(E_BASE=3, 10) = 10
75-
assert_eq!(e_rel_1pct(), 10);
80+
fn e_relevance_is_11_for_tau_0_5_pct() {
81+
// 2 * ln(200) ≈ 10.5966347331 -> ceil = 11; max(E_BASE=3, 11) = 11
82+
assert_eq!(e_rel_1pct(), 11);
7683
}
7784

7885
#[test]
@@ -84,7 +91,7 @@ fn returns_none_when_no_slots_or_zero_threshold_inputs() {
8491
1_000, // operator_stake
8592
1_000_000, // total_domain_stake
8693
(1, 1), // theta
87-
LN_1_OVER_TAU_1_PERCENT,
94+
LN_1_OVER_TAU_0_5_PERCENT,
8895
E_BASE,
8996
)
9097
.is_none()
@@ -97,7 +104,7 @@ fn returns_none_when_no_slots_or_zero_threshold_inputs() {
97104
1_000, // operator_stake
98105
0, // total_domain_stake
99106
(1, 1),
100-
LN_1_OVER_TAU_1_PERCENT,
107+
LN_1_OVER_TAU_0_5_PERCENT,
101108
E_BASE,
102109
)
103110
.is_none()
@@ -110,7 +117,7 @@ fn returns_none_when_no_slots_or_zero_threshold_inputs() {
110117
0, // operator_stake
111118
1_000_000_000_000_000_000, // total_domain_stake
112119
(1, 1),
113-
LN_1_OVER_TAU_1_PERCENT,
120+
LN_1_OVER_TAU_0_5_PERCENT,
114121
E_BASE,
115122
)
116123
.is_none()
@@ -128,7 +135,7 @@ fn small_mu_is_not_throughput_relevant() {
128135
operator,
129136
total,
130137
(1, 1),
131-
LN_1_OVER_TAU_1_PERCENT,
138+
LN_1_OVER_TAU_0_5_PERCENT,
132139
E_BASE,
133140
)
134141
.is_none()
@@ -137,19 +144,19 @@ fn small_mu_is_not_throughput_relevant() {
137144

138145
#[test]
139146
fn relevant_operator_produces_some_expectations() {
140-
// S = 600, share ~ 2% => μ ≈ 12 >= 10 -> Some(...)
147+
// S = 600, share ~ 2% => μ ≈ 12 >= 11 -> Some(...)
141148
let total = 1_000_000_000_000_000_000u128;
142-
let operator = total / 50; // 2%
149+
let operator = total / 30; // 3.33%
143150
let exp = operator_expected_bundles_in_epoch(
144151
600,
145152
operator,
146153
total,
147154
(1, 1),
148-
LN_1_OVER_TAU_1_PERCENT,
155+
LN_1_OVER_TAU_0_5_PERCENT,
149156
E_BASE,
150157
)
151158
.expect("should be relevant");
152-
// expected_bundles should be >= E_relevance (10) for τ=1%
159+
// expected_bundles should be >= E_relevance (11) for τ=0.5%
153160
assert!(exp.expected_bundles >= e_rel_1pct());
154161
// Chernoff threshold is a lower bound; must be <= expected_bundles
155162
assert!(exp.min_required_bundles <= exp.expected_bundles);
@@ -167,7 +174,7 @@ fn near_full_stake_behaves_sensibly() {
167174
operator,
168175
total,
169176
(1, 1),
170-
LN_1_OVER_TAU_1_PERCENT,
177+
LN_1_OVER_TAU_0_5_PERCENT,
171178
E_BASE,
172179
)
173180
.expect("should be relevant");
@@ -187,9 +194,9 @@ fn monotonic_in_stake_when_relevant() {
187194
let tests = [
188195
(total / 200, false), // 0.5% -> not relevant (μ≈3)
189196
(total / 100, false), // 1% -> not relevant (μ≈6)
190-
(total / 59, true), // ~1.695% -> μ_floor >= 10 -> relevant
191-
(total / 50, true), // 2% -> μ≈12
192-
(total / 20, true), // 5% -> μ≈30
197+
(total / 59, false), // ~1.695% -> μ_floor >= 10 -> not relevant
198+
(total / 50, true), // 2% -> μ≈12 relevant
199+
(total / 20, true), // 5% -> μ≈30 relevant
193200
];
194201

195202
let mut last_exp_bundles = 0u64;
@@ -200,7 +207,7 @@ fn monotonic_in_stake_when_relevant() {
200207
stake,
201208
total,
202209
(1, 1),
203-
LN_1_OVER_TAU_1_PERCENT,
210+
LN_1_OVER_TAU_0_5_PERCENT,
204211
E_BASE,
205212
);
206213

@@ -255,16 +262,16 @@ fn chernoff_monotone_in_tau() {
255262
fn chernoff_edges_p_zero_or_one() {
256263
let s = 123u64;
257264
let r_zero =
258-
chernoff_threshold_fp(s, FixedU128::from_inner(0), LN_1_OVER_TAU_1_PERCENT).unwrap();
265+
chernoff_threshold_fp(s, FixedU128::from_inner(0), LN_1_OVER_TAU_0_5_PERCENT).unwrap();
259266
assert_eq!(r_zero, 0);
260-
let r_one = chernoff_threshold_fp(s, FixedU128::one(), LN_1_OVER_TAU_1_PERCENT).unwrap();
267+
let r_one = chernoff_threshold_fp(s, FixedU128::one(), LN_1_OVER_TAU_0_5_PERCENT).unwrap();
261268
assert_eq!(r_one, s);
262269
}
263270

264271
#[test]
265272
fn throughput_relevance_boundary() {
266273
// Construct p so that floor(S * p) == E_relevance, should be relevant.
267-
let e_rel = compute_e_relevance(LN_1_OVER_TAU_1_PERCENT, E_BASE); // 10
274+
let e_rel = compute_e_relevance(LN_1_OVER_TAU_0_5_PERCENT, E_BASE); // 11
268275
let s = 600u64;
269276
let p = FixedU128::saturating_from_rational(e_rel as u128, s as u128);
270277
// at boundary is not relevant
@@ -288,12 +295,12 @@ fn operator_expected_bundles_theta_one() {
288295
operator,
289296
total,
290297
(1, 1),
291-
LN_1_OVER_TAU_1_PERCENT,
298+
LN_1_OVER_TAU_0_5_PERCENT,
292299
E_BASE,
293300
)
294301
.expect("relevant operator should produce expectations");
295302
// Expected bundles >= relevance floor
296-
assert!(out.expected_bundles >= compute_e_relevance(LN_1_OVER_TAU_1_PERCENT, E_BASE));
303+
assert!(out.expected_bundles >= compute_e_relevance(LN_1_OVER_TAU_0_5_PERCENT, E_BASE));
297304
// r <= expected
298305
assert!(out.min_required_bundles <= out.expected_bundles);
299306
// monotone sanity: increasing S increases expected bundles
@@ -302,7 +309,7 @@ fn operator_expected_bundles_theta_one() {
302309
operator,
303310
total,
304311
(1, 1),
305-
LN_1_OVER_TAU_1_PERCENT,
312+
LN_1_OVER_TAU_0_5_PERCENT,
306313
E_BASE,
307314
)
308315
.expect("still relevant at larger S");
@@ -315,13 +322,13 @@ fn operator_expected_bundles_theta_half_not_relevant() {
315322
let s = 600u64;
316323
let total = 1_000_000_000_000_000_000u128;
317324
let operator = total / 50; // 2% stake; μ would be ~12 at theta=1
318-
// theta = 1/2 -> μ ≈ 6 -> below 10 => None
325+
// theta = 1/2 -> μ ≈ 6 -> below 11 => None
319326
let out = operator_expected_bundles_in_epoch(
320327
s,
321328
operator,
322329
total,
323330
(1, 2),
324-
LN_1_OVER_TAU_1_PERCENT,
331+
LN_1_OVER_TAU_0_5_PERCENT,
325332
E_BASE,
326333
);
327334
assert!(out.is_none());
@@ -338,11 +345,11 @@ fn operator_expected_bundles_handles_huge_total_and_small_op() {
338345
operator,
339346
total,
340347
(1, 1),
341-
LN_1_OVER_TAU_1_PERCENT,
348+
LN_1_OVER_TAU_0_5_PERCENT,
342349
E_BASE,
343350
)
344351
.expect("should be relevant");
345-
assert!(out.expected_bundles >= compute_e_relevance(LN_1_OVER_TAU_1_PERCENT, E_BASE));
352+
assert!(out.expected_bundles >= compute_e_relevance(LN_1_OVER_TAU_0_5_PERCENT, E_BASE));
346353
assert!(out.min_required_bundles <= out.expected_bundles);
347354
}
348355

@@ -414,7 +421,7 @@ proptest! {
414421
let stake = operator_stake_strategy(total).new_tree(&mut TestRunner::default()).unwrap().current();
415422

416423
let out = operator_expected_bundles_in_epoch(
417-
s, stake, total, theta, LN_1_OVER_TAU_1_PERCENT, E_BASE
424+
s, stake, total, theta, LN_1_OVER_TAU_0_5_PERCENT, E_BASE
418425
);
419426

420427
let mu_floor = mu_floor_from_threshold(s, stake, total, theta);
@@ -460,10 +467,10 @@ proptest! {
460467
prop_assume!(stake_hi >= stake_lo);
461468

462469
let out_lo = operator_expected_bundles_in_epoch(
463-
s, stake_lo, total, theta, LN_1_OVER_TAU_1_PERCENT, E_BASE
470+
s, stake_lo, total, theta, LN_1_OVER_TAU_0_5_PERCENT, E_BASE
464471
);
465472
let out_hi = operator_expected_bundles_in_epoch(
466-
s, stake_hi, total, theta, LN_1_OVER_TAU_1_PERCENT, E_BASE
473+
s, stake_hi, total, theta, LN_1_OVER_TAU_0_5_PERCENT, E_BASE
467474
);
468475

469476
if let Some(exp_lo) = out_lo {
@@ -495,10 +502,10 @@ proptest! {
495502
let (s_min, s_max) = if s1 <= s2 { (s1, s2) } else { (s2, s1) };
496503

497504
let out_min = operator_expected_bundles_in_epoch(
498-
s_min, stake, total, theta, LN_1_OVER_TAU_1_PERCENT, E_BASE
505+
s_min, stake, total, theta, LN_1_OVER_TAU_0_5_PERCENT, E_BASE
499506
);
500507
let out_max = operator_expected_bundles_in_epoch(
501-
s_max, stake, total, theta, LN_1_OVER_TAU_1_PERCENT, E_BASE
508+
s_max, stake, total, theta, LN_1_OVER_TAU_0_5_PERCENT, E_BASE
502509
);
503510

504511
if let Some(exp_min) = out_min {
@@ -533,10 +540,10 @@ proptest! {
533540
let theta_hi = (num_hi, den);
534541

535542
let out_lo = operator_expected_bundles_in_epoch(
536-
s, stake, total, theta_lo, LN_1_OVER_TAU_1_PERCENT, E_BASE
543+
s, stake, total, theta_lo, LN_1_OVER_TAU_0_5_PERCENT, E_BASE
537544
);
538545
let out_hi = operator_expected_bundles_in_epoch(
539-
s, stake, total, theta_hi, LN_1_OVER_TAU_1_PERCENT, E_BASE
546+
s, stake, total, theta_hi, LN_1_OVER_TAU_0_5_PERCENT, E_BASE
540547
);
541548

542549
if let Some(exp_lo) = out_lo {

0 commit comments

Comments
 (0)