peer_scoring.rs
1 //! Protocol vocabulary for peer scoring. 2 //! 3 //! This module defines the types and traits that core functions use to communicate 4 //! scoring-relevant events. The actual scoring service and default implementations 5 //! live in the `app::peer_scoring` module (re-exported via [`crate::app`]). 6 //! 7 //! # Data flow 8 //! 9 //! ```text 10 //! core functions ──► Vec<ScoreOp> ──► app layer ──► PeerScoringService 11 //! ``` 12 13 // ── Score events ──────────────────────────────────────────────────── 14 15 /// A scoreable event in the protocol. 16 /// 17 /// This is a finite, predetermined catalog. Members are scored only on events 18 /// from this list — no free-form penalties. 19 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 20 pub enum ScoreEvent { 21 /// Steward committed proposals that don't match what was voted on. 22 BrokenCommit, 23 /// MLS proposal payload was malformed or didn't match the voted action. 24 BrokenMlsProposal, 25 /// Steward failed to commit within the threshold duration. 26 CensorshipInactivity, 27 /// Steward successfully committed a valid batch. 28 SuccessfulCommit, 29 /// Emergency criteria accepted — penalty to the accused target. 30 EmergencyYesTarget, 31 /// Emergency criteria accepted — reward to the proposal creator. 32 EmergencyYesCreator, 33 /// Emergency criteria rejected — penalty to the proposal creator (false accusation). 34 EmergencyNoCreator, 35 /// Commit referenced proposals that were not yet finalized by consensus. 36 NonFinalizedProposalCommit, 37 } 38 39 /// A score operation produced by core logic. 40 /// 41 /// Core functions return these to describe scoring-relevant events. The application 42 /// layer feeds them into a [`PeerScoringService`](crate::app::PeerScoringService). 43 #[derive(Debug, Clone, PartialEq, Eq)] 44 pub struct ScoreOp { 45 pub member_id: Vec<u8>, 46 pub event: ScoreEvent, 47 } 48 49 impl ScoreOp { 50 pub fn new(member_id: Vec<u8>, event: ScoreEvent) -> Self { 51 Self { member_id, event } 52 } 53 } 54 55 // ── Scoring configuration ─────────────────────────────────────────── 56 57 /// Maps each [`ScoreEvent`] to a score delta. 58 /// 59 /// Implement this trait to customize score values. A default implementation 60 /// ([`FixedScoringProvider`](crate::app::FixedScoringProvider)) is provided in the app layer. 61 pub trait ScoringProvider { 62 /// Returns the score change for the given event. 63 /// 64 /// Positive values reward, negative values penalize. 65 fn score_delta(&self, event: ScoreEvent) -> i64; 66 } 67 68 /// Configuration for a [`PeerScoringService`](crate::app::PeerScoringService). 69 #[derive(Debug, Clone)] 70 pub struct ScoringConfig { 71 /// Score assigned to newly added members. 72 pub default_score: i64, 73 /// Members at or below this score are candidates for removal. 74 pub removal_threshold: i64, 75 } 76 77 // ── Storage trait ─────────────────────────────────────────────────── 78 79 /// Persistence layer for peer scores. 80 /// 81 /// Scores are scoped by group — each `(group_id, member_id)` pair has its own 82 /// score. A default in-memory implementation 83 /// ([`InMemoryPeerScoreStorage`](crate::app::InMemoryPeerScoreStorage)) is 84 /// provided in the app layer. Implement this trait to back scores with a 85 /// database or other durable store. 86 pub trait PeerScoreStorage { 87 fn get(&self, group_id: &str, member_id: &[u8]) -> Option<i64>; 88 fn set(&mut self, group_id: &str, member_id: &[u8], score: i64); 89 fn remove(&mut self, group_id: &str, member_id: &[u8]); 90 fn all_scores(&self, group_id: &str) -> Vec<(Vec<u8>, i64)>; 91 } 92 93 // ── Result type for consensus ─────────────────────────────────────── 94 95 /// Result of applying a consensus outcome, including any score events 96 /// that the application layer should feed into a [`PeerScoringService`](crate::app::PeerScoringService). 97 #[derive(Debug, Clone, Default)] 98 pub struct ConsensusApplyResult { 99 pub score_ops: Vec<ScoreOp>, 100 } 101 102 impl ConsensusApplyResult { 103 pub fn empty() -> Self { 104 Self::default() 105 } 106 107 pub fn with_ops(score_ops: Vec<ScoreOp>) -> Self { 108 Self { score_ops } 109 } 110 }