/ src / core / peer_scoring.rs
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  }