encointer_ceremonies_assignment/
lib.rs1#![cfg_attr(not(feature = "std"), no_std)]
4
5use crate::math::{checked_ceil_division, checked_mod_inv, checked_modulo, find_prime_below};
6use encointer_primitives::{
7 ceremonies::{AssignmentParams, MeetupIndexType, MeetupTimeOffsetType, ParticipantIndexType},
8 communities::{Location, LossyFrom},
9 RandomNumberGenerator,
10};
11use sp_runtime::{
12 traits::{AtLeast32Bit, Hash},
13 SaturatedConversion,
14};
15use sp_std::{prelude::Vec, vec};
16
17pub mod math;
18
19pub fn assignment_fn(
21 participant_index: ParticipantIndexType,
22 assignment_params: AssignmentParams,
23 assignment_count: u64,
24) -> Option<MeetupIndexType> {
25 participant_index
26 .checked_mul(assignment_params.s1)?
27 .checked_add(assignment_params.s2)
28 .and_then(|div| checked_modulo::<u64>(div, assignment_params.m))
29 .and_then(|div| checked_modulo::<u64>(div, assignment_count))
30}
31
32pub fn generate_assignment_function_params<Hashing: Hash>(
35 num_participants: u64,
36 num_meetups: u64,
37 random_source: &mut RandomNumberGenerator<Hashing>,
38) -> AssignmentParams {
39 let max_skips = 200;
40 let m = find_prime_below(num_participants) as u32;
41 let mut skip_count = 0;
42 let mut s1 = random_source.pick_non_zero_u32(m - 1); let mut s2 = random_source.pick_non_zero_u32(m - 1);
44
45 while skip_count <= max_skips {
46 s1 = random_source.pick_non_zero_u32(m - 1);
47 s2 = random_source.pick_non_zero_u32(m - 1);
48 if validate_equal_mapping(
49 num_participants,
50 AssignmentParams { m: m as u64, s1: s1 as u64, s2: s2 as u64 },
51 num_meetups,
52 ) {
53 break;
54 } else {
55 skip_count += 1; }
57 }
58 AssignmentParams { m: m as u64, s1: s1 as u64, s2: s2 as u64 }
59}
60
61fn validate_equal_mapping(
63 num_participants: u64,
64 assignment_params: AssignmentParams,
65 meetup_count: u64,
66) -> bool {
67 if num_participants < 2 {
68 return true;
69 }
70
71 let mut meetup_index_count: Vec<u64> = vec![0; meetup_count as usize];
72 let meetup_index_count_max =
73 checked_ceil_division(num_participants - assignment_params.m, meetup_count).unwrap_or(0);
74
75 for i in assignment_params.m..num_participants {
76 let meetup_index = match assignment_fn(i, assignment_params, meetup_count) {
77 Some(i) => i as usize,
78 None => return false,
79 };
80
81 meetup_index_count[meetup_index] += 1; if meetup_index_count[meetup_index] > meetup_index_count_max {
83 return false;
84 }
85 }
86 true
87}
88
89pub fn assignment_fn_inverse(
94 meetup_index: u64,
95 assignment_params: AssignmentParams,
96 assignment_count: u64,
97 participant_count: u64,
98) -> Option<Vec<ParticipantIndexType>> {
99 if assignment_count == 0 {
100 return Some(vec![]);
101 }
102
103 let mut max_index = assignment_params.m.saturating_sub(meetup_index) / assignment_count;
104 let mut result: Vec<ParticipantIndexType> = Vec::with_capacity(max_index as usize);
105 if (assignment_params.m.saturating_sub(meetup_index) as i64).rem_euclid(assignment_count as i64) !=
107 0
108 {
109 max_index += 1; }
111
112 for i in 0..max_index {
113 let t2 = checked_mod_inv(assignment_params.s1 as i64, assignment_params.m as i64)?;
114
115 let t3 = match t3(assignment_count, i, meetup_index, assignment_params, t2) {
116 Some(t3) => t3,
117 None => continue,
118 };
119
120 if t3 >= participant_count {
121 continue;
122 }
123
124 result.push(t3);
125
126 if let Some(t3_plus_m) = t3.checked_add(assignment_params.m) {
127 if t3_plus_m < participant_count {
128 result.push(t3_plus_m)
129 }
130 }
131 }
132 Some(result)
133}
134
135fn t3(
136 n: u64,
137 current_index: u64,
138 meetup_index: MeetupIndexType,
139 params: AssignmentParams,
140 t2: i64,
141) -> Option<u64> {
142 let t3 = (n as i64)
143 .checked_mul(current_index as i64)?
144 .checked_add(meetup_index as i64)?
145 .checked_sub(params.s2 as i64)?
146 .checked_rem_euclid(params.m as i64)?
147 .checked_mul(t2)?
148 .checked_rem_euclid(params.m as i64)?;
149
150 Some(t3 as u64)
151}
152
153pub fn meetup_index(
154 participant_index: ParticipantIndexType,
155 params: AssignmentParams,
156 meetup_count: MeetupIndexType,
157) -> Option<MeetupIndexType> {
158 Some(assignment_fn(participant_index, params, meetup_count)? + 1)
159}
160
161pub fn get_meetup_location_index(
162 meetup_index: MeetupIndexType,
163 locations: &[Location],
164 location_assignment_params: AssignmentParams,
165) -> Option<MeetupIndexType> {
166 assignment_fn(meetup_index, location_assignment_params, locations.len() as u64)
167}
168
169pub fn meetup_location(
170 meetup_index: MeetupIndexType,
171 locations: Vec<Location>,
172 location_assignment_params: AssignmentParams,
173) -> Option<Location> {
174 let location_idx =
175 get_meetup_location_index(meetup_index, &locations, location_assignment_params)?;
176
177 if location_idx < locations.len() as u64 {
178 Some(locations[(location_idx) as usize])
179 } else {
180 None
181 }
182}
183
184pub fn meetup_time<Moment: Copy + AtLeast32Bit>(
185 location: Location,
186 attesting_start: Moment,
187 one_day: Moment,
188 offset: MeetupTimeOffsetType,
189) -> Moment {
190 let one_day_u64: u64 = one_day.saturated_into();
191 let per_degree: i64 = one_day_u64 as i64 / 360i64;
192
193 let lon: i64 = (i64::lossy_from(location.lon.round_to_zero()) - 180i64).abs();
198
199 let lon_time = lon * per_degree;
200 let attesting_start_u64: u64 = attesting_start.saturated_into();
201 let meetup_time = attesting_start_u64 as i64 + lon_time + offset as i64;
202 (meetup_time as u64).saturated_into()
203}
204
205#[cfg(test)]
206mod tests {
207 use super::*;
208 use encointer_primitives::communities::Degree;
209 #[test]
210 fn meetup_time_works() {
211 let attesting_start = 1671408000000u64; let moments_per_day = 86400000u64; let meetup_time_offset = -2100000; let greenbay_location =
215 Location { lat: Degree::from_num(0.0), lon: Degree::from_num(-88.15) };
216 assert_eq!(
217 {
218 meetup_time(greenbay_location, attesting_start, moments_per_day, meetup_time_offset)
219 },
220 1671470220000u64
221 ) }
223
224 #[test]
225 fn assignment_fn_works() {
226 assert_eq!(assignment_fn(6, AssignmentParams { m: 4, s1: 5, s2: 3 }, 5).unwrap(), 1)
227 }
228
229 #[test]
230 fn validate_equal_mapping_works() {
231 assert!(!validate_equal_mapping(
232 2761,
233 AssignmentParams { m: 2753, s1: 2326, s2: 1099 },
234 427
235 ));
236 assert!(validate_equal_mapping(
237 2761,
238 AssignmentParams { m: 2753, s1: 2325, s2: 1099 },
239 427
240 ));
241 }
242
243 #[test]
244 fn assignment_fn_inverse_works() {
245 let mut s1 = 78u64;
246 let mut s2 = 23u64;
247 let mut n = 12u64;
248 let mut num_participants = 118u64;
249 let mut m = 113u64;
250
251 let mut assignment_params = AssignmentParams { m, s1, s2 };
252 check_assignment(num_participants, assignment_params, n);
253
254 s1 = 1u64;
255 s2 = 1u64;
256 n = 2u64;
257 num_participants = 20u64;
258 m = 19u64;
259 assignment_params = AssignmentParams { m, s1, s2 };
260 check_assignment(num_participants, assignment_params, n);
261 s1 = 1u64;
262 s2 = 1u64;
263 n = 1u64;
264 num_participants = 10u64;
265 m = 7u64;
266 assignment_params = AssignmentParams { m, s1, s2 };
267 check_assignment(num_participants, assignment_params, n);
268
269 s1 = 1u64;
272 s2 = 1u64;
273 n = 1u64;
274 num_participants = 1u64;
275 m = 2u64;
276 assignment_params = AssignmentParams { m, s1, s2 };
277 check_assignment(num_participants, assignment_params, n);
278 }
279
280 fn check_assignment(num_participants: u64, assignment_params: AssignmentParams, n: u64) {
281 let mut locations: Vec<u64> = vec![0; num_participants as usize];
282
283 for i in 0..num_participants {
284 locations[i as usize] = assignment_fn(i, assignment_params, n).unwrap();
285 }
286
287 let mut assigned_participants: Vec<bool> = vec![false; num_participants as usize];
288
289 for i in 0..n {
291 let participants =
292 assignment_fn_inverse(i, assignment_params, n, num_participants).unwrap();
293 for p in participants {
294 assigned_participants[p as usize] = true;
295 assert_eq!(locations[p as usize], i)
296 }
297 }
298
299 for val in assigned_participants {
301 assert!(val);
302 }
303 }
304}