sui_core/
scoring_decision.rs1use std::{collections::HashMap, sync::Arc};
4
5use arc_swap::ArcSwap;
6use consensus_config::Committee as ConsensusCommittee;
7use sui_types::{
8 base_types::AuthorityName, committee::Committee, messages_consensus::AuthorityIndex,
9};
10use tracing::debug;
11
12use crate::authority::AuthorityMetrics;
13
14pub(crate) fn update_low_scoring_authorities(
22 low_scoring_authorities: Arc<ArcSwap<HashMap<AuthorityName, u64>>>,
23 sui_committee: &Committee,
24 consensus_committee: &ConsensusCommittee,
25 reputation_score_sorted_desc: Option<Vec<(AuthorityIndex, u64)>>,
26 metrics: &Arc<AuthorityMetrics>,
27 consensus_bad_nodes_stake_threshold: u64,
28) {
29 assert!(
30 (0..=33).contains(&consensus_bad_nodes_stake_threshold),
31 "The bad_nodes_stake_threshold should be in range [0 - 33], out of bounds parameter detected {}",
32 consensus_bad_nodes_stake_threshold
33 );
34
35 let Some(reputation_scores) = reputation_score_sorted_desc else {
36 return;
37 };
38
39 let scores_per_authority_order_asc: Vec<_> = reputation_scores
42 .into_iter()
43 .rev() .collect();
45
46 let mut final_low_scoring_map = HashMap::new();
47 let mut total_stake = 0;
48 for (index, score) in scores_per_authority_order_asc {
49 let authority_name = sui_committee.authority_by_index(index).unwrap();
50 let authority_index = consensus_committee
51 .to_authority_index(index as usize)
52 .unwrap();
53 let consensus_authority = consensus_committee.authority(authority_index);
54 let hostname = &consensus_authority.hostname;
55 let stake = consensus_authority.stake;
56 total_stake += stake;
57
58 let included = if total_stake
59 <= consensus_bad_nodes_stake_threshold * consensus_committee.total_stake() / 100
60 {
61 final_low_scoring_map.insert(*authority_name, score);
62 true
63 } else {
64 false
65 };
66
67 if !hostname.is_empty() {
68 debug!(
69 "authority {} has score {}, is low scoring: {}",
70 hostname, score, included
71 );
72
73 metrics
74 .consensus_handler_scores
75 .with_label_values(&[hostname])
76 .set(score as i64);
77 }
78 }
79 metrics
81 .consensus_handler_num_low_scoring_authorities
82 .set(final_low_scoring_map.len() as i64);
83 low_scoring_authorities.swap(Arc::new(final_low_scoring_map));
84}
85
86#[cfg(test)]
87mod tests {
88 #![allow(clippy::mutable_key_type)]
89 use std::{collections::HashMap, sync::Arc};
90
91 use arc_swap::ArcSwap;
92 use consensus_config::{Committee as ConsensusCommittee, local_committee_and_keys};
93 use prometheus::Registry;
94 use sui_types::{committee::Committee, crypto::AuthorityPublicKeyBytes};
95
96 use crate::{authority::AuthorityMetrics, scoring_decision::update_low_scoring_authorities};
97
98 #[test]
99 #[cfg_attr(msim, ignore)]
100 pub fn test_update_low_scoring_authorities() {
101 let (sui_committee, consensus_committee) = generate_committees(8);
104
105 let low_scoring = Arc::new(ArcSwap::from_pointee(HashMap::new()));
106 let metrics = Arc::new(AuthorityMetrics::new(&Registry::new()));
107
108 let authorities_by_score_desc = vec![
110 (1, 390_u64),
111 (0, 350_u64),
112 (6, 340_u64),
113 (7, 310_u64),
114 (5, 300_u64),
115 (3, 50_u64),
116 (2, 50_u64),
117 (4, 0_u64), ];
119
120 let consensus_bad_nodes_stake_threshold = 33; update_low_scoring_authorities(
124 low_scoring.clone(),
125 &sui_committee,
126 &consensus_committee,
127 Some(authorities_by_score_desc.clone()),
128 &metrics,
129 consensus_bad_nodes_stake_threshold,
130 );
131
132 assert_eq!(low_scoring.load().len(), 2);
134 assert_eq!(
135 *low_scoring
136 .load()
137 .get(sui_committee.authority_by_index(2).unwrap())
139 .unwrap(),
140 50
141 );
142 assert_eq!(
143 *low_scoring
144 .load()
145 .get(sui_committee.authority_by_index(4).unwrap())
147 .unwrap(),
148 0
149 );
150
151 let consensus_bad_nodes_stake_threshold = 20; update_low_scoring_authorities(
154 low_scoring.clone(),
155 &sui_committee,
156 &consensus_committee,
157 Some(authorities_by_score_desc.clone()),
158 &metrics,
159 consensus_bad_nodes_stake_threshold,
160 );
161
162 assert_eq!(low_scoring.load().len(), 1);
164 assert_eq!(
165 *low_scoring
166 .load()
167 .get(sui_committee.authority_by_index(4).unwrap())
168 .unwrap(),
169 0
170 );
171 }
172
173 fn generate_committees(committee_size: usize) -> (Committee, ConsensusCommittee) {
175 let (consensus_committee, _) = local_committee_and_keys(0, vec![1; committee_size]);
176
177 let public_keys = consensus_committee
178 .authorities()
179 .map(|(_i, authority)| authority.authority_key.inner())
180 .collect::<Vec<_>>();
181 let sui_authorities = public_keys
182 .iter()
183 .map(|key| (AuthorityPublicKeyBytes::from(*key), 1))
184 .collect::<Vec<_>>();
185 let sui_committee = Committee::new_for_testing_with_normalized_voting_power(
186 0,
187 sui_authorities.iter().cloned().collect(),
188 );
189
190 (sui_committee, consensus_committee)
191 }
192}