1use crate::{epoch::Epoch, EigenError};
8use libp2p::PeerId;
9use std::collections::HashMap;
10
11#[derive(Clone, Copy, Debug, PartialEq)]
13pub struct Opinion {
14 k: Epoch,
15 local_trust_score: f64,
16 global_trust_score: f64,
17 product: f64,
18}
19
20impl Opinion {
21 pub fn new(k: Epoch, local_trust_score: f64, global_trust_score: f64, product: f64) -> Self {
23 Self {
24 k,
25 local_trust_score,
26 global_trust_score,
27 product,
28 }
29 }
30
31 pub fn empty(k: Epoch) -> Self {
34 Self::new(k, 0.0, 0.0, 0.0)
35 }
36
37 pub fn get_epoch(&self) -> Epoch {
39 self.k
40 }
41
42 pub fn get_local_trust_score(&self) -> f64 {
44 self.local_trust_score
45 }
46
47 pub fn get_global_trust_score(&self) -> f64 {
49 self.global_trust_score
50 }
51
52 pub fn get_product(&self) -> f64 {
54 self.product
55 }
56}
57
58pub struct Peer {
60 neighbor_scores: HashMap<PeerId, u32>,
61 neighbors: Vec<Option<PeerId>>,
62 cached_neighbor_opinion: HashMap<(PeerId, Epoch), Opinion>,
63 cached_local_opinion: HashMap<(PeerId, Epoch), Opinion>,
64 pre_trust_score: f64,
65 pre_trust_weight: f64,
66}
67
68impl Peer {
69 pub fn new(num_neighbors: usize, pre_trust_score: f64, pre_trust_weight: f64) -> Self {
71 let mut neighbors = Vec::with_capacity(num_neighbors);
74 for _ in 0..num_neighbors {
75 neighbors.push(None);
76 }
77 Peer {
78 neighbors,
79 neighbor_scores: HashMap::new(),
80 cached_neighbor_opinion: HashMap::new(),
81 cached_local_opinion: HashMap::new(),
82 pre_trust_score,
83 pre_trust_weight,
84 }
85 }
86
87 pub fn add_neighbor(&mut self, peer_id: PeerId) -> Result<(), EigenError> {
89 if self.neighbors.contains(&Some(peer_id)) {
90 return Ok(());
91 }
92 let index = self
93 .neighbors
94 .iter()
95 .position(|&x| x.is_none())
96 .ok_or(EigenError::MaxNeighboursReached)?;
97 self.neighbors[index] = Some(peer_id);
98 Ok(())
99 }
100
101 pub fn remove_neighbor(&mut self, peer_id: PeerId) {
103 let index_res = self.neighbors.iter().position(|&x| x == Some(peer_id));
104 if let Some(index) = index_res {
105 self.neighbors[index] = None;
106 }
107 }
108
109 pub fn neighbors(&self) -> Vec<PeerId> {
111 self.neighbors.iter().filter_map(|&x| x).collect()
112 }
113
114 pub fn set_score(&mut self, peer_id: PeerId, score: u32) {
116 self.neighbor_scores.insert(peer_id, score);
117 }
118
119 pub fn calculate_global_trust_score(&self, epoch: Epoch) -> f64 {
124 let mut global_score = 0.;
125
126 for peer_id in self.neighbors() {
127 let opinion = self.get_neighbor_opinion(&(peer_id, epoch));
128 global_score += opinion.get_product();
129 }
130 global_score = (1. - self.pre_trust_weight) * global_score
132 + self.pre_trust_weight * self.pre_trust_score;
133
134 global_score
135 }
136
137 pub fn calculate_local_opinions(&mut self, k: Epoch) {
140 let global_score = self.calculate_global_trust_score(k);
141
142 let mut opinions = Vec::new();
143 for peer_id in self.neighbors() {
144 let score = self.neighbor_scores.get(&peer_id).unwrap_or(&0);
145 let normalized_score = self.get_normalized_score(*score);
146 let product = global_score * normalized_score;
147 let opinion = Opinion::new(k.next(), normalized_score, global_score, product);
148
149 opinions.push((peer_id, opinion));
150 }
151
152 for (peer_id, opinion) in opinions {
153 self.cache_local_opinion((peer_id, opinion.get_epoch()), opinion);
154 }
155 }
156
157 pub fn get_sum_of_scores(&self) -> u32 {
159 let mut sum = 0;
160 for peer_id in self.neighbors() {
161 let score = self.neighbor_scores.get(&peer_id).unwrap_or(&0);
162 sum += score;
163 }
164 sum
165 }
166
167 pub fn get_normalized_score(&self, score: u32) -> f64 {
169 let sum = self.get_sum_of_scores();
170 let f_raw_score = f64::from(score);
171 let f_sum = f64::from(sum);
172 f_raw_score / f_sum
173 }
174
175 pub fn get_local_opinion(&self, key: &(PeerId, Epoch)) -> Opinion {
177 *self
178 .cached_local_opinion
179 .get(key)
180 .unwrap_or(&Opinion::empty(key.1))
181 }
182
183 pub fn cache_local_opinion(&mut self, key: (PeerId, Epoch), opinion: Opinion) {
185 self.cached_local_opinion.insert(key, opinion);
186 }
187
188 pub fn get_neighbor_opinion(&self, key: &(PeerId, Epoch)) -> Opinion {
190 *self
191 .cached_neighbor_opinion
192 .get(key)
193 .unwrap_or(&Opinion::empty(key.1))
194 }
195
196 pub fn cache_neighbor_opinion(&mut self, key: (PeerId, Epoch), opinion: Opinion) {
198 self.cached_neighbor_opinion.insert(key, opinion);
199 }
200}
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205
206 const NUM_CONNECTIONS: usize = 256;
207
208 #[test]
209 fn should_create_opinion() {
210 let opinion = Opinion::new(Epoch(0), 0.5, 0.5, 0.5);
211 assert_eq!(opinion.get_epoch(), Epoch(0));
212 assert_eq!(opinion.get_global_trust_score(), 0.5);
213 assert_eq!(opinion.get_local_trust_score(), 0.5);
214 assert_eq!(opinion.get_product(), 0.5);
215 }
216
217 #[test]
218 fn should_create_peer() {
219 let peer = Peer::new(NUM_CONNECTIONS, 0.5, 0.5);
220 assert_eq!(peer.pre_trust_score, 0.5);
221 assert_eq!(peer.pre_trust_weight, 0.5);
222 assert_eq!(peer.get_sum_of_scores(), 0);
223 }
224
225 #[test]
226 fn should_cache_local_and_global_opinion() {
227 let pre_trust_score = 0.5;
228 let pre_trust_weight = 0.5;
229 let mut peer = Peer::new(NUM_CONNECTIONS, pre_trust_score, pre_trust_weight);
230
231 let epoch = Epoch(0);
232 let neighbor_id = PeerId::random();
233 let opinion = Opinion::new(epoch, 0.5, 0.5, 0.25);
234 peer.cache_local_opinion((neighbor_id, epoch), opinion);
235 peer.cache_neighbor_opinion((neighbor_id, epoch), opinion);
236
237 assert_eq!(peer.get_local_opinion(&(neighbor_id, epoch)), opinion);
238 assert_eq!(peer.get_neighbor_opinion(&(neighbor_id, epoch)), opinion);
239 }
240
241 #[test]
242 fn should_add_and_remove_neghbours() {
243 let mut peer = Peer::new(NUM_CONNECTIONS, 0.5, 0.5);
244 let neighbor_id = PeerId::random();
245
246 peer.add_neighbor(neighbor_id).unwrap();
247 let num_neighbors = peer.neighbors().len();
248 assert_eq!(num_neighbors, 1);
249
250 peer.remove_neighbor(neighbor_id);
251 let num_neighbors = peer.neighbors().len();
252 assert_eq!(num_neighbors, 0);
253 }
254
255 #[test]
256 fn should_add_neighbors_and_calculate_global_score() {
257 let pre_trust_score = 0.5;
258 let pre_trust_weight = 0.5;
259 let mut peer = Peer::new(NUM_CONNECTIONS, pre_trust_score, pre_trust_weight);
260
261 let epoch = Epoch(0);
262 for _ in 0..256 {
263 let peer_id = PeerId::random();
264 peer.add_neighbor(peer_id).unwrap();
265 peer.set_score(peer_id, 5);
266 let opinion = Opinion::new(epoch, 0.1, 0.1, 0.01);
267 peer.cache_neighbor_opinion((peer_id, epoch), opinion);
268 }
269
270 let global_score = peer.calculate_global_trust_score(epoch);
271
272 let mut true_global_score = 0.0;
273 for _ in 0..256 {
274 true_global_score += 0.01;
275 }
276 let boostrap_score =
277 (1. - pre_trust_weight) * true_global_score + pre_trust_weight * pre_trust_score;
278
279 assert_eq!(boostrap_score, global_score);
280 }
281
282 #[test]
283 fn should_add_neighbors_and_calculate_local_scores() {
284 let pre_trust_score = 0.5;
285 let pre_trust_weight = 0.5;
286 let mut peer = Peer::new(NUM_CONNECTIONS, pre_trust_score, pre_trust_weight);
287
288 let epoch = Epoch(0);
289 for _ in 0..256 {
290 let peer_id = PeerId::random();
291 peer.add_neighbor(peer_id).unwrap();
292 peer.set_score(peer_id, 5);
293 let opinion = Opinion::new(epoch, 0.1, 0.1, 0.01);
294 peer.cache_neighbor_opinion((peer_id, epoch), opinion);
295 }
296
297 let global_score = peer.calculate_global_trust_score(epoch);
298
299 peer.calculate_local_opinions(epoch);
300
301 for peer_id in peer.neighbors() {
302 let opinion = peer.get_local_opinion(&(peer_id, epoch.next()));
303 let score = peer.neighbor_scores.get(&peer_id).unwrap_or(&0);
304 let normalized_score = peer.get_normalized_score(*score);
305 let local_score = normalized_score * global_score;
306 assert_eq!(opinion.get_product(), local_score);
307 }
308 }
309}