vote: plumb TowerSync ix (#584)

This commit is contained in:
Ashwin Sekar 2024-04-10 20:33:45 -07:00 committed by GitHub
parent 16efe510cb
commit c4734ad127
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 425 additions and 324 deletions

View File

@ -1,7 +1,10 @@
#![allow(clippy::arithmetic_side_effects)]
#![feature(test)]
use solana_core::validator::BlockProductionMethod;
use {
solana_core::validator::BlockProductionMethod,
solana_vote_program::{vote_state::TowerSync, vote_transaction::new_tower_sync_transaction},
};
extern crate test;
@ -50,9 +53,6 @@ use {
transaction::{Transaction, VersionedTransaction},
},
solana_streamer::socket::SocketAddrSpace,
solana_vote_program::{
vote_state::VoteStateUpdate, vote_transaction::new_vote_state_update_transaction,
},
std::{
iter::repeat_with,
sync::{atomic::Ordering, Arc},
@ -169,11 +169,11 @@ fn make_vote_txs(txes: usize) -> Vec<Transaction> {
.map(|i| {
// Quarter of the votes should be filtered out
let vote = if i % 4 == 0 {
VoteStateUpdate::from(vec![(2, 1)])
TowerSync::from(vec![(2, 1)])
} else {
VoteStateUpdate::from(vec![(i as u64, 1)])
TowerSync::from(vec![(i as u64, 1)])
};
new_vote_state_update_transaction(
new_tower_sync_transaction(
vote,
Hash::new_unique(),
&keypairs[i % num_voters],

View File

@ -1,4 +1,4 @@
use crate::replay_stage::DUPLICATE_THRESHOLD;
use {crate::replay_stage::DUPLICATE_THRESHOLD, solana_sdk::feature_set};
pub mod fork_choice;
pub mod heaviest_subtree_fork_choice;
@ -35,8 +35,8 @@ use {
vote_instruction,
vote_state::{
process_slot_vote_unchecked, process_vote_unchecked, BlockTimestamp, LandedVote,
Lockout, Vote, VoteState, VoteState1_14_11, VoteStateUpdate, VoteStateVersions,
VoteTransaction, MAX_LOCKOUT_HISTORY,
Lockout, TowerSync, Vote, VoteState, VoteState1_14_11, VoteStateUpdate,
VoteStateVersions, VoteTransaction, MAX_LOCKOUT_HISTORY,
},
},
std::{
@ -267,7 +267,7 @@ impl Default for Tower {
threshold_depth: VOTE_THRESHOLD_DEPTH,
threshold_size: VOTE_THRESHOLD_SIZE,
vote_state: VoteState::default(),
last_vote: VoteTransaction::from(VoteStateUpdate::default()),
last_vote: VoteTransaction::from(TowerSync::default()),
last_timestamp: BlockTimestamp::default(),
last_vote_tx_blockhash: BlockhashStatus::default(),
stray_restored_slot: Option::default(),
@ -310,7 +310,7 @@ impl Tower {
let mut rng = rand::thread_rng();
let root_slot = rng.gen();
let vote_state = VoteState::new_rand_for_tests(node_pubkey, root_slot);
let last_vote = VoteStateUpdate::from(
let last_vote = TowerSync::from(
vote_state
.votes
.iter()
@ -320,7 +320,7 @@ impl Tower {
Self {
node_pubkey,
vote_state,
last_vote: VoteTransaction::CompactVoteStateUpdate(last_vote),
last_vote: VoteTransaction::from(last_vote),
..Tower::default()
}
}
@ -590,21 +590,43 @@ impl Tower {
pub fn record_bank_vote(&mut self, bank: &Bank) -> Option<Slot> {
// Returns the new root if one is made after applying a vote for the given bank to
// `self.vote_state`
self.record_bank_vote_and_update_lockouts(bank.slot(), bank.hash())
self.record_bank_vote_and_update_lockouts(
bank.slot(),
bank.hash(),
bank.feature_set
.is_active(&feature_set::enable_tower_sync_ix::id()),
)
}
/// If we've recently updated the vote state by applying a new vote
/// or syncing from a bank, generate the proper last_vote.
pub(crate) fn update_last_vote_from_vote_state(&mut self, vote_hash: Hash) {
let mut new_vote = VoteTransaction::from(VoteStateUpdate::new(
self.vote_state
.votes
.iter()
.map(|vote| vote.lockout)
.collect(),
self.vote_state.root_slot,
vote_hash,
));
pub(crate) fn update_last_vote_from_vote_state(
&mut self,
vote_hash: Hash,
enable_tower_sync_ix: bool,
) {
let mut new_vote = if enable_tower_sync_ix {
VoteTransaction::from(TowerSync::new(
self.vote_state
.votes
.iter()
.map(|vote| vote.lockout)
.collect(),
self.vote_state.root_slot,
vote_hash,
Hash::default(), // TODO: block_id will fill in upcoming pr
))
} else {
VoteTransaction::from(VoteStateUpdate::new(
self.vote_state
.votes
.iter()
.map(|vote| vote.lockout)
.collect(),
self.vote_state.root_slot,
vote_hash,
))
};
new_vote.set_timestamp(self.maybe_timestamp(self.last_voted_slot().unwrap_or_default()));
self.last_vote = new_vote;
@ -614,6 +636,7 @@ impl Tower {
&mut self,
vote_slot: Slot,
vote_hash: Hash,
enable_tower_sync_ix: bool,
) -> Option<Slot> {
trace!("{} record_vote for {}", self.node_pubkey, vote_slot);
let old_root = self.root();
@ -626,7 +649,7 @@ impl Tower {
vote_slot, vote_hash, result
);
}
self.update_last_vote_from_vote_state(vote_hash);
self.update_last_vote_from_vote_state(vote_hash, enable_tower_sync_ix);
let new_root = self.root();
@ -644,7 +667,7 @@ impl Tower {
#[cfg(feature = "dev-context-only-utils")]
pub fn record_vote(&mut self, slot: Slot, hash: Hash) -> Option<Slot> {
self.record_bank_vote_and_update_lockouts(slot, hash)
self.record_bank_vote_and_update_lockouts(slot, hash, true)
}
/// Used for tests
@ -1271,8 +1294,9 @@ impl Tower {
assert!(
self.last_vote == VoteTransaction::from(VoteStateUpdate::default())
&& self.vote_state.votes.is_empty()
|| self.last_vote != VoteTransaction::from(VoteStateUpdate::default())
&& !self.vote_state.votes.is_empty(),
|| self.last_vote == VoteTransaction::from(TowerSync::default())
&& self.vote_state.votes.is_empty()
|| !self.vote_state.votes.is_empty(),
"last vote: {:?} vote_state.votes: {:?}",
self.last_vote,
self.vote_state.votes
@ -2734,10 +2758,11 @@ pub mod test {
} else {
vec![]
};
let mut expected = VoteStateUpdate::new(
let mut expected = TowerSync::new(
VecDeque::from(slots),
if num_votes > 0 { Some(0) } else { None },
Hash::default(),
Hash::default(),
);
for i in 0..num_votes {
tower.record_vote(i as u64, Hash::default());
@ -2789,8 +2814,7 @@ pub mod test {
assert_eq!(tower.last_timestamp.timestamp, 0);
// Tower has vote no timestamp, but is greater than heaviest_bank
tower.last_vote =
VoteTransaction::from(VoteStateUpdate::from(vec![(0, 3), (1, 2), (6, 1)]));
tower.last_vote = VoteTransaction::from(TowerSync::from(vec![(0, 3), (1, 2), (6, 1)]));
assert_eq!(tower.last_vote.timestamp(), None);
tower.refresh_last_vote_timestamp(5);
assert_eq!(tower.last_vote.timestamp(), None);
@ -2798,8 +2822,7 @@ pub mod test {
assert_eq!(tower.last_timestamp.timestamp, 0);
// Tower has vote with no timestamp
tower.last_vote =
VoteTransaction::from(VoteStateUpdate::from(vec![(0, 3), (1, 2), (2, 1)]));
tower.last_vote = VoteTransaction::from(TowerSync::from(vec![(0, 3), (1, 2), (2, 1)]));
assert_eq!(tower.last_vote.timestamp(), None);
tower.refresh_last_vote_timestamp(5);
assert_eq!(tower.last_vote.timestamp(), Some(1));
@ -2807,8 +2830,7 @@ pub mod test {
assert_eq!(tower.last_timestamp.timestamp, 1);
// Vote has timestamp
tower.last_vote =
VoteTransaction::from(VoteStateUpdate::from(vec![(0, 3), (1, 2), (2, 1)]));
tower.last_vote = VoteTransaction::from(TowerSync::from(vec![(0, 3), (1, 2), (2, 1)]));
tower.refresh_last_vote_timestamp(5);
assert_eq!(tower.last_vote.timestamp(), Some(2));
assert_eq!(tower.last_timestamp.slot, 2);

View File

@ -3390,6 +3390,8 @@ impl ReplayStage {
progress
.get_hash(last_voted_slot)
.expect("Must exist for us to have frozen descendant"),
bank.feature_set
.is_active(&feature_set::enable_tower_sync_ix::id()),
);
// Since we are updating our tower we need to update associated caches for previously computed
// slots as well.

View File

@ -12,7 +12,7 @@ use {
slot_hashes::SlotHashes,
sysvar,
},
solana_vote::vote_transaction::{VoteTransaction, VoteTransaction::VoteStateUpdate},
solana_vote::vote_transaction::VoteTransaction,
std::{
collections::{BTreeMap, HashMap, HashSet},
sync::Arc,
@ -231,7 +231,7 @@ impl VerifiedVotePackets {
let timestamp = vote.timestamp();
match vote {
VoteStateUpdate(_) => {
VoteTransaction::VoteStateUpdate(_) | VoteTransaction::TowerSync(_) => {
let (latest_gossip_slot, latest_timestamp) =
self.0.get(&vote_account_key).map_or((0, None), |vote| {
(vote.get_latest_gossip_slot(), vote.get_latest_timestamp())

View File

@ -193,16 +193,25 @@ declare_process_instruction!(Entrypoint, DEFAULT_COMPUTE_UNITS, |invoke_context|
&invoke_context.feature_set,
)
}
VoteInstruction::TowerSync(_tower_sync)
| VoteInstruction::TowerSyncSwitch(_tower_sync, _) => {
VoteInstruction::TowerSync(tower_sync)
| VoteInstruction::TowerSyncSwitch(tower_sync, _) => {
if !invoke_context
.feature_set
.is_active(&feature_set::enable_tower_sync_ix::id())
{
return Err(InstructionError::InvalidInstructionData);
}
// TODO: will fill in future PR
return Err(InstructionError::InvalidInstructionData);
let sysvar_cache = invoke_context.get_sysvar_cache();
let slot_hashes = sysvar_cache.get_slot_hashes()?;
let clock = sysvar_cache.get_clock()?;
vote_state::process_tower_sync(
&mut me,
slot_hashes.slot_hashes(),
&clock,
tower_sync,
&signers,
&invoke_context.feature_set,
)
}
VoteInstruction::Withdraw(lamports) => {
instruction_context.check_number_of_instruction_accounts(2)?;
@ -257,7 +266,7 @@ mod tests {
vote_switch, withdraw, CreateVoteAccountConfig, VoteInstruction,
},
vote_state::{
self, Lockout, Vote, VoteAuthorize, VoteAuthorizeCheckedWithSeedArgs,
self, Lockout, TowerSync, Vote, VoteAuthorize, VoteAuthorizeCheckedWithSeedArgs,
VoteAuthorizeWithSeedArgs, VoteInit, VoteState, VoteStateUpdate, VoteStateVersions,
},
},
@ -274,6 +283,7 @@ mod tests {
self, clock::Clock, epoch_schedule::EpochSchedule, rent::Rent,
slot_hashes::SlotHashes,
},
vote::instruction::{tower_sync, tower_sync_switch},
},
std::{collections::HashSet, str::FromStr},
};
@ -490,11 +500,12 @@ mod tests {
(vote_pubkey, vote_account_with_epoch_credits)
}
/// Returns Vec of serialized VoteInstruction and flag indicating if it is a vote state update
/// Returns Vec of serialized VoteInstruction and flag indicating if it is a vote state proposal
/// variant, along with the original vote
fn create_serialized_votes() -> (Vote, Vec<(Vec<u8>, bool)>) {
let vote = Vote::new(vec![1], Hash::default());
let vote_state_update = VoteStateUpdate::from(vec![(1, 1)]);
let tower_sync = TowerSync::from(vec![(1, 1)]);
(
vote.clone(),
vec![
@ -508,6 +519,10 @@ mod tests {
serialize(&VoteInstruction::CompactUpdateVoteState(vote_state_update)).unwrap(),
true,
),
(
serialize(&VoteInstruction::TowerSync(tower_sync)).unwrap(),
true,
),
],
)
}
@ -1742,6 +1757,14 @@ mod tests {
),
Err(InstructionError::InvalidAccountOwner),
);
process_instruction_as_one_arg(
&tower_sync(
&invalid_vote_state_pubkey(),
&Pubkey::default(),
TowerSync::default(),
),
Err(InstructionError::InvalidAccountOwner),
);
}
#[test]
@ -1904,7 +1927,6 @@ mod tests {
),
Err(InstructionError::InvalidAccountData),
);
process_instruction_as_one_arg(
&compact_update_vote_state_switch(
&Pubkey::default(),
@ -1914,6 +1936,19 @@ mod tests {
),
Err(InstructionError::InvalidAccountData),
);
process_instruction_as_one_arg(
&tower_sync(&Pubkey::default(), &Pubkey::default(), TowerSync::default()),
Err(InstructionError::InvalidAccountData),
);
process_instruction_as_one_arg(
&tower_sync_switch(
&Pubkey::default(),
&Pubkey::default(),
TowerSync::default(),
Hash::default(),
),
Err(InstructionError::InvalidAccountData),
);
process_instruction_as_one_arg(
&update_validator_identity(

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
use {
solana_program::vote::{
self,
state::{Vote, VoteStateUpdate},
state::{TowerSync, Vote, VoteStateUpdate},
},
solana_sdk::{
clock::Slot,
@ -102,3 +102,33 @@ pub fn new_compact_vote_state_update_transaction(
vote_tx.partial_sign(&[authorized_voter_keypair], blockhash);
vote_tx
}
pub fn new_tower_sync_transaction(
tower_sync: TowerSync,
blockhash: Hash,
node_keypair: &Keypair,
vote_keypair: &Keypair,
authorized_voter_keypair: &Keypair,
switch_proof_hash: Option<Hash>,
) -> Transaction {
let vote_ix = if let Some(switch_proof_hash) = switch_proof_hash {
vote::instruction::tower_sync_switch(
&vote_keypair.pubkey(),
&authorized_voter_keypair.pubkey(),
tower_sync,
switch_proof_hash,
)
} else {
vote::instruction::tower_sync(
&vote_keypair.pubkey(),
&authorized_voter_keypair.pubkey(),
tower_sync,
)
};
let mut vote_tx = Transaction::new_with_payer(&[vote_ix], Some(&node_keypair.pubkey()));
vote_tx.partial_sign(&[node_keypair], blockhash);
vote_tx.partial_sign(&[authorized_voter_keypair], blockhash);
vote_tx
}