eigen_trust/
peer.rs

1//! The module for the peer related functionalities, like:
2//! - Adding/removing neighbors
3//! - Calculating the global trust score
4//! - Calculating local scores toward neighbors for a given epoch
5//! - Keeping track of neighbors scores towards us
6
7use crate::{epoch::Epoch, EigenError};
8use libp2p::PeerId;
9use std::collections::HashMap;
10
11/// The struct for opinions between peers at the specific epoch.
12#[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	/// Creates a new opinion.
22	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	/// Creates an empty opinion, in a case when we don't have any opinion about
32	/// a peer, or the neighbor doesn't have any opinion about us.
33	pub fn empty(k: Epoch) -> Self {
34		Self::new(k, 0.0, 0.0, 0.0)
35	}
36
37	/// Returns the epoch of the opinion.
38	pub fn get_epoch(&self) -> Epoch {
39		self.k
40	}
41
42	/// Returns the local trust score of the opinion.
43	pub fn get_local_trust_score(&self) -> f64 {
44		self.local_trust_score
45	}
46
47	/// Returns the global trust score of the opinion.
48	pub fn get_global_trust_score(&self) -> f64 {
49		self.global_trust_score
50	}
51
52	/// Returns the product of global and local trust score of the opinion.
53	pub fn get_product(&self) -> f64 {
54		self.product
55	}
56}
57
58/// The peer struct.
59pub 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	/// Creates a new peer.
70	pub fn new(num_neighbors: usize, pre_trust_score: f64, pre_trust_weight: f64) -> Self {
71		// Our neighbors array is fixed size.
72		// TODO: Consider using ArrayVec instead.
73		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	/// Adds a neighbor in the first available spot.
88	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	/// Removes a neighbor, if found.
102	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	/// Returns the neighbors of the peer.
110	pub fn neighbors(&self) -> Vec<PeerId> {
111		self.neighbors.iter().filter_map(|&x| x).collect()
112	}
113
114	/// Set the local score towards a neighbor.
115	pub fn set_score(&mut self, peer_id: PeerId, score: u32) {
116		self.neighbor_scores.insert(peer_id, score);
117	}
118
119	/// Calculate the global trust score of the peer in the specified epoch.
120	/// We do this by taking the sum of neighbor's opinions and weighting it by
121	/// the pre trust weight. Then we are adding it with the weighted pre-trust
122	/// score.
123	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		// We are adding the weighted pre trust score to the weighted score.
131		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	/// Calculate the local trust score toward all neighbors in the specified
138	/// epoch.
139	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	/// Returns sum of local scores.
158	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	/// Returns the normalized score.
168	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	/// Returns the local score towards a neighbor in a specified epoch.
176	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	/// Caches the local opinion towards a peer in a specified epoch.
184	pub fn cache_local_opinion(&mut self, key: (PeerId, Epoch), opinion: Opinion) {
185		self.cached_local_opinion.insert(key, opinion);
186	}
187
188	/// Returns the neighbor's opinion towards us in a specified epoch.
189	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	/// Caches the neighbor opinion towards us in specified epoch.
197	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}