getVoteAccounts now excludes listing inactive unstaked accounts as delinquent
This keeps abandoned vote accounts out of the `solana show-validators` output
This commit is contained in:
parent
f05860672c
commit
d01ea20273
125
core/src/rpc.rs
125
core/src/rpc.rs
|
@ -256,10 +256,6 @@ impl JsonRpcRequestProcessor {
|
||||||
last_vote,
|
last_vote,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.filter(|vote_account_info| {
|
|
||||||
// Remove vote accounts with no delegated stake that have never voted
|
|
||||||
vote_account_info.last_vote != 0 || vote_account_info.activated_stake != 0
|
|
||||||
})
|
|
||||||
.partition(|vote_account_info| {
|
.partition(|vote_account_info| {
|
||||||
if bank.slot() >= MAX_LOCKOUT_HISTORY as u64 {
|
if bank.slot() >= MAX_LOCKOUT_HISTORY as u64 {
|
||||||
vote_account_info.last_vote > bank.slot() - MAX_LOCKOUT_HISTORY as u64
|
vote_account_info.last_vote > bank.slot() - MAX_LOCKOUT_HISTORY as u64
|
||||||
|
@ -267,9 +263,15 @@ impl JsonRpcRequestProcessor {
|
||||||
vote_account_info.last_vote > 0
|
vote_account_info.last_vote > 0
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let delinquent_staked_vote_accounts = delinquent_vote_accounts
|
||||||
|
.into_iter()
|
||||||
|
.filter(|vote_account_info| vote_account_info.activated_stake > 0)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
Ok(RpcVoteAccountStatus {
|
Ok(RpcVoteAccountStatus {
|
||||||
current: current_vote_accounts,
|
current: current_vote_accounts,
|
||||||
delinquent: delinquent_vote_accounts,
|
delinquent: delinquent_staked_vote_accounts,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1926,23 +1928,22 @@ pub mod tests {
|
||||||
|
|
||||||
assert_eq!(bank.vote_accounts().len(), 1);
|
assert_eq!(bank.vote_accounts().len(), 1);
|
||||||
|
|
||||||
// Create a second vote account that has no stake. It should not be included in the
|
// Create a vote account with no stake.
|
||||||
// getVoteAccounts response
|
let alice_vote_keypair = Keypair::new();
|
||||||
let vote_keypair = Keypair::new();
|
|
||||||
let instructions = vote_instruction::create_account(
|
let instructions = vote_instruction::create_account(
|
||||||
&alice.pubkey(),
|
&alice.pubkey(),
|
||||||
&vote_keypair.pubkey(),
|
&alice_vote_keypair.pubkey(),
|
||||||
&VoteInit {
|
&VoteInit {
|
||||||
node_pubkey: alice.pubkey(),
|
node_pubkey: alice.pubkey(),
|
||||||
authorized_voter: vote_keypair.pubkey(),
|
authorized_voter: alice_vote_keypair.pubkey(),
|
||||||
authorized_withdrawer: vote_keypair.pubkey(),
|
authorized_withdrawer: alice_vote_keypair.pubkey(),
|
||||||
commission: 0,
|
commission: 0,
|
||||||
},
|
},
|
||||||
bank.get_minimum_balance_for_rent_exemption(VoteState::size_of()),
|
bank.get_minimum_balance_for_rent_exemption(VoteState::size_of()),
|
||||||
);
|
);
|
||||||
|
|
||||||
let transaction = Transaction::new_signed_instructions(
|
let transaction = Transaction::new_signed_instructions(
|
||||||
&[&alice, &vote_keypair],
|
&[&alice, &alice_vote_keypair],
|
||||||
instructions,
|
instructions,
|
||||||
bank.last_blockhash(),
|
bank.last_blockhash(),
|
||||||
);
|
);
|
||||||
|
@ -1950,19 +1951,49 @@ pub mod tests {
|
||||||
.expect("process transaction");
|
.expect("process transaction");
|
||||||
assert_eq!(bank.vote_accounts().len(), 2);
|
assert_eq!(bank.vote_accounts().len(), 2);
|
||||||
|
|
||||||
|
// Check getVoteAccounts: the bootstrap leader vote account will be delinquent as it has
|
||||||
|
// stake but has never voted, and the vote account with no stake should not be present.
|
||||||
|
{
|
||||||
|
let req = format!(r#"{{"jsonrpc":"2.0","id":1,"method":"getVoteAccounts"}}"#);
|
||||||
|
let res = io.handle_request_sync(&req, meta.clone());
|
||||||
|
let result: Value = serde_json::from_str(&res.expect("actual response"))
|
||||||
|
.expect("actual response deserialization");
|
||||||
|
|
||||||
|
let vote_account_status: RpcVoteAccountStatus =
|
||||||
|
serde_json::from_value(result["result"].clone()).unwrap();
|
||||||
|
|
||||||
|
assert!(vote_account_status.current.is_empty());
|
||||||
|
assert_eq!(vote_account_status.delinquent.len(), 1);
|
||||||
|
for vote_account_info in vote_account_status.delinquent {
|
||||||
|
assert_ne!(vote_account_info.activated_stake, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Advance bank to the next epoch
|
// Advance bank to the next epoch
|
||||||
for _ in 0..TEST_SLOTS_PER_EPOCH {
|
for _ in 0..TEST_SLOTS_PER_EPOCH {
|
||||||
bank.freeze();
|
bank.freeze();
|
||||||
|
|
||||||
let instruction = vote_instruction::vote(
|
// Votes
|
||||||
&leader_vote_keypair.pubkey(),
|
let instructions = vec![
|
||||||
&leader_vote_keypair.pubkey(),
|
vote_instruction::vote(
|
||||||
Vote {
|
&leader_vote_keypair.pubkey(),
|
||||||
slots: vec![bank.slot()],
|
&leader_vote_keypair.pubkey(),
|
||||||
hash: bank.hash(),
|
Vote {
|
||||||
timestamp: None,
|
slots: vec![bank.slot()],
|
||||||
},
|
hash: bank.hash(),
|
||||||
);
|
timestamp: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
vote_instruction::vote(
|
||||||
|
&alice_vote_keypair.pubkey(),
|
||||||
|
&alice_vote_keypair.pubkey(),
|
||||||
|
Vote {
|
||||||
|
slots: vec![bank.slot()],
|
||||||
|
hash: bank.hash(),
|
||||||
|
timestamp: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
bank = bank_forks.write().unwrap().insert(Bank::new_from_parent(
|
bank = bank_forks.write().unwrap().insert(Bank::new_from_parent(
|
||||||
&bank,
|
&bank,
|
||||||
|
@ -1971,9 +2002,9 @@ pub mod tests {
|
||||||
));
|
));
|
||||||
|
|
||||||
let transaction = Transaction::new_signed_with_payer(
|
let transaction = Transaction::new_signed_with_payer(
|
||||||
vec![instruction],
|
instructions,
|
||||||
Some(&alice.pubkey()),
|
Some(&alice.pubkey()),
|
||||||
&[&alice, &leader_vote_keypair],
|
&[&alice, &leader_vote_keypair, &alice_vote_keypair],
|
||||||
bank.last_blockhash(),
|
bank.last_blockhash(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1996,16 +2027,50 @@ pub mod tests {
|
||||||
// The vote account with no stake should not be present.
|
// The vote account with no stake should not be present.
|
||||||
assert!(vote_account_status.delinquent.is_empty());
|
assert!(vote_account_status.delinquent.is_empty());
|
||||||
|
|
||||||
// The leader vote account should be active and have voting history.
|
// Both accounts should be active and have voting history.
|
||||||
assert_eq!(vote_account_status.current.len(), 1);
|
assert_eq!(vote_account_status.current.len(), 2);
|
||||||
let leader_info = &vote_account_status.current[0];
|
//let leader_info = &vote_account_status.current[0];
|
||||||
assert_eq!(
|
let leader_info = vote_account_status
|
||||||
leader_info.vote_pubkey,
|
.current
|
||||||
leader_vote_keypair.pubkey().to_string()
|
.iter()
|
||||||
);
|
.find(|x| x.vote_pubkey == leader_vote_keypair.pubkey().to_string())
|
||||||
|
.unwrap();
|
||||||
assert_ne!(leader_info.activated_stake, 0);
|
assert_ne!(leader_info.activated_stake, 0);
|
||||||
// Subtract one because the last vote always carries over to the next epoch
|
// Subtract one because the last vote always carries over to the next epoch
|
||||||
let expected_credits = TEST_SLOTS_PER_EPOCH - MAX_LOCKOUT_HISTORY as u64 - 1;
|
let expected_credits = TEST_SLOTS_PER_EPOCH - MAX_LOCKOUT_HISTORY as u64 - 1;
|
||||||
assert_eq!(leader_info.epoch_credits, vec![(0, expected_credits, 0)]);
|
assert_eq!(leader_info.epoch_credits, vec![(0, expected_credits, 0)]);
|
||||||
|
|
||||||
|
// Advance bank with no voting
|
||||||
|
bank.freeze();
|
||||||
|
bank_forks.write().unwrap().insert(Bank::new_from_parent(
|
||||||
|
&bank,
|
||||||
|
&Pubkey::default(),
|
||||||
|
bank.slot() + TEST_SLOTS_PER_EPOCH,
|
||||||
|
));
|
||||||
|
|
||||||
|
// The leader vote account should now be delinquent, and the other vote account disappears
|
||||||
|
// because it's inactive with no stake
|
||||||
|
{
|
||||||
|
let req = format!(
|
||||||
|
r#"{{"jsonrpc":"2.0","id":1,"method":"getVoteAccounts","params":{}}}"#,
|
||||||
|
json!([CommitmentConfig::recent()])
|
||||||
|
);
|
||||||
|
|
||||||
|
let res = io.handle_request_sync(&req, meta.clone());
|
||||||
|
let result: Value = serde_json::from_str(&res.expect("actual response"))
|
||||||
|
.expect("actual response deserialization");
|
||||||
|
|
||||||
|
let vote_account_status: RpcVoteAccountStatus =
|
||||||
|
serde_json::from_value(result["result"].clone()).unwrap();
|
||||||
|
|
||||||
|
assert!(vote_account_status.current.is_empty());
|
||||||
|
assert_eq!(vote_account_status.delinquent.len(), 1);
|
||||||
|
for vote_account_info in vote_account_status.delinquent {
|
||||||
|
assert_eq!(
|
||||||
|
vote_account_info.vote_pubkey,
|
||||||
|
leader_vote_keypair.pubkey().to_string()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue