de_mls/core/peer_scoring/
helpers.rs1use std::collections::HashSet;
5
6use prost::Message;
7
8use crate::protos::de_mls::messages::v1::{
9 ConversationUpdateRequest, ViolationEvidence, conversation_update_request::Payload,
10};
11
12use crate::core::{ScoreEvent, ScoreOp, ScoringMemberDiff};
13
14pub fn scoring_member_diff(scored: &[Vec<u8>], mls_members: &[Vec<u8>]) -> ScoringMemberDiff {
17 let scored_set: HashSet<&[u8]> = scored.iter().map(Vec::as_slice).collect();
18 let mls_set: HashSet<&[u8]> = mls_members.iter().map(Vec::as_slice).collect();
19
20 let to_add = mls_members
21 .iter()
22 .filter(|m| !scored_set.contains(m.as_slice()))
23 .cloned()
24 .collect();
25 let to_remove = scored
26 .iter()
27 .filter(|m| !mls_set.contains(m.as_slice()))
28 .cloned()
29 .collect();
30 ScoringMemberDiff { to_add, to_remove }
31}
32
33pub fn emergency_score_ops(payload: &[u8], approved: bool) -> Vec<ScoreOp> {
40 let Ok(req) = ConversationUpdateRequest::decode(payload) else {
41 return Vec::new();
42 };
43 let Some(Payload::EmergencyCriteria(ec)) = req.payload else {
44 return Vec::new();
45 };
46 let Some(evidence) = ec.evidence else {
47 return Vec::new();
48 };
49
50 if approved {
51 let mut ops = vec![creator_reward(&evidence)];
52 if let Some(target_op) = evidence.target_score_op() {
53 ops.push(target_op);
54 }
55 ops
56 } else {
57 vec![creator_penalty(&evidence)]
58 }
59}
60
61fn creator_reward(ev: &ViolationEvidence) -> ScoreOp {
62 ScoreOp {
63 member_id: ev.creator_member_id.clone(),
64 event: ScoreEvent::EmergencyYesCreator,
65 }
66}
67
68fn creator_penalty(ev: &ViolationEvidence) -> ScoreOp {
69 ScoreOp {
70 member_id: ev.creator_member_id.clone(),
71 event: ScoreEvent::EmergencyNoCreator,
72 }
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78 use crate::protos::de_mls::messages::v1::{EmergencyCriteriaProposal, ViolationType};
79
80 fn ecp_payload(violation_type: i32, target: Vec<u8>, creator: Vec<u8>) -> Vec<u8> {
81 let evidence = ViolationEvidence {
82 violation_type,
83 target_member_id: target,
84 evidence_payload: Vec::new(),
85 epoch: 0,
86 creator_member_id: creator,
87 };
88 let req = ConversationUpdateRequest {
89 payload: Some(Payload::EmergencyCriteria(EmergencyCriteriaProposal {
90 evidence: Some(evidence),
91 })),
92 };
93 req.encode_to_vec()
94 }
95
96 #[test]
98 fn approved_broken_commit_emits_reward_and_target_penalty() {
99 let payload = ecp_payload(ViolationType::BrokenCommit as i32, vec![0xAA], vec![0xBB]);
100 let ops = emergency_score_ops(&payload, true);
101 assert_eq!(ops.len(), 2);
102 assert_eq!(ops[0].event, ScoreEvent::EmergencyYesCreator);
103 assert_eq!(ops[0].member_id, vec![0xBB]);
104 assert_eq!(ops[1].event, ScoreEvent::BrokenCommit);
105 assert_eq!(ops[1].member_id, vec![0xAA]);
106 }
107
108 #[test]
110 fn approved_deadlock_emits_reward_only() {
111 let payload = ecp_payload(ViolationType::Deadlock as i32, Vec::new(), vec![0xBB]);
112 let ops = emergency_score_ops(&payload, true);
113 assert_eq!(ops.len(), 1);
114 assert_eq!(ops[0].event, ScoreEvent::EmergencyYesCreator);
115 }
116
117 #[test]
120 fn approved_score_below_threshold_emits_reward_only() {
121 let payload = ecp_payload(
122 ViolationType::ScoreBelowThreshold as i32,
123 vec![0xAA],
124 vec![0xBB],
125 );
126 let ops = emergency_score_ops(&payload, true);
127 assert_eq!(ops.len(), 1);
128 assert_eq!(ops[0].event, ScoreEvent::EmergencyYesCreator);
129 }
130
131 #[test]
134 fn approved_unspecified_emits_reward_only() {
135 let payload = ecp_payload(0, vec![0xAA], vec![0xBB]);
136 let ops = emergency_score_ops(&payload, true);
137 assert_eq!(ops.len(), 1);
138 assert_eq!(ops[0].event, ScoreEvent::EmergencyYesCreator);
139 }
140
141 #[test]
143 fn rejected_emits_creator_penalty() {
144 for vt in [
145 ViolationType::BrokenCommit,
146 ViolationType::Deadlock,
147 ViolationType::ScoreBelowThreshold,
148 ] {
149 let payload = ecp_payload(vt as i32, vec![0xAA], vec![0xBB]);
150 let ops = emergency_score_ops(&payload, false);
151 assert_eq!(ops.len(), 1);
152 assert_eq!(ops[0].event, ScoreEvent::EmergencyNoCreator);
153 assert_eq!(ops[0].member_id, vec![0xBB]);
154 }
155 }
156}