Skip to content

Commit 85048c6

Browse files
cli: account for rpc nodes when considering feature set adoption (solana-labs#20774)
(cherry picked from commit 5794bba) Co-authored-by: Trent Nelson <trent@solana.com>
1 parent 440ccd1 commit 85048c6

File tree

1 file changed

+113
-50
lines changed

1 file changed

+113
-50
lines changed

cli/src/feature.rs

Lines changed: 113 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -222,43 +222,63 @@ pub fn process_feature_subcommand(
222222
}
223223
}
224224

225-
fn active_stake_by_feature_set(rpc_client: &RpcClient) -> Result<HashMap<u32, f64>, ClientError> {
225+
fn feature_set_stats(rpc_client: &RpcClient) -> Result<HashMap<u32, (f64, f32)>, ClientError> {
226226
// Validator identity -> feature set
227-
let feature_set_map = rpc_client
227+
let feature_sets = rpc_client
228228
.get_cluster_nodes()?
229229
.into_iter()
230-
.map(|contact_info| (contact_info.pubkey, contact_info.feature_set))
231-
.collect::<HashMap<_, _>>();
230+
.map(|contact_info| {
231+
(
232+
contact_info.pubkey,
233+
contact_info.feature_set,
234+
contact_info.rpc.is_some(),
235+
)
236+
})
237+
.collect::<Vec<_>>();
232238

233239
let vote_accounts = rpc_client.get_vote_accounts()?;
234240

235-
let total_active_stake: u64 = vote_accounts
236-
.current
241+
let mut total_active_stake: u64 = vote_accounts
242+
.delinquent
237243
.iter()
238-
.chain(vote_accounts.delinquent.iter())
239244
.map(|vote_account| vote_account.activated_stake)
240245
.sum();
241246

242-
// Sum all active stake by feature set
243-
let mut active_stake_by_feature_set: HashMap<u32, u64> = HashMap::new();
244-
for vote_account in vote_accounts.current {
245-
if let Some(Some(feature_set)) = feature_set_map.get(&vote_account.node_pubkey) {
246-
*active_stake_by_feature_set.entry(*feature_set).or_default() +=
247-
vote_account.activated_stake;
248-
} else {
249-
*active_stake_by_feature_set
250-
.entry(0 /* "unknown" */)
251-
.or_default() += vote_account.activated_stake;
247+
let vote_stakes = vote_accounts
248+
.current
249+
.into_iter()
250+
.map(|vote_account| {
251+
total_active_stake += vote_account.activated_stake;
252+
(vote_account.node_pubkey, vote_account.activated_stake)
253+
})
254+
.collect::<HashMap<_, _>>();
255+
256+
let mut feature_set_stats: HashMap<u32, (u64, u32)> = HashMap::new();
257+
let mut total_rpc_nodes = 0;
258+
for (node_id, feature_set, is_rpc) in feature_sets {
259+
let feature_set = feature_set.unwrap_or(0);
260+
let feature_set_entry = feature_set_stats.entry(feature_set).or_default();
261+
262+
if let Some(vote_stake) = vote_stakes.get(&node_id) {
263+
feature_set_entry.0 += *vote_stake;
264+
}
265+
266+
if is_rpc {
267+
feature_set_entry.1 += 1;
268+
total_rpc_nodes += 1;
252269
}
253270
}
254271

255-
Ok(active_stake_by_feature_set
272+
Ok(feature_set_stats
256273
.into_iter()
257-
.map(|(feature_set, active_stake)| {
258-
(
259-
feature_set,
260-
active_stake as f64 * 100. / total_active_stake as f64,
261-
)
274+
.filter_map(|(feature_set, (active_stake, is_rpc))| {
275+
let active_stake = active_stake as f64 * 100. / total_active_stake as f64;
276+
let is_rpc = is_rpc as f32 * 100. / total_rpc_nodes as f32;
277+
if active_stake >= 0.001 || is_rpc >= 0.001 {
278+
Some((feature_set, (active_stake, is_rpc)))
279+
} else {
280+
None
281+
}
262282
})
263283
.collect())
264284
}
@@ -267,50 +287,93 @@ fn active_stake_by_feature_set(rpc_client: &RpcClient) -> Result<HashMap<u32, f6
267287
fn feature_activation_allowed(rpc_client: &RpcClient, quiet: bool) -> Result<bool, ClientError> {
268288
let my_feature_set = solana_version::Version::default().feature_set;
269289

270-
let active_stake_by_feature_set = active_stake_by_feature_set(rpc_client)?;
290+
let feature_set_stats = feature_set_stats(rpc_client)?;
271291

272-
let feature_activation_allowed = active_stake_by_feature_set
292+
let (stake_allowed, rpc_allowed) = feature_set_stats
273293
.get(&my_feature_set)
274-
.map(|percentage| *percentage >= 95.)
275-
.unwrap_or(false);
294+
.map(|(stake_percent, rpc_percent)| (*stake_percent >= 95., *rpc_percent >= 95.))
295+
.unwrap_or((false, false));
276296

277-
if !feature_activation_allowed && !quiet {
278-
if active_stake_by_feature_set.get(&my_feature_set).is_none() {
297+
if !stake_allowed && !rpc_allowed && !quiet {
298+
if feature_set_stats.get(&my_feature_set).is_none() {
279299
println!(
280300
"{}",
281301
style("To activate features the tool and cluster feature sets must match, select a tool version that matches the cluster")
282302
.bold());
283303
} else {
284-
println!(
285-
"{}",
286-
style("To activate features the stake must be >= 95%").bold()
287-
);
304+
if !stake_allowed {
305+
print!(
306+
"\n{}",
307+
style("To activate features the stake must be >= 95%")
308+
.bold()
309+
.red()
310+
);
311+
}
312+
if !rpc_allowed {
313+
print!(
314+
"\n{}",
315+
style("To activate features the RPC nodes must be >= 95%")
316+
.bold()
317+
.red()
318+
);
319+
}
288320
}
289321
println!(
290-
"{}",
322+
"\n\n{}",
291323
style(format!("Tool Feature Set: {}", my_feature_set)).bold()
292324
);
293-
println!("{}", style("Cluster Feature Sets and Stakes:").bold());
294-
for (feature_set, percentage) in active_stake_by_feature_set.iter() {
295-
if *feature_set == 0 {
296-
println!(" unknown - {:.2}%", percentage);
325+
let feature_set_title = "Feature Set";
326+
let stake_percent_title = "Stake";
327+
let rpc_percent_title = "RPC";
328+
let mut stats_output = Vec::new();
329+
let mut max_feature_set_len = feature_set_title.len();
330+
let mut max_stake_percent_len = stake_percent_title.len();
331+
let mut max_rpc_percent_len = rpc_percent_title.len();
332+
for (feature_set, (stake_percent, rpc_percent)) in feature_set_stats.iter() {
333+
let me = *feature_set == my_feature_set;
334+
let feature_set = if *feature_set == 0 {
335+
"unknown".to_string()
297336
} else {
298-
println!(
299-
" {:<10} - {:.2}% {}",
300-
feature_set,
301-
percentage,
302-
if *feature_set == my_feature_set {
303-
" <-- me"
304-
} else {
305-
""
306-
}
307-
);
308-
}
337+
feature_set.to_string()
338+
};
339+
let stake_percent = format!("{:.2}%", stake_percent);
340+
let rpc_percent = format!("{:.2}%", rpc_percent);
341+
342+
max_feature_set_len = max_feature_set_len.max(feature_set.len());
343+
max_stake_percent_len = max_stake_percent_len.max(stake_percent.len());
344+
max_rpc_percent_len = max_rpc_percent_len.max(rpc_percent.len());
345+
346+
stats_output.push((feature_set, stake_percent, rpc_percent, me));
347+
}
348+
println!(
349+
"{}",
350+
style(format!(
351+
"{1:<0$} {3:<2$} {5:<4$}",
352+
max_feature_set_len,
353+
feature_set_title,
354+
max_stake_percent_len,
355+
stake_percent_title,
356+
max_rpc_percent_len,
357+
rpc_percent_title,
358+
))
359+
.bold(),
360+
);
361+
for (feature_set, stake_percent, rpc_percent, me) in stats_output {
362+
println!(
363+
"{1:>0$} {3:>2$} {5:>4$} {6}",
364+
max_feature_set_len,
365+
feature_set,
366+
max_stake_percent_len,
367+
stake_percent,
368+
max_rpc_percent_len,
369+
rpc_percent,
370+
if me { "<-- me" } else { "" },
371+
);
309372
}
310373
println!();
311374
}
312375

313-
Ok(feature_activation_allowed)
376+
Ok(stake_allowed && rpc_allowed)
314377
}
315378

316379
fn status_from_account(account: Account) -> Option<CliFeatureStatus> {

0 commit comments

Comments
 (0)