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