From bf80e86aff571a0e24295fd7e4861d75e6cdfd7e Mon Sep 17 00:00:00 2001 From: Ashwin Sekar Date: Thu, 4 Apr 2024 22:25:38 +0000 Subject: [PATCH 1/2] vote: deprecate legacy vote instructions --- programs/vote/src/vote_processor.rs | 27 +++++++++++++++++++++++++++ sdk/src/feature_set.rs | 5 +++++ 2 files changed, 32 insertions(+) diff --git a/programs/vote/src/vote_processor.rs b/programs/vote/src/vote_processor.rs index 748a8e9d6915d5..1b066be6e2125a 100644 --- a/programs/vote/src/vote_processor.rs +++ b/programs/vote/src/vote_processor.rs @@ -133,6 +133,15 @@ declare_process_instruction!(Entrypoint, DEFAULT_COMPUTE_UNITS, |invoke_context| ) } VoteInstruction::Vote(vote) | VoteInstruction::VoteSwitch(vote, _) => { + if invoke_context + .get_feature_set() + .is_active(&feature_set::deprecate_legacy_vote_ixs::id()) + && invoke_context + .get_feature_set() + .is_active(&feature_set::enable_tower_sync_ix::id()) + { + return Err(InstructionError::InvalidInstructionData); + } let slot_hashes = get_sysvar_with_account_check::slot_hashes(invoke_context, instruction_context, 1)?; let clock = @@ -148,6 +157,15 @@ declare_process_instruction!(Entrypoint, DEFAULT_COMPUTE_UNITS, |invoke_context| } VoteInstruction::UpdateVoteState(vote_state_update) | VoteInstruction::UpdateVoteStateSwitch(vote_state_update, _) => { + if invoke_context + .get_feature_set() + .is_active(&feature_set::deprecate_legacy_vote_ixs::id()) + && invoke_context + .get_feature_set() + .is_active(&feature_set::enable_tower_sync_ix::id()) + { + 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()?; @@ -162,6 +180,15 @@ declare_process_instruction!(Entrypoint, DEFAULT_COMPUTE_UNITS, |invoke_context| } VoteInstruction::CompactUpdateVoteState(vote_state_update) | VoteInstruction::CompactUpdateVoteStateSwitch(vote_state_update, _) => { + if invoke_context + .get_feature_set() + .is_active(&feature_set::deprecate_legacy_vote_ixs::id()) + && invoke_context + .get_feature_set() + .is_active(&feature_set::enable_tower_sync_ix::id()) + { + 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()?; diff --git a/sdk/src/feature_set.rs b/sdk/src/feature_set.rs index 4626240a949de4..7322fdbfde900c 100644 --- a/sdk/src/feature_set.rs +++ b/sdk/src/feature_set.rs @@ -845,6 +845,10 @@ pub mod enable_turbine_extended_fanout_experiments { solana_sdk::declare_id!("BZn14Liea52wtBwrXUxTv6vojuTTmfc7XGEDTXrvMD7b"); } +pub mod deprecate_legacy_vote_ixs { + solana_sdk::declare_id!("depVvnQ2UysGrhwdiwU42tCadZL8GcBb1i2GYhMopQv"); +} + lazy_static! { /// Map of feature identifiers to user-visible description pub static ref FEATURE_NAMES: HashMap = [ @@ -1051,6 +1055,7 @@ lazy_static! { (ed25519_precompile_verify_strict::id(), "Use strict verification in ed25519 precompile SIMD-0152"), (vote_only_retransmitter_signed_fec_sets::id(), "vote only on retransmitter signed fec sets"), (enable_turbine_extended_fanout_experiments::id(), "enable turbine extended fanout experiments #"), + (deprecate_legacy_vote_ixs::id(), "Deprecate legacy vote instructions"), /*************** ADD NEW FEATURES HERE ***************/ ] .iter() From 01c2a93c4d70ac3f8d773cdb535d8de37cac88e6 Mon Sep 17 00:00:00 2001 From: Ashwin Sekar Date: Wed, 14 Aug 2024 17:15:54 +0000 Subject: [PATCH 2/2] fix tests --- .../banking_stage/latest_unprocessed_votes.rs | 44 +--------- core/src/banking_stage/qos_service.rs | 12 ++- .../unprocessed_packet_batches.rs | 7 +- core/src/cluster_info_vote_listener.rs | 45 +++++----- core/src/commitment_service.rs | 27 +++--- core/src/replay_stage.rs | 14 +-- core/src/vote_simulator.rs | 39 +++++++-- cost-model/src/cost_tracker.rs | 7 +- cost-model/src/transaction_cost.rs | 9 +- ledger/src/blockstore_processor.rs | 32 +++---- local-cluster/src/cluster_tests.rs | 8 +- local-cluster/tests/local_cluster.rs | 7 +- perf/src/test_tx.rs | 8 +- programs/vote/src/vote_processor.rs | 85 +++++++++++-------- rpc/src/rpc.rs | 18 ++-- .../src/bank/partitioned_epoch_rewards/mod.rs | 11 ++- runtime/tests/stake.rs | 32 +++++-- sdk/program/src/vote/state/mod.rs | 33 +++++++ 18 files changed, 243 insertions(+), 195 deletions(-) diff --git a/core/src/banking_stage/latest_unprocessed_votes.rs b/core/src/banking_stage/latest_unprocessed_votes.rs index 52b520b8a0322b..9b43f6bd6e086a 100644 --- a/core/src/banking_stage/latest_unprocessed_votes.rs +++ b/core/src/banking_stage/latest_unprocessed_votes.rs @@ -407,8 +407,7 @@ mod tests { }, solana_sdk::{hash::Hash, signature::Signer, system_transaction::transfer}, solana_vote_program::{ - vote_state::TowerSync, - vote_transaction::{new_tower_sync_transaction, new_vote_transaction}, + vote_state::TowerSync, vote_transaction::new_tower_sync_transaction, }, std::{sync::Arc, thread::Builder}, }; @@ -450,40 +449,8 @@ mod tests { #[test] fn test_deserialize_vote_packets() { let keypairs = ValidatorVoteKeypairs::new_rand(); - let bankhash = Hash::new_unique(); let blockhash = Hash::new_unique(); let switch_proof = Hash::new_unique(); - let mut vote = Packet::from_data( - None, - new_vote_transaction( - vec![0, 1, 2], - bankhash, - blockhash, - &keypairs.node_keypair, - &keypairs.vote_keypair, - &keypairs.vote_keypair, - None, - ), - ) - .unwrap(); - vote.meta_mut().flags.set(PacketFlags::SIMPLE_VOTE_TX, true); - let mut vote_switch = Packet::from_data( - None, - new_vote_transaction( - vec![0, 1, 2], - bankhash, - blockhash, - &keypairs.node_keypair, - &keypairs.vote_keypair, - &keypairs.vote_keypair, - Some(switch_proof), - ), - ) - .unwrap(); - vote_switch - .meta_mut() - .flags - .set(PacketFlags::SIMPLE_VOTE_TX, true); let mut tower_sync = Packet::from_data( None, new_tower_sync_transaction( @@ -526,13 +493,8 @@ mod tests { ), ) .unwrap(); - let packet_batch = PacketBatch::new(vec![ - vote, - vote_switch, - tower_sync, - tower_sync_switch, - random_transaction, - ]); + let packet_batch = + PacketBatch::new(vec![tower_sync, tower_sync_switch, random_transaction]); let deserialized_packets = deserialize_packets( &packet_batch, diff --git a/core/src/banking_stage/qos_service.rs b/core/src/banking_stage/qos_service.rs index afa871277cce42..6fe35c46f54e03 100644 --- a/core/src/banking_stage/qos_service.rs +++ b/core/src/banking_stage/qos_service.rs @@ -598,7 +598,7 @@ mod tests { signature::{Keypair, Signer}, system_transaction, }, - solana_vote_program::vote_transaction, + solana_vote_program::{vote_state::TowerSync, vote_transaction}, std::sync::Arc, }; @@ -612,9 +612,8 @@ mod tests { system_transaction::transfer(&keypair, &keypair.pubkey(), 1, Hash::default()), ); let vote_tx = SanitizedTransaction::from_transaction_for_tests( - vote_transaction::new_vote_transaction( - vec![42], - Hash::default(), + vote_transaction::new_tower_sync_transaction( + TowerSync::from(vec![(42, 1)]), Hash::default(), &keypair, &keypair, @@ -656,9 +655,8 @@ mod tests { system_transaction::transfer(&keypair, &keypair.pubkey(), 1, Hash::default()), ); let vote_tx = SanitizedTransaction::from_transaction_for_tests( - vote_transaction::new_vote_transaction( - vec![42], - Hash::default(), + vote_transaction::new_tower_sync_transaction( + TowerSync::from(vec![(42, 1)]), Hash::default(), &keypair, &keypair, diff --git a/core/src/banking_stage/unprocessed_packet_batches.rs b/core/src/banking_stage/unprocessed_packet_batches.rs index 2bec44dbd0ea5e..f92eeb09c57b54 100644 --- a/core/src/banking_stage/unprocessed_packet_batches.rs +++ b/core/src/banking_stage/unprocessed_packet_batches.rs @@ -315,7 +315,7 @@ mod tests { system_instruction, system_transaction, transaction::{SimpleAddressLoader, Transaction}, }, - solana_vote_program::vote_transaction, + solana_vote_program::{vote_state::TowerSync, vote_transaction}, }; fn simple_deserialized_packet() -> DeserializedPacket { @@ -467,9 +467,8 @@ mod tests { let keypair = Keypair::new(); let transfer_tx = system_transaction::transfer(&keypair, &keypair.pubkey(), 1, Hash::default()); - let vote_tx = vote_transaction::new_vote_transaction( - vec![42], - Hash::default(), + let vote_tx = vote_transaction::new_tower_sync_transaction( + TowerSync::from(vec![(42, 1)]), Hash::default(), &keypair, &keypair, diff --git a/core/src/cluster_info_vote_listener.rs b/core/src/cluster_info_vote_listener.rs index a4306dcbea2ea4..d8a8670f585e56 100644 --- a/core/src/cluster_info_vote_listener.rs +++ b/core/src/cluster_info_vote_listener.rs @@ -734,7 +734,7 @@ mod tests { signature::{Keypair, Signature, Signer}, }, solana_vote_program::{ - vote_state::{TowerSync, Vote}, + vote_state::{TowerSync, Vote, MAX_LOCKOUT_HISTORY}, vote_transaction, }, std::{ @@ -749,11 +749,9 @@ mod tests { solana_logger::setup(); let node_keypair = Keypair::new(); let vote_keypair = Keypair::new(); - let slots: Vec<_> = (0..31).collect(); - - let vote_tx = vote_transaction::new_vote_transaction( - slots, - Hash::default(), + let tower_sync = TowerSync::new_from_slot(MAX_LOCKOUT_HISTORY as u64, Hash::default()); + let vote_tx = vote_transaction::new_tower_sync_transaction( + tower_sync, Hash::default(), &node_keypair, &vote_keypair, @@ -918,12 +916,12 @@ mod tests { votes_sender: &VerifiedVoteTransactionsSender, replay_votes_sender: &ReplayVoteSender, ) { + let tower_sync = TowerSync::new_from_slots(gossip_vote_slots, Hash::default(), None); validator_voting_keypairs.iter().for_each(|keypairs| { let node_keypair = &keypairs.node_keypair; let vote_keypair = &keypairs.vote_keypair; - let vote_tx = vote_transaction::new_vote_transaction( - gossip_vote_slots.clone(), - Hash::default(), + let vote_tx = vote_transaction::new_tower_sync_transaction( + tower_sync.clone(), Hash::default(), node_keypair, vote_keypair, @@ -1121,9 +1119,10 @@ mod tests { let node_keypair = &keypairs.node_keypair; let vote_keypair = &keypairs.vote_keypair; expected_votes.push((vote_keypair.pubkey(), vec![i as Slot + 1])); - vote_transaction::new_vote_transaction( - vec![i as u64 + 1], - bank_hash, + let tower_sync = + TowerSync::new_from_slots(vec![(i as u64 + 1)], bank_hash, None); + vote_transaction::new_tower_sync_transaction( + tower_sync, Hash::default(), node_keypair, vote_keypair, @@ -1218,9 +1217,10 @@ mod tests { for &e in &events { if e == 0 || e == 2 { // Create vote transaction - let vote_tx = vote_transaction::new_vote_transaction( - vec![vote_slot], - vote_bank_hash, + let tower_sync = + TowerSync::new_from_slots(vec![(vote_slot)], vote_bank_hash, None); + let vote_tx = vote_transaction::new_tower_sync_transaction( + tower_sync, Hash::default(), node_keypair, vote_keypair, @@ -1315,10 +1315,9 @@ mod tests { // in the tracker let validator0_keypairs = &validator_keypairs[0]; let voted_slot = bank.slot() + 1; - let vote_tx = vec![vote_transaction::new_vote_transaction( + let vote_tx = vec![vote_transaction::new_tower_sync_transaction( // Must vote > root to be processed - vec![voted_slot], - Hash::default(), + TowerSync::from(vec![(voted_slot, 1)]), Hash::default(), &validator0_keypairs.node_keypair, &validator0_keypairs.vote_keypair, @@ -1362,10 +1361,9 @@ mod tests { let vote_txs: Vec<_> = [first_slot_in_new_epoch - 1, first_slot_in_new_epoch] .iter() .map(|slot| { - vote_transaction::new_vote_transaction( + vote_transaction::new_tower_sync_transaction( // Must vote > root to be processed - vec![*slot], - Hash::default(), + TowerSync::from(vec![(*slot, 1)]), Hash::default(), &validator0_keypairs.node_keypair, &validator0_keypairs.vote_keypair, @@ -1462,9 +1460,8 @@ mod tests { let validator_vote_keypair = validator_vote_keypairs.unwrap_or(&other); // TODO authorized_voter_keypair should be different from vote-keypair // but that is what create_genesis_... currently generates. - vote_transaction::new_vote_transaction( - vec![0], - Hash::default(), + vote_transaction::new_tower_sync_transaction( + TowerSync::from(vec![(0, 1)]), Hash::default(), &validator_vote_keypair.node_keypair, &validator_vote_keypair.vote_keypair, diff --git a/core/src/commitment_service.rs b/core/src/commitment_service.rs index cae40c587cb572..03c04ad9ab4c96 100644 --- a/core/src/commitment_service.rs +++ b/core/src/commitment_service.rs @@ -277,7 +277,10 @@ mod tests { solana_sdk::{account::Account, pubkey::Pubkey, signature::Signer}, solana_stake_program::stake_state, solana_vote_program::{ - vote_state::{self, process_slot_vote_unchecked, VoteStateVersions}, + vote_state::{ + self, process_slot_vote_unchecked, TowerSync, VoteStateVersions, + MAX_LOCKOUT_HISTORY, + }, vote_transaction, }, }; @@ -568,9 +571,9 @@ mod tests { &Pubkey::default(), x + 1, ); - let vote = vote_transaction::new_vote_transaction( - vec![x], - previous_bank.hash(), + let tower_sync = TowerSync::new_from_slot(x, previous_bank.hash()); + let vote = vote_transaction::new_tower_sync_transaction( + tower_sync, previous_bank.last_blockhash(), &validator_vote_keypairs.node_keypair, &validator_vote_keypairs.vote_keypair, @@ -601,9 +604,9 @@ mod tests { &Pubkey::default(), 34, ); - let vote33 = vote_transaction::new_vote_transaction( - vec![33], - bank33.hash(), + let tower_sync = TowerSync::new_from_slot(33, bank33.hash()); + let vote33 = vote_transaction::new_tower_sync_transaction( + tower_sync, bank33.last_blockhash(), &validator_vote_keypairs.node_keypair, &validator_vote_keypairs.vote_keypair, @@ -683,9 +686,13 @@ mod tests { &Pubkey::default(), x + 1, ); - let vote = vote_transaction::new_vote_transaction( - vec![x], - previous_bank.hash(), + // Skip 34 as it is not part of this fork. + let lowest_slot = x - MAX_LOCKOUT_HISTORY as u64; + let slots: Vec<_> = (lowest_slot..(x + 1)).filter(|s| *s != 34).collect(); + let tower_sync = + TowerSync::new_from_slots(slots, previous_bank.hash(), Some(lowest_slot - 1)); + let vote = vote_transaction::new_tower_sync_transaction( + tower_sync, previous_bank.last_blockhash(), &validator_vote_keypairs.node_keypair, &validator_vote_keypairs.vote_keypair, diff --git a/core/src/replay_stage.rs b/core/src/replay_stage.rs index cca977768c0436..7f7d0f61157d9c 100644 --- a/core/src/replay_stage.rs +++ b/core/src/replay_stage.rs @@ -4501,7 +4501,7 @@ pub(crate) mod tests { solana_streamer::socket::SocketAddrSpace, solana_transaction_status::VersionedTransactionWithStatusMeta, solana_vote_program::{ - vote_state::{self, VoteStateVersions}, + vote_state::{self, TowerSync, VoteStateVersions}, vote_transaction, }, std::{ @@ -5464,9 +5464,9 @@ pub(crate) mod tests { LatestValidatorVotesForFrozenBanks::default(); let bank0 = bank_forks.read().unwrap().get(0).unwrap(); let my_keypairs = keypairs.get(&my_node_pubkey).unwrap(); - let vote_tx = vote_transaction::new_vote_transaction( - vec![0], - bank0.hash(), + let tower_sync = TowerSync::new_from_slots(vec![0], bank0.hash(), None); + let vote_tx = vote_transaction::new_tower_sync_transaction( + tower_sync, bank0.last_blockhash(), &my_keypairs.node_keypair, &my_keypairs.vote_keypair, @@ -6408,9 +6408,9 @@ pub(crate) mod tests { // Process a vote for slot 0 in bank 5 let validator0_keypairs = &validator_keypairs.get(&sender).unwrap(); let bank0 = bank_forks.read().unwrap().get(0).unwrap(); - let vote_tx = vote_transaction::new_vote_transaction( - vec![0], - bank0.hash(), + let tower_sync = TowerSync::new_from_slots(vec![0], bank0.hash(), None); + let vote_tx = vote_transaction::new_tower_sync_transaction( + tower_sync, bank0.last_blockhash(), &validator0_keypairs.node_keypair, &validator0_keypairs.vote_keypair, diff --git a/core/src/vote_simulator.rs b/core/src/vote_simulator.rs index 7a09a7111f0b07..f886d2821af4b0 100644 --- a/core/src/vote_simulator.rs +++ b/core/src/vote_simulator.rs @@ -26,9 +26,12 @@ use { }, }, solana_sdk::{clock::Slot, hash::Hash, pubkey::Pubkey, signature::Signer}, - solana_vote_program::vote_transaction, + solana_vote_program::{ + vote_state::{process_vote_unchecked, Lockout, TowerSync}, + vote_transaction, + }, std::{ - collections::{HashMap, HashSet}, + collections::{HashMap, HashSet, VecDeque}, sync::{Arc, RwLock}, }, trees::{tr, Tree, TreeWalk}, @@ -98,10 +101,34 @@ impl VoteSimulator { if vote.contains(&parent) { let keypairs = self.validator_keypairs.get(pubkey).unwrap(); let latest_blockhash = parent_bank.last_blockhash(); - let vote_tx = vote_transaction::new_vote_transaction( - // Must vote > root to be processed - vec![parent], - parent_bank.hash(), + let tower_sync = if let Some(vote_account) = + parent_bank.get_vote_account(&keypairs.vote_keypair.pubkey()) + { + let mut vote_state = vote_account.vote_state().unwrap().clone(); + process_vote_unchecked( + &mut vote_state, + solana_vote_program::vote_state::Vote::new( + vec![parent], + parent_bank.hash(), + ), + ) + .unwrap(); + TowerSync::new( + vote_state.votes.iter().map(|vote| vote.lockout).collect(), + vote_state.root_slot, + parent_bank.hash(), + Hash::default(), + ) + } else { + TowerSync::new( + VecDeque::from([Lockout::new(parent)]), + Some(root), + parent_bank.hash(), + Hash::default(), + ) + }; + let vote_tx = vote_transaction::new_tower_sync_transaction( + tower_sync, latest_blockhash, &keypairs.node_keypair, &keypairs.vote_keypair, diff --git a/cost-model/src/cost_tracker.rs b/cost-model/src/cost_tracker.rs index 0c731f946ec3b8..23583068fb13b3 100644 --- a/cost-model/src/cost_tracker.rs +++ b/cost-model/src/cost_tracker.rs @@ -396,7 +396,7 @@ mod tests { MessageHash, SanitizedTransaction, SimpleAddressLoader, VersionedTransaction, }, }, - solana_vote_program::vote_transaction, + solana_vote_program::{vote_state::TowerSync, vote_transaction}, std::cmp, }; @@ -438,9 +438,8 @@ mod tests { start_hash: &Hash, ) -> (SanitizedTransaction, TransactionCost) { let keypair = Keypair::new(); - let transaction = vote_transaction::new_vote_transaction( - vec![42], - Hash::default(), + let transaction = vote_transaction::new_tower_sync_transaction( + TowerSync::from(vec![(42, 1)]), *start_hash, mint_keypair, &keypair, diff --git a/cost-model/src/transaction_cost.rs b/cost-model/src/transaction_cost.rs index 4951e50036ca8b..9db5832a114a42 100644 --- a/cost-model/src/transaction_cost.rs +++ b/cost-model/src/transaction_cost.rs @@ -207,7 +207,7 @@ mod tests { signer::keypair::Keypair, transaction::{MessageHash, SanitizedTransaction, VersionedTransaction}, }, - solana_vote_program::vote_transaction, + solana_vote_program::{vote_state::TowerSync, vote_transaction}, }; #[test] @@ -216,9 +216,8 @@ mod tests { let node_keypair = Keypair::new(); let vote_keypair = Keypair::new(); let auth_keypair = Keypair::new(); - let transaction = vote_transaction::new_vote_transaction( - vec![], - Hash::default(), + let transaction = vote_transaction::new_tower_sync_transaction( + TowerSync::default(), Hash::default(), &node_keypair, &vote_keypair, @@ -249,7 +248,7 @@ mod tests { // expected vote tx cost: 2 write locks, 1 sig, 1 vote ix, 8cu of loaded accounts size, let expected_vote_cost = SIMPLE_VOTE_USAGE_COST; // expected non-vote tx cost would include default loaded accounts size cost (16384) additionally - let expected_none_vote_cost = 20535; + let expected_none_vote_cost = 20543; let vote_cost = CostModel::calculate_cost(&vote_transaction, &FeatureSet::all_enabled()); let none_vote_cost = diff --git a/ledger/src/blockstore_processor.rs b/ledger/src/blockstore_processor.rs index ec099b4ecfedab..d34543db73993c 100644 --- a/ledger/src/blockstore_processor.rs +++ b/ledger/src/blockstore_processor.rs @@ -2247,7 +2247,7 @@ pub mod tests { solana_vote::vote_account::VoteAccount, solana_vote_program::{ self, - vote_state::{VoteState, VoteStateVersions, MAX_LOCKOUT_HISTORY}, + vote_state::{TowerSync, VoteState, VoteStateVersions, MAX_LOCKOUT_HISTORY}, vote_transaction, }, std::{collections::BTreeSet, sync::RwLock}, @@ -4286,13 +4286,13 @@ pub mod tests { .iter() .enumerate() .map(|(i, validator_keypairs)| { + let tower_sync = TowerSync::new_from_slots(vec![0], bank0.hash(), None); if i % 3 == 0 { // These votes are correct expected_successful_voter_pubkeys .insert(validator_keypairs.vote_keypair.pubkey()); - vote_transaction::new_vote_transaction( - vec![0], - bank0.hash(), + vote_transaction::new_tower_sync_transaction( + tower_sync, bank_1_blockhash, &validator_keypairs.node_keypair, &validator_keypairs.vote_keypair, @@ -4301,9 +4301,8 @@ pub mod tests { ) } else if i % 3 == 1 { // These have the wrong authorized voter - vote_transaction::new_vote_transaction( - vec![0], - bank0.hash(), + vote_transaction::new_tower_sync_transaction( + tower_sync, bank_1_blockhash, &validator_keypairs.node_keypair, &validator_keypairs.vote_keypair, @@ -4312,9 +4311,8 @@ pub mod tests { ) } else { // These have an invalid vote for non-existent bank 2 - vote_transaction::new_vote_transaction( - vec![bank1.slot() + 1], - bank0.hash(), + vote_transaction::new_tower_sync_transaction( + TowerSync::from(vec![(bank1.slot() + 1, 1)]), bank_1_blockhash, &validator_keypairs.node_keypair, &validator_keypairs.vote_keypair, @@ -4452,10 +4450,9 @@ pub mod tests { .get(last_main_fork_slot - 1) .unwrap() .last_blockhash(); - let slots: Vec<_> = (expected_root_slot..last_main_fork_slot).collect(); - let vote_tx = vote_transaction::new_vote_transaction( - slots, - last_vote_bank_hash, + let tower_sync = TowerSync::new_from_slot(last_main_fork_slot - 1, last_vote_bank_hash); + let vote_tx = vote_transaction::new_tower_sync_transaction( + tower_sync, last_vote_blockhash, &validator_keypairs.node_keypair, &validator_keypairs.vote_keypair, @@ -4513,10 +4510,9 @@ pub mod tests { .get(last_main_fork_slot) .unwrap() .last_blockhash(); - let slots: Vec<_> = vec![last_main_fork_slot]; - let vote_tx = vote_transaction::new_vote_transaction( - slots, - last_vote_bank_hash, + let tower_sync = TowerSync::new_from_slot(last_main_fork_slot, last_vote_bank_hash); + let vote_tx = vote_transaction::new_tower_sync_transaction( + tower_sync, last_vote_blockhash, &leader_keypair, &validator_keypairs.vote_keypair, diff --git a/local-cluster/src/cluster_tests.rs b/local-cluster/src/cluster_tests.rs index b46fd67023d649..65aa539e32c9e6 100644 --- a/local-cluster/src/cluster_tests.rs +++ b/local-cluster/src/cluster_tests.rs @@ -37,7 +37,7 @@ use { solana_streamer::socket::SocketAddrSpace, solana_tpu_client::tpu_client::{TpuClient, TpuClientConfig, TpuSenderError}, solana_vote::vote_transaction::VoteTransaction, - solana_vote_program::vote_transaction, + solana_vote_program::{vote_state::TowerSync, vote_transaction}, std::{ collections::{HashMap, HashSet, VecDeque}, net::{IpAddr, Ipv4Addr, SocketAddr, TcpListener}, @@ -677,9 +677,9 @@ pub fn submit_vote_to_cluster_gossip( gossip_addr: SocketAddr, socket_addr_space: &SocketAddrSpace, ) -> Result<(), GossipError> { - let vote_tx = vote_transaction::new_vote_transaction( - vec![vote_slot], - vote_hash, + let tower_sync = TowerSync::new_from_slots(vec![vote_slot], vote_hash, None); + let vote_tx = vote_transaction::new_tower_sync_transaction( + tower_sync, blockhash, node_keypair, vote_keypair, diff --git a/local-cluster/tests/local_cluster.rs b/local-cluster/tests/local_cluster.rs index 62f7fd32435205..1e62835f91b1a2 100644 --- a/local-cluster/tests/local_cluster.rs +++ b/local-cluster/tests/local_cluster.rs @@ -2698,12 +2698,11 @@ fn test_oc_bad_signatures() { // Add all recent vote slots on this fork to allow cluster to pass // vote threshold checks in replay. Note this will instantly force a // root by this validator. - let vote_slots: Vec = vec![vote_slot]; + let tower_sync = TowerSync::new_from_slots(vec![vote_slot], vote_hash, None); let bad_authorized_signer_keypair = Keypair::new(); - let mut vote_tx = vote_transaction::new_vote_transaction( - vote_slots, - vote_hash, + let mut vote_tx = vote_transaction::new_tower_sync_transaction( + tower_sync, leader_vote_tx.message.recent_blockhash, &node_keypair, &vote_keypair, diff --git a/perf/src/test_tx.rs b/perf/src/test_tx.rs index befbc83206b281..118f8de15b4217 100644 --- a/perf/src/test_tx.rs +++ b/perf/src/test_tx.rs @@ -10,7 +10,7 @@ use { system_program, system_transaction, transaction::Transaction, }, - solana_vote_program::vote_transaction, + solana_vote_program::{vote_state::TowerSync, vote_transaction}, }; pub fn test_tx() -> Transaction { @@ -60,9 +60,9 @@ where slots.sort_unstable(); slots.dedup(); let switch_proof_hash = rng.gen_bool(0.5).then(Hash::new_unique); - vote_transaction::new_vote_transaction( - slots, - Hash::new_unique(), // bank_hash + let tower_sync = TowerSync::new_from_slots(slots, Hash::default(), None); + vote_transaction::new_tower_sync_transaction( + tower_sync, Hash::new_unique(), // blockhash &Keypair::new(), // node_keypair &Keypair::new(), // vote_keypair diff --git a/programs/vote/src/vote_processor.rs b/programs/vote/src/vote_processor.rs index 1b066be6e2125a..ea6514420cd0d2 100644 --- a/programs/vote/src/vote_processor.rs +++ b/programs/vote/src/vote_processor.rs @@ -478,7 +478,7 @@ mod tests { (vote_pubkey, vote_account_with_epoch_credits) } - /// Returns Vec of serialized VoteInstruction and flag indicating if it is a vote state proposal + /// Returns Vec of serialized VoteInstruction and flag indicating if it is a tower sync /// variant, along with the original vote fn create_serialized_votes() -> (Vote, Vec<(Vec, bool)>) { let vote = Vote::new(vec![1], Hash::default()); @@ -491,11 +491,11 @@ mod tests { ( serialize(&VoteInstruction::UpdateVoteState(vote_state_update.clone())) .unwrap(), - true, + false, ), ( serialize(&VoteInstruction::CompactUpdateVoteState(vote_state_update)).unwrap(), - true, + false, ), ( serialize(&VoteInstruction::TowerSync(tower_sync)).unwrap(), @@ -771,20 +771,28 @@ mod tests { }, ]; - for (instruction_data, is_vote_state_update) in instruction_datas { + for (instruction_data, is_tower_sync) in instruction_datas { let mut transaction_accounts = vec![ (vote_pubkey, vote_account.clone()), (sysvar::slot_hashes::id(), slot_hashes_account.clone()), (sysvar::clock::id(), create_default_clock_account()), ]; + let error = |err| { + if !is_tower_sync { + Err(InstructionError::InvalidInstructionData) + } else { + Err(err) + } + }; + // should fail, unsigned instruction_accounts[0].is_signer = false; process_instruction( &instruction_data, transaction_accounts.clone(), instruction_accounts.clone(), - Err(InstructionError::MissingRequiredSignature), + error(InstructionError::MissingRequiredSignature), ); instruction_accounts[0].is_signer = true; @@ -793,18 +801,24 @@ mod tests { &instruction_data, transaction_accounts.clone(), instruction_accounts.clone(), - Ok(()), - ); - let vote_state: VoteState = StateMut::::state(&accounts[0]) - .unwrap() - .convert_to_current(); - assert_eq!( - vote_state.votes, - vec![vote_state::LandedVote::from(Lockout::new( - *vote.slots.last().unwrap() - ))] + if is_tower_sync { + Ok(()) + } else { + Err(InstructionError::InvalidInstructionData) + }, ); - assert_eq!(vote_state.credits(), 0); + if is_tower_sync { + let vote_state: VoteState = StateMut::::state(&accounts[0]) + .unwrap() + .convert_to_current(); + assert_eq!( + vote_state.votes, + vec![vote_state::LandedVote::from(Lockout::new( + *vote.slots.last().unwrap() + ))] + ); + assert_eq!(vote_state.credits(), 0); + } // should fail, wrong hash transaction_accounts[1] = ( @@ -818,7 +832,7 @@ mod tests { &instruction_data, transaction_accounts.clone(), instruction_accounts.clone(), - Err(VoteError::SlotHashMismatch.into()), + error(VoteError::SlotHashMismatch.into()), ); // should fail, wrong slot @@ -830,7 +844,7 @@ mod tests { &instruction_data, transaction_accounts.clone(), instruction_accounts.clone(), - Err(VoteError::SlotsMismatch.into()), + error(VoteError::SlotsMismatch.into()), ); // should fail, empty slot_hashes @@ -842,12 +856,7 @@ mod tests { &instruction_data, transaction_accounts.clone(), instruction_accounts.clone(), - Err((if is_vote_state_update { - VoteError::SlotsMismatch - } else { - VoteError::VoteTooOld - }) - .into()), + error(VoteError::SlotsMismatch.into()), ); transaction_accounts[1] = (sysvar::slot_hashes::id(), slot_hashes_account.clone()); @@ -858,7 +867,7 @@ mod tests { &instruction_data, transaction_accounts.clone(), instruction_accounts.clone(), - Err(InstructionError::UninitializedAccount), + error(InstructionError::UninitializedAccount), ); } } @@ -968,12 +977,16 @@ mod tests { is_writable: false, }); - for (instruction_data, _) in instruction_datas { + for (instruction_data, is_tower_sync) in instruction_datas { process_instruction( &instruction_data, transaction_accounts.clone(), instruction_accounts.clone(), - Err(InstructionError::MissingRequiredSignature), + Err(if is_tower_sync { + InstructionError::MissingRequiredSignature + } else { + InstructionError::InvalidInstructionData + }), ); // should pass, signed by authorized voter @@ -981,7 +994,11 @@ mod tests { &instruction_data, transaction_accounts.clone(), authorized_instruction_accounts.clone(), - Ok(()), + if is_tower_sync { + Ok(()) + } else { + Err(InstructionError::InvalidInstructionData) + }, ); } } @@ -1842,7 +1859,7 @@ mod tests { &Pubkey::new_unique(), Vote::default(), ), - Err(InstructionError::InvalidAccountData), + Err(InstructionError::InvalidInstructionData), ); process_instruction_as_one_arg( &vote_switch( @@ -1851,7 +1868,7 @@ mod tests { Vote::default(), Hash::default(), ), - Err(InstructionError::InvalidAccountData), + Err(InstructionError::InvalidInstructionData), ); process_instruction_as_one_arg( &authorize( @@ -1868,7 +1885,7 @@ mod tests { &Pubkey::default(), VoteStateUpdate::default(), ), - Err(InstructionError::InvalidAccountData), + Err(InstructionError::InvalidInstructionData), ); process_instruction_as_one_arg( @@ -1878,7 +1895,7 @@ mod tests { VoteStateUpdate::default(), Hash::default(), ), - Err(InstructionError::InvalidAccountData), + Err(InstructionError::InvalidInstructionData), ); process_instruction_as_one_arg( &compact_update_vote_state( @@ -1886,7 +1903,7 @@ mod tests { &Pubkey::default(), VoteStateUpdate::default(), ), - Err(InstructionError::InvalidAccountData), + Err(InstructionError::InvalidInstructionData), ); process_instruction_as_one_arg( &compact_update_vote_state_switch( @@ -1895,7 +1912,7 @@ mod tests { VoteStateUpdate::default(), Hash::default(), ), - Err(InstructionError::InvalidAccountData), + Err(InstructionError::InvalidInstructionData), ); process_instruction_as_one_arg( &tower_sync(&Pubkey::default(), &Pubkey::default(), TowerSync::default()), diff --git a/rpc/src/rpc.rs b/rpc/src/rpc.rs index d62a61ec81fe00..db4d7bf9e69b53 100644 --- a/rpc/src/rpc.rs +++ b/rpc/src/rpc.rs @@ -4367,7 +4367,7 @@ pub mod tests { }, solana_vote_program::{ vote_instruction, - vote_state::{self, Vote, VoteInit, VoteStateVersions, MAX_LOCKOUT_HISTORY}, + vote_state::{self, TowerSync, VoteInit, VoteStateVersions, MAX_LOCKOUT_HISTORY}, }, spl_pod::optional_keys::OptionalNonZeroPubkey, spl_token_2022::{ @@ -7239,23 +7239,15 @@ pub mod tests { // Votes let instructions = [ - vote_instruction::vote( + vote_instruction::tower_sync( &leader_vote_keypair.pubkey(), &leader_vote_keypair.pubkey(), - Vote { - slots: vec![bank.slot()], - hash: bank.hash(), - timestamp: None, - }, + TowerSync::new_from_slot(bank.slot(), bank.hash()), ), - vote_instruction::vote( + vote_instruction::tower_sync( &alice_vote_keypair.pubkey(), &alice_vote_keypair.pubkey(), - Vote { - slots: vec![bank.slot()], - hash: bank.hash(), - timestamp: None, - }, + TowerSync::new_from_slot(bank.slot(), bank.hash()), ), ]; diff --git a/runtime/src/bank/partitioned_epoch_rewards/mod.rs b/runtime/src/bank/partitioned_epoch_rewards/mod.rs index 49622ba183a396..f8ad09e06f66ec 100644 --- a/runtime/src/bank/partitioned_epoch_rewards/mod.rs +++ b/runtime/src/bank/partitioned_epoch_rewards/mod.rs @@ -282,7 +282,10 @@ mod tests { transaction::Transaction, vote::state::{VoteStateVersions, MAX_LOCKOUT_HISTORY}, }, - solana_vote_program::{vote_state, vote_transaction}, + solana_vote_program::{ + vote_state::{self, TowerSync}, + vote_transaction, + }, }; impl PartitionedStakeReward { @@ -769,9 +772,9 @@ mod tests { // Fill bank_forks with banks with votes landing in the next slot // So that rewards will be paid out at the epoch boundary, i.e. slot = 32 - let vote = vote_transaction::new_vote_transaction( - vec![slot - 1], - previous_bank.hash(), + let tower_sync = TowerSync::new_from_slot(slot - 1, previous_bank.hash()); + let vote = vote_transaction::new_tower_sync_transaction( + tower_sync, previous_bank.last_blockhash(), &validator_vote_keypairs.node_keypair, &validator_vote_keypairs.vote_keypair, diff --git a/runtime/tests/stake.rs b/runtime/tests/stake.rs index 9922b8c9a5d075..edc3bd3a4befb0 100755 --- a/runtime/tests/stake.rs +++ b/runtime/tests/stake.rs @@ -28,7 +28,7 @@ use { solana_stake_program::stake_state, solana_vote_program::{ vote_instruction, - vote_state::{Vote, VoteInit, VoteState, VoteStateVersions}, + vote_state::{TowerSync, VoteInit, VoteState, VoteStateVersions, MAX_LOCKOUT_HISTORY}, }, std::sync::{Arc, RwLock}, }; @@ -72,6 +72,7 @@ fn fill_epoch_with_votes( bank_forks: &RwLock, vote_keypair: &Keypair, mint_keypair: &Keypair, + start_slot: Slot, ) -> Arc { let mint_pubkey = mint_keypair.pubkey(); let vote_pubkey = vote_keypair.pubkey(); @@ -83,12 +84,18 @@ fn fill_epoch_with_votes( let bank_client = BankClient::new_shared(bank.clone()); let parent = bank.parent().unwrap(); - + let lowest_slot = u64::max( + (parent.slot() + 1).saturating_sub(MAX_LOCKOUT_HISTORY as u64), + start_slot, + ); + let slots: Vec<_> = (lowest_slot..(parent.slot() + 1)).collect(); + let root = (lowest_slot > start_slot).then(|| lowest_slot - 1); + let tower_sync = TowerSync::new_from_slots(slots, parent.hash(), root); let message = Message::new( - &[vote_instruction::vote( + &[vote_instruction::tower_sync( &vote_pubkey, &vote_pubkey, - Vote::new(vec![parent.slot()], parent.hash()), + tower_sync, )], Some(&mint_pubkey), ); @@ -413,7 +420,14 @@ fn test_stake_account_lifetime() { // Reward redemption // Submit enough votes to generate rewards - bank = fill_epoch_with_votes(bank, bank_forks.as_ref(), &vote_keypair, &mint_keypair); + let start_slot = bank.slot(); + bank = fill_epoch_with_votes( + bank, + bank_forks.as_ref(), + &vote_keypair, + &mint_keypair, + start_slot, + ); // Test that votes and credits are there let account = bank.get_account(&vote_pubkey).expect("account not found"); @@ -426,7 +440,13 @@ fn test_stake_account_lifetime() { // one vote per slot, might be more slots than 32 in the epoch assert!(vote_state.credits() >= 1); - bank = fill_epoch_with_votes(bank, bank_forks.as_ref(), &vote_keypair, &mint_keypair); + bank = fill_epoch_with_votes( + bank, + bank_forks.as_ref(), + &vote_keypair, + &mint_keypair, + start_slot, + ); let pre_staked = get_staked(&bank, &stake_pubkey); let pre_balance = bank.get_balance(&stake_pubkey); diff --git a/sdk/program/src/vote/state/mod.rs b/sdk/program/src/vote/state/mod.rs index abac8f5abff61f..9a08f58f2907ef 100644 --- a/sdk/program/src/vote/state/mod.rs +++ b/sdk/program/src/vote/state/mod.rs @@ -277,6 +277,39 @@ impl TowerSync { } } + /// Creates a tower with consecutive votes for `slot - MAX_LOCKOUT_HISTORY + 1` to `slot` inclusive. + /// If `slot >= MAX_LOCKOUT_HISTORY`, sets the root to `(slot - MAX_LOCKOUT_HISTORY)` + /// Sets the hash to `hash` and leaves `block_id` unset. + pub fn new_from_slot(slot: Slot, hash: Hash) -> Self { + let lowest_slot = slot + .saturating_add(1) + .saturating_sub(MAX_LOCKOUT_HISTORY as u64); + let slots: Vec<_> = (lowest_slot..slot.saturating_add(1)).collect(); + Self::new_from_slots( + slots, + hash, + (lowest_slot > 0).then(|| lowest_slot.saturating_sub(1)), + ) + } + + /// Creates a tower with consecutive confirmation for `slots` + pub fn new_from_slots(slots: Vec, hash: Hash, root: Option) -> Self { + let lockouts: VecDeque = slots + .into_iter() + .rev() + .enumerate() + .map(|(cc, s)| Lockout::new_with_confirmation_count(s, cc.saturating_add(1) as u32)) + .rev() + .collect(); + Self { + lockouts, + hash, + root, + timestamp: None, + block_id: Hash::default(), + } + } + pub fn slots(&self) -> Vec { self.lockouts.iter().map(|lockout| lockout.slot()).collect() }