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