agentic_payments/consensus/
reputation.rs

1//! Reputation System
2//!
3//! Manages authority reputation for weighted Byzantine Fault Tolerant consensus.
4//! Reputation affects vote weights and Byzantine detection.
5
6use super::AuthorityId;
7use crate::error::{Error, Result};
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10
11/// Reputation configuration
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct ReputationConfig {
14    /// Initial reputation for new authorities
15    pub initial_reputation: f64,
16    /// Minimum reputation before authority is excluded
17    pub min_reputation: f64,
18    /// Maximum reputation cap
19    pub max_reputation: f64,
20    /// Reputation increase for correct votes
21    pub correct_vote_reward: f64,
22    /// Reputation decrease for incorrect votes
23    pub incorrect_vote_penalty: f64,
24    /// Reputation decrease for timeouts
25    pub timeout_penalty: f64,
26    /// Reputation decrease for Byzantine behavior
27    pub byzantine_penalty: f64,
28    /// Decay rate per round (0.0 = no decay)
29    pub decay_rate: f64,
30}
31
32impl Default for ReputationConfig {
33    fn default() -> Self {
34        ReputationConfig {
35            initial_reputation: 1.0,
36            min_reputation: 0.1,
37            max_reputation: 2.0,
38            correct_vote_reward: 0.01,
39            incorrect_vote_penalty: 0.05,
40            timeout_penalty: 0.02,
41            byzantine_penalty: 0.5,
42            decay_rate: 0.001,
43        }
44    }
45}
46
47/// Authority reputation entry
48#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct ReputationEntry {
50    pub authority: AuthorityId,
51    pub reputation: f64,
52    pub correct_votes: u64,
53    pub incorrect_votes: u64,
54    pub timeouts: u64,
55    pub byzantine_faults: u64,
56    pub total_rounds: u64,
57}
58
59impl ReputationEntry {
60    pub fn new(authority: AuthorityId, initial_reputation: f64) -> Self {
61        ReputationEntry {
62            authority,
63            reputation: initial_reputation,
64            correct_votes: 0,
65            incorrect_votes: 0,
66            timeouts: 0,
67            byzantine_faults: 0,
68            total_rounds: 0,
69        }
70    }
71
72    /// Calculate accuracy rate
73    pub fn accuracy(&self) -> f64 {
74        let total = self.correct_votes + self.incorrect_votes;
75        if total == 0 {
76            1.0
77        } else {
78            self.correct_votes as f64 / total as f64
79        }
80    }
81
82    /// Calculate reliability (considering timeouts)
83    pub fn reliability(&self) -> f64 {
84        if self.total_rounds == 0 {
85            1.0
86        } else {
87            let participated = self.total_rounds - self.timeouts;
88            participated as f64 / self.total_rounds as f64
89        }
90    }
91
92    /// Check if authority is trustworthy
93    pub fn is_trustworthy(&self, min_reputation: f64) -> bool {
94        self.reputation >= min_reputation && self.byzantine_faults == 0
95    }
96}
97
98/// Reputation system
99pub struct ReputationSystem {
100    config: ReputationConfig,
101    reputations: HashMap<AuthorityId, ReputationEntry>,
102}
103
104impl ReputationSystem {
105    pub fn new(config: ReputationConfig) -> Self {
106        ReputationSystem {
107            config,
108            reputations: HashMap::new(),
109        }
110    }
111
112    /// Register a new authority
113    pub fn register_authority(&mut self, authority: AuthorityId) {
114        if !self.reputations.contains_key(&authority) {
115            self.reputations.insert(
116                authority.clone(),
117                ReputationEntry::new(authority, self.config.initial_reputation),
118            );
119        }
120    }
121
122    /// Get authority reputation
123    pub fn get_reputation(&self, authority: &AuthorityId) -> Result<f64> {
124        self.reputations
125            .get(authority)
126            .map(|e| e.reputation)
127            .ok_or_else(|| Error::AuthorityNotFound {
128                authority: authority.0.clone(),
129            })
130    }
131
132    /// Get reputation entry
133    pub fn get_entry(&self, authority: &AuthorityId) -> Result<&ReputationEntry> {
134        self.reputations
135            .get(authority)
136            .ok_or_else(|| Error::AuthorityNotFound {
137                authority: authority.0.clone(),
138            })
139    }
140
141    /// Record correct vote
142    pub fn record_correct_vote(&mut self, authority: &AuthorityId) -> Result<()> {
143        let entry = self.reputations.get_mut(authority).ok_or_else(|| {
144            Error::AuthorityNotFound {
145                authority: authority.0.clone(),
146            }
147        })?;
148
149        entry.correct_votes += 1;
150        entry.total_rounds += 1;
151        entry.reputation =
152            (entry.reputation + self.config.correct_vote_reward).min(self.config.max_reputation);
153
154        Ok(())
155    }
156
157    /// Record incorrect vote
158    pub fn record_incorrect_vote(&mut self, authority: &AuthorityId) -> Result<()> {
159        let entry = self.reputations.get_mut(authority).ok_or_else(|| {
160            Error::AuthorityNotFound {
161                authority: authority.0.clone(),
162            }
163        })?;
164
165        entry.incorrect_votes += 1;
166        entry.total_rounds += 1;
167        entry.reputation =
168            (entry.reputation - self.config.incorrect_vote_penalty).max(self.config.min_reputation);
169
170        Ok(())
171    }
172
173    /// Record timeout
174    pub fn record_timeout(&mut self, authority: &AuthorityId) -> Result<()> {
175        let entry = self.reputations.get_mut(authority).ok_or_else(|| {
176            Error::AuthorityNotFound {
177                authority: authority.0.clone(),
178            }
179        })?;
180
181        entry.timeouts += 1;
182        entry.total_rounds += 1;
183        entry.reputation =
184            (entry.reputation - self.config.timeout_penalty).max(self.config.min_reputation);
185
186        Ok(())
187    }
188
189    /// Record Byzantine fault
190    pub fn record_byzantine_fault(&mut self, authority: &AuthorityId) -> Result<()> {
191        let entry = self.reputations.get_mut(authority).ok_or_else(|| {
192            Error::AuthorityNotFound {
193                authority: authority.0.clone(),
194            }
195        })?;
196
197        entry.byzantine_faults += 1;
198        entry.reputation =
199            (entry.reputation - self.config.byzantine_penalty).max(self.config.min_reputation);
200
201        Ok(())
202    }
203
204    /// Apply reputation decay for all authorities
205    pub fn apply_decay(&mut self) {
206        for entry in self.reputations.values_mut() {
207            let decay = entry.reputation * self.config.decay_rate;
208            entry.reputation = (entry.reputation - decay).max(self.config.min_reputation);
209        }
210    }
211
212    /// Get all trustworthy authorities
213    pub fn get_trustworthy_authorities(&self) -> Vec<AuthorityId> {
214        self.reputations
215            .values()
216            .filter(|e| e.is_trustworthy(self.config.min_reputation))
217            .map(|e| e.authority.clone())
218            .collect()
219    }
220
221    /// Calculate weighted reputation for authority
222    pub fn calculate_weighted_reputation(&self, authority: &AuthorityId) -> Result<f64> {
223        let entry = self.get_entry(authority)?;
224
225        // Combine reputation with accuracy and reliability
226        let base_reputation = entry.reputation;
227        let accuracy_weight = entry.accuracy();
228        let reliability_weight = entry.reliability();
229
230        Ok(base_reputation * accuracy_weight * reliability_weight)
231    }
232
233    /// Get authorities ranked by reputation
234    pub fn get_ranked_authorities(&self) -> Vec<(AuthorityId, f64)> {
235        let mut ranked: Vec<_> = self
236            .reputations
237            .values()
238            .map(|e| (e.authority.clone(), e.reputation))
239            .collect();
240
241        ranked.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
242        ranked
243    }
244
245    /// Get statistics
246    pub fn get_statistics(&self) -> ReputationStatistics {
247        let reputations: Vec<f64> = self.reputations.values().map(|e| e.reputation).collect();
248
249        let avg_reputation = if reputations.is_empty() {
250            0.0
251        } else {
252            reputations.iter().sum::<f64>() / reputations.len() as f64
253        };
254
255        let total_byzantine = self
256            .reputations
257            .values()
258            .filter(|e| e.byzantine_faults > 0)
259            .count();
260
261        let total_trustworthy = self
262            .reputations
263            .values()
264            .filter(|e| e.is_trustworthy(self.config.min_reputation))
265            .count();
266
267        ReputationStatistics {
268            total_authorities: self.reputations.len(),
269            avg_reputation,
270            min_reputation: reputations.iter().cloned().fold(f64::INFINITY, f64::min),
271            max_reputation: reputations.iter().cloned().fold(f64::NEG_INFINITY, f64::max),
272            byzantine_count: total_byzantine,
273            trustworthy_count: total_trustworthy,
274        }
275    }
276}
277
278/// Reputation statistics
279#[derive(Debug, Clone, Serialize, Deserialize)]
280pub struct ReputationStatistics {
281    pub total_authorities: usize,
282    pub avg_reputation: f64,
283    pub min_reputation: f64,
284    pub max_reputation: f64,
285    pub byzantine_count: usize,
286    pub trustworthy_count: usize,
287}
288
289#[cfg(test)]
290mod tests {
291    use super::*;
292
293    #[test]
294    fn test_reputation_entry() {
295        let entry = ReputationEntry::new(AuthorityId::from("auth-1"), 1.0);
296        assert_eq!(entry.reputation, 1.0);
297        assert_eq!(entry.accuracy(), 1.0);
298        assert_eq!(entry.reliability(), 1.0);
299    }
300
301    #[test]
302    fn test_accuracy_calculation() {
303        let mut entry = ReputationEntry::new(AuthorityId::from("auth-1"), 1.0);
304        entry.correct_votes = 8;
305        entry.incorrect_votes = 2;
306        assert_eq!(entry.accuracy(), 0.8);
307    }
308
309    #[test]
310    fn test_reliability_calculation() {
311        let mut entry = ReputationEntry::new(AuthorityId::from("auth-1"), 1.0);
312        entry.total_rounds = 10;
313        entry.timeouts = 2;
314        assert_eq!(entry.reliability(), 0.8);
315    }
316
317    #[test]
318    fn test_register_authority() {
319        let mut system = ReputationSystem::new(ReputationConfig::default());
320        let auth = AuthorityId::from("auth-1");
321
322        system.register_authority(auth.clone());
323        assert_eq!(system.get_reputation(&auth).unwrap(), 1.0);
324    }
325
326    #[test]
327    fn test_record_correct_vote() {
328        let mut system = ReputationSystem::new(ReputationConfig::default());
329        let auth = AuthorityId::from("auth-1");
330        system.register_authority(auth.clone());
331
332        system.record_correct_vote(&auth).unwrap();
333        assert!(system.get_reputation(&auth).unwrap() > 1.0);
334
335        let entry = system.get_entry(&auth).unwrap();
336        assert_eq!(entry.correct_votes, 1);
337        assert_eq!(entry.total_rounds, 1);
338    }
339
340    #[test]
341    fn test_record_incorrect_vote() {
342        let mut system = ReputationSystem::new(ReputationConfig::default());
343        let auth = AuthorityId::from("auth-1");
344        system.register_authority(auth.clone());
345
346        system.record_incorrect_vote(&auth).unwrap();
347        assert!(system.get_reputation(&auth).unwrap() < 1.0);
348
349        let entry = system.get_entry(&auth).unwrap();
350        assert_eq!(entry.incorrect_votes, 1);
351    }
352
353    #[test]
354    fn test_record_timeout() {
355        let mut system = ReputationSystem::new(ReputationConfig::default());
356        let auth = AuthorityId::from("auth-1");
357        system.register_authority(auth.clone());
358
359        system.record_timeout(&auth).unwrap();
360        assert!(system.get_reputation(&auth).unwrap() < 1.0);
361
362        let entry = system.get_entry(&auth).unwrap();
363        assert_eq!(entry.timeouts, 1);
364    }
365
366    #[test]
367    fn test_record_byzantine_fault() {
368        let mut system = ReputationSystem::new(ReputationConfig::default());
369        let auth = AuthorityId::from("auth-1");
370        system.register_authority(auth.clone());
371
372        let initial = system.get_reputation(&auth).unwrap();
373        system.record_byzantine_fault(&auth).unwrap();
374
375        assert!(system.get_reputation(&auth).unwrap() < initial);
376        let entry = system.get_entry(&auth).unwrap();
377        assert_eq!(entry.byzantine_faults, 1);
378        assert!(!entry.is_trustworthy(0.1));
379    }
380
381    #[test]
382    fn test_reputation_decay() {
383        let config = ReputationConfig {
384            decay_rate: 0.1,
385            ..Default::default()
386        };
387        let mut system = ReputationSystem::new(config);
388        let auth = AuthorityId::from("auth-1");
389        system.register_authority(auth.clone());
390
391        let initial = system.get_reputation(&auth).unwrap();
392        system.apply_decay();
393
394        assert!(system.get_reputation(&auth).unwrap() < initial);
395    }
396
397    #[test]
398    fn test_reputation_bounds() {
399        let mut system = ReputationSystem::new(ReputationConfig::default());
400        let auth = AuthorityId::from("auth-1");
401        system.register_authority(auth.clone());
402
403        // Test upper bound
404        for _ in 0..100 {
405            system.record_correct_vote(&auth).unwrap();
406        }
407        assert!(system.get_reputation(&auth).unwrap() <= 2.0);
408
409        // Test lower bound
410        for _ in 0..100 {
411            system.record_incorrect_vote(&auth).unwrap();
412        }
413        assert!(system.get_reputation(&auth).unwrap() >= 0.1);
414    }
415
416    #[test]
417    fn test_weighted_reputation() {
418        let mut system = ReputationSystem::new(ReputationConfig::default());
419        let auth = AuthorityId::from("auth-1");
420        system.register_authority(auth.clone());
421
422        system.record_correct_vote(&auth).unwrap();
423        system.record_correct_vote(&auth).unwrap();
424        system.record_timeout(&auth).unwrap();
425
426        let weighted = system.calculate_weighted_reputation(&auth).unwrap();
427        let base = system.get_reputation(&auth).unwrap();
428
429        // Weighted should be lower due to timeout affecting reliability
430        assert!(weighted < base);
431    }
432
433    #[test]
434    fn test_ranked_authorities() {
435        let mut system = ReputationSystem::new(ReputationConfig::default());
436
437        for i in 0..3 {
438            let auth = AuthorityId::from(format!("auth-{}", i));
439            system.register_authority(auth.clone());
440
441            for _ in 0..i {
442                system.record_correct_vote(&auth).unwrap();
443            }
444        }
445
446        let ranked = system.get_ranked_authorities();
447        assert_eq!(ranked.len(), 3);
448
449        // Should be sorted descending
450        assert!(ranked[0].1 >= ranked[1].1);
451        assert!(ranked[1].1 >= ranked[2].1);
452    }
453
454    #[test]
455    fn test_statistics() {
456        let mut system = ReputationSystem::new(ReputationConfig::default());
457
458        for i in 0..5 {
459            let auth = AuthorityId::from(format!("auth-{}", i));
460            system.register_authority(auth.clone());
461        }
462
463        system.record_byzantine_fault(&AuthorityId::from("auth-0")).unwrap();
464
465        let stats = system.get_statistics();
466        assert_eq!(stats.total_authorities, 5);
467        assert_eq!(stats.byzantine_count, 1);
468        assert_eq!(stats.trustworthy_count, 4);
469    }
470}