Compact vote state updates to reduce block size (#26616)

* Compact vote state updates to reduce block size

* Add rpc transaction tests
This commit is contained in:
Ashwin Sekar 2022-07-27 12:23:44 -07:00 committed by GitHub
parent 1a5b830294
commit 8d69e8d447
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 339 additions and 19 deletions

View File

@ -87,11 +87,22 @@ impl SwitchForkDecision {
v,
*switch_proof_hash,
)),
(SwitchForkDecision::SameFork, VoteTransaction::CompactVoteStateUpdate(_v)) => None,
(SwitchForkDecision::SameFork, VoteTransaction::CompactVoteStateUpdate(v)) => {
Some(vote_instruction::compact_update_vote_state(
vote_account_pubkey,
authorized_voter_pubkey,
v,
))
}
(
SwitchForkDecision::SwitchProof(_switch_proof_hash),
VoteTransaction::CompactVoteStateUpdate(_v),
) => None,
SwitchForkDecision::SwitchProof(switch_proof_hash),
VoteTransaction::CompactVoteStateUpdate(v),
) => Some(vote_instruction::compact_update_vote_state_switch(
vote_account_pubkey,
authorized_voter_pubkey,
v,
*switch_proof_hash,
)),
}
}

View File

@ -56,6 +56,7 @@ use {
},
solana_sdk::{
clock::{BankId, Slot, MAX_PROCESSING_AGE, NUM_CONSECUTIVE_LEADER_SLOTS},
feature_set,
genesis_config::ClusterType,
hash::Hash,
pubkey::Pubkey,
@ -64,7 +65,7 @@ use {
timing::timestamp,
transaction::Transaction,
},
solana_vote_program::vote_state::VoteTransaction,
solana_vote_program::vote_state::{CompactVoteStateUpdate, VoteTransaction},
std::{
collections::{HashMap, HashSet},
result,
@ -1980,6 +1981,16 @@ impl ReplayStage {
};
// Send our last few votes along with the new one
// Compact the vote state update before sending
let should_compact = bank
.feature_set
.is_active(&feature_set::compact_vote_state_updates::id());
let vote = match (should_compact, vote) {
(true, VoteTransaction::VoteStateUpdate(vote_state_update)) => {
VoteTransaction::from(CompactVoteStateUpdate::from(vote_state_update))
}
(_, vote) => vote,
};
let vote_ix = switch_fork_decision
.to_vote_instruction(
vote,

View File

@ -4,8 +4,8 @@ use {
crate::{
id,
vote_state::{
Vote, VoteAuthorize, VoteAuthorizeCheckedWithSeedArgs, VoteAuthorizeWithSeedArgs,
VoteInit, VoteState, VoteStateUpdate,
CompactVoteStateUpdate, Vote, VoteAuthorize, VoteAuthorizeCheckedWithSeedArgs,
VoteAuthorizeWithSeedArgs, VoteInit, VoteState, VoteStateUpdate,
},
},
serde_derive::{Deserialize, Serialize},
@ -103,6 +103,20 @@ pub enum VoteInstruction {
/// 1. `[SIGNER]` Vote authority
UpdateVoteStateSwitch(VoteStateUpdate, Hash),
/// Update the onchain vote state for the signer.
///
/// # Account references
/// 0. `[Write]` Vote account to vote with
/// 1. `[SIGNER]` Vote authority
CompactUpdateVoteState(CompactVoteStateUpdate),
/// Update the onchain vote state for the signer along with a switching proof.
///
/// # Account references
/// 0. `[Write]` Vote account to vote with
/// 1. `[SIGNER]` Vote authority
CompactUpdateVoteStateSwitch(CompactVoteStateUpdate, Hash),
/// Given that the current Voter or Withdrawer authority is a derived key,
/// this instruction allows someone who can sign for that derived key's
/// base key to authorize a new Voter or Withdrawer for a vote account.
@ -370,6 +384,41 @@ pub fn update_vote_state_switch(
)
}
pub fn compact_update_vote_state(
vote_pubkey: &Pubkey,
authorized_voter_pubkey: &Pubkey,
compact_vote_state_update: CompactVoteStateUpdate,
) -> Instruction {
let account_metas = vec![
AccountMeta::new(*vote_pubkey, false),
AccountMeta::new_readonly(*authorized_voter_pubkey, true),
];
Instruction::new_with_bincode(
id(),
&VoteInstruction::CompactUpdateVoteState(compact_vote_state_update),
account_metas,
)
}
pub fn compact_update_vote_state_switch(
vote_pubkey: &Pubkey,
authorized_voter_pubkey: &Pubkey,
vote_state_update: CompactVoteStateUpdate,
proof_hash: Hash,
) -> Instruction {
let account_metas = vec![
AccountMeta::new(*vote_pubkey, false),
AccountMeta::new_readonly(*authorized_voter_pubkey, true),
];
Instruction::new_with_bincode(
id(),
&VoteInstruction::CompactUpdateVoteStateSwitch(vote_state_update, proof_hash),
account_metas,
)
}
pub fn withdraw(
vote_pubkey: &Pubkey,
authorized_withdrawer_pubkey: &Pubkey,

View File

@ -4,7 +4,7 @@ use {
crate::{
id,
vote_instruction::VoteInstruction,
vote_state::{self, VoteAuthorize},
vote_state::{self, VoteAuthorize, VoteStateUpdate},
},
log::*,
solana_program_runtime::{
@ -173,6 +173,31 @@ pub fn process_instruction(
Err(InstructionError::InvalidInstructionData)
}
}
VoteInstruction::CompactUpdateVoteState(compact_vote_state_update)
| VoteInstruction::CompactUpdateVoteStateSwitch(compact_vote_state_update, _) => {
if invoke_context
.feature_set
.is_active(&feature_set::allow_votes_to_directly_update_vote_state::id())
&& invoke_context
.feature_set
.is_active(&feature_set::compact_vote_state_updates::id())
{
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_vote_state_update(
&mut me,
slot_hashes.slot_hashes(),
&clock,
VoteStateUpdate::from(compact_vote_state_update),
&signers,
&invoke_context.feature_set,
)
} else {
Err(InstructionError::InvalidInstructionData)
}
}
VoteInstruction::Withdraw(lamports) => {
instruction_context.check_number_of_instruction_accounts(2)?;
let rent_sysvar = invoke_context.get_sysvar_cache().get_rent()?;

View File

@ -7,7 +7,7 @@ use {
signature::Signature,
transaction::{SanitizedTransaction, Transaction},
},
solana_vote_program::vote_instruction::VoteInstruction,
solana_vote_program::{vote_instruction::VoteInstruction, vote_state::VoteStateUpdate},
};
pub type ParsedVote = (Pubkey, VoteTransaction, Option<Hash>, Signature);
@ -29,6 +29,8 @@ pub(crate) fn is_simple_vote_transaction(transaction: &SanitizedTransaction) ->
| VoteInstruction::VoteSwitch(_, _)
| VoteInstruction::UpdateVoteState(_)
| VoteInstruction::UpdateVoteStateSwitch(_, _)
| VoteInstruction::CompactUpdateVoteState(_)
| VoteInstruction::CompactUpdateVoteStateSwitch(..)
);
}
}
@ -80,6 +82,14 @@ fn parse_vote_instruction_data(
VoteInstruction::UpdateVoteStateSwitch(vote_state_update, hash) => {
Some((VoteTransaction::from(vote_state_update), Some(hash)))
}
VoteInstruction::CompactUpdateVoteState(compact_vote_state_update) => Some((
VoteTransaction::from(VoteStateUpdate::from(compact_vote_state_update)),
None,
)),
VoteInstruction::CompactUpdateVoteStateSwitch(compact_vote_state_update, hash) => Some((
VoteTransaction::from(VoteStateUpdate::from(compact_vote_state_update)),
Some(hash),
)),
VoteInstruction::Authorize(_, _)
| VoteInstruction::AuthorizeChecked(_)
| VoteInstruction::AuthorizeWithSeed(_)

View File

@ -476,6 +476,10 @@ pub mod use_default_units_in_fee_calculation {
solana_sdk::declare_id!("8sKQrMQoUHtQSUP83SPG4ta2JDjSAiWs7t5aJ9uEd6To");
}
pub mod compact_vote_state_updates {
solana_sdk::declare_id!("86HpNqzutEZwLcPxS6EHDcMNYWk6ikhteg9un7Y2PBKE");
}
lazy_static! {
/// Map of feature identifiers to user-visible description
pub static ref FEATURE_NAMES: HashMap<Pubkey, &'static str> = [
@ -589,6 +593,7 @@ lazy_static! {
(cap_bpf_program_instruction_accounts::id(), "enforce max number of accounts per bpf program instruction #26628"),
(loosen_cpi_size_restriction::id(), "loosen cpi size restrictions #26641"),
(use_default_units_in_fee_calculation::id(), "use default units per instruction in fee calculation #26785"),
(compact_vote_state_updates::id(), "Compact vote state updates to lower block size"),
/*************** ADD NEW FEATURES HERE ***************/
]
.iter()

View File

@ -5,7 +5,7 @@ use {
bincode::deserialize,
serde_json::json,
solana_sdk::{instruction::CompiledInstruction, message::AccountKeys},
solana_vote_program::vote_instruction::VoteInstruction,
solana_vote_program::{vote_instruction::VoteInstruction, vote_state::VoteStateUpdate},
};
pub fn parse_vote(
@ -101,7 +101,7 @@ pub fn parse_vote(
})
}
VoteInstruction::UpdateVoteState(vote_state_update) => {
check_num_vote_accounts(&instruction.accounts, 4)?;
check_num_vote_accounts(&instruction.accounts, 2)?;
let vote_state_update = json!({
"lockouts": vote_state_update.lockouts,
"root": vote_state_update.root,
@ -112,15 +112,13 @@ pub fn parse_vote(
instruction_type: "updatevotestate".to_string(),
info: json!({
"voteAccount": account_keys[instruction.accounts[0] as usize].to_string(),
"slotHashesSysvar": account_keys[instruction.accounts[1] as usize].to_string(),
"clockSysvar": account_keys[instruction.accounts[2] as usize].to_string(),
"voteAuthority": account_keys[instruction.accounts[3] as usize].to_string(),
"voteAuthority": account_keys[instruction.accounts[1] as usize].to_string(),
"voteStateUpdate": vote_state_update,
}),
})
}
VoteInstruction::UpdateVoteStateSwitch(vote_state_update, hash) => {
check_num_vote_accounts(&instruction.accounts, 4)?;
check_num_vote_accounts(&instruction.accounts, 2)?;
let vote_state_update = json!({
"lockouts": vote_state_update.lockouts,
"root": vote_state_update.root,
@ -131,9 +129,44 @@ pub fn parse_vote(
instruction_type: "updatevotestateswitch".to_string(),
info: json!({
"voteAccount": account_keys[instruction.accounts[0] as usize].to_string(),
"slotHashesSysvar": account_keys[instruction.accounts[1] as usize].to_string(),
"clockSysvar": account_keys[instruction.accounts[2] as usize].to_string(),
"voteAuthority": account_keys[instruction.accounts[3] as usize].to_string(),
"voteAuthority": account_keys[instruction.accounts[1] as usize].to_string(),
"voteStateUpdate": vote_state_update,
"hash": hash.to_string(),
}),
})
}
VoteInstruction::CompactUpdateVoteState(compact_vote_state_update) => {
let vote_state_update = VoteStateUpdate::from(compact_vote_state_update);
check_num_vote_accounts(&instruction.accounts, 2)?;
let vote_state_update = json!({
"lockouts": vote_state_update.lockouts,
"root": vote_state_update.root,
"hash": vote_state_update.hash.to_string(),
"timestamp": vote_state_update.timestamp,
});
Ok(ParsedInstructionEnum {
instruction_type: "compactupdatevotestate".to_string(),
info: json!({
"voteAccount": account_keys[instruction.accounts[0] as usize].to_string(),
"voteAuthority": account_keys[instruction.accounts[1] as usize].to_string(),
"voteStateUpdate": vote_state_update,
}),
})
}
VoteInstruction::CompactUpdateVoteStateSwitch(compact_vote_state_update, hash) => {
let vote_state_update = VoteStateUpdate::from(compact_vote_state_update);
check_num_vote_accounts(&instruction.accounts, 2)?;
let vote_state_update = json!({
"lockouts": vote_state_update.lockouts,
"root": vote_state_update.root,
"hash": vote_state_update.hash.to_string(),
"timestamp": vote_state_update.timestamp,
});
Ok(ParsedInstructionEnum {
instruction_type: "compactupdatevotestateswitch".to_string(),
info: json!({
"voteAccount": account_keys[instruction.accounts[0] as usize].to_string(),
"voteAuthority": account_keys[instruction.accounts[1] as usize].to_string(),
"voteStateUpdate": vote_state_update,
"hash": hash.to_string(),
}),
@ -219,7 +252,7 @@ mod test {
solana_sdk::{hash::Hash, message::Message, pubkey::Pubkey, sysvar},
solana_vote_program::{
vote_instruction,
vote_state::{Vote, VoteAuthorize, VoteInit},
vote_state::{CompactVoteStateUpdate, Vote, VoteAuthorize, VoteInit},
},
};
@ -642,4 +675,180 @@ mod test {
message.instructions[0].accounts.pop();
assert!(parse_vote(&message.instructions[0], &AccountKeys::new(&keys, None)).is_err());
}
#[test]
fn test_parse_vote_state_update_ix() {
let vote_state_update = VoteStateUpdate::from(vec![(0, 3), (1, 2), (2, 1)]);
let vote_pubkey = Pubkey::new_unique();
let authorized_voter_pubkey = Pubkey::new_unique();
let instruction = vote_instruction::update_vote_state(
&vote_pubkey,
&authorized_voter_pubkey,
vote_state_update.clone(),
);
let mut message = Message::new(&[instruction], None);
assert_eq!(
parse_vote(
&message.instructions[0],
&AccountKeys::new(&message.account_keys, None)
)
.unwrap(),
ParsedInstructionEnum {
instruction_type: "updatevotestate".to_string(),
info: json!({
"voteAccount": vote_pubkey.to_string(),
"voteAuthority": authorized_voter_pubkey.to_string(),
"voteStateUpdate": {
"lockouts": vote_state_update.lockouts,
"root": None::<u64>,
"hash": Hash::default().to_string(),
"timestamp": None::<u64>,
},
}),
}
);
assert!(parse_vote(
&message.instructions[0],
&AccountKeys::new(&message.account_keys[0..1], None)
)
.is_err());
let keys = message.account_keys.clone();
message.instructions[0].accounts.pop();
assert!(parse_vote(&message.instructions[0], &AccountKeys::new(&keys, None)).is_err());
}
#[test]
fn test_parse_vote_state_update_switch_ix() {
let vote_state_update = VoteStateUpdate::from(vec![(0, 3), (1, 2), (2, 1)]);
let vote_pubkey = Pubkey::new_unique();
let authorized_voter_pubkey = Pubkey::new_unique();
let proof_hash = Hash::new_from_array([2; 32]);
let instruction = vote_instruction::update_vote_state_switch(
&vote_pubkey,
&authorized_voter_pubkey,
vote_state_update.clone(),
proof_hash,
);
let mut message = Message::new(&[instruction], None);
assert_eq!(
parse_vote(
&message.instructions[0],
&AccountKeys::new(&message.account_keys, None)
)
.unwrap(),
ParsedInstructionEnum {
instruction_type: "updatevotestateswitch".to_string(),
info: json!({
"voteAccount": vote_pubkey.to_string(),
"voteAuthority": authorized_voter_pubkey.to_string(),
"voteStateUpdate": {
"lockouts": vote_state_update.lockouts,
"root": None::<u64>,
"hash": Hash::default().to_string(),
"timestamp": None::<u64>,
},
"hash": proof_hash.to_string(),
}),
}
);
assert!(parse_vote(
&message.instructions[0],
&AccountKeys::new(&message.account_keys[0..1], None)
)
.is_err());
let keys = message.account_keys.clone();
message.instructions[0].accounts.pop();
assert!(parse_vote(&message.instructions[0], &AccountKeys::new(&keys, None)).is_err());
}
#[test]
fn test_parse_compact_vote_state_update_ix() {
let vote_state_update = VoteStateUpdate::from(vec![(0, 3), (1, 2), (2, 1)]);
let compact_vote_state_update = CompactVoteStateUpdate::from(vote_state_update.clone());
let vote_pubkey = Pubkey::new_unique();
let authorized_voter_pubkey = Pubkey::new_unique();
let instruction = vote_instruction::compact_update_vote_state(
&vote_pubkey,
&authorized_voter_pubkey,
compact_vote_state_update,
);
let mut message = Message::new(&[instruction], None);
assert_eq!(
parse_vote(
&message.instructions[0],
&AccountKeys::new(&message.account_keys, None)
)
.unwrap(),
ParsedInstructionEnum {
instruction_type: "compactupdatevotestate".to_string(),
info: json!({
"voteAccount": vote_pubkey.to_string(),
"voteAuthority": authorized_voter_pubkey.to_string(),
"voteStateUpdate": {
"lockouts": vote_state_update.lockouts,
"root": None::<u64>,
"hash": Hash::default().to_string(),
"timestamp": None::<u64>,
},
}),
}
);
assert!(parse_vote(
&message.instructions[0],
&AccountKeys::new(&message.account_keys[0..1], None)
)
.is_err());
let keys = message.account_keys.clone();
message.instructions[0].accounts.pop();
assert!(parse_vote(&message.instructions[0], &AccountKeys::new(&keys, None)).is_err());
}
#[test]
fn test_parse_compact_vote_state_update_switch_ix() {
let vote_state_update = VoteStateUpdate::from(vec![(0, 3), (1, 2), (2, 1)]);
let compact_vote_state_update = CompactVoteStateUpdate::from(vote_state_update.clone());
let vote_pubkey = Pubkey::new_unique();
let authorized_voter_pubkey = Pubkey::new_unique();
let proof_hash = Hash::new_from_array([2; 32]);
let instruction = vote_instruction::compact_update_vote_state_switch(
&vote_pubkey,
&authorized_voter_pubkey,
compact_vote_state_update,
proof_hash,
);
let mut message = Message::new(&[instruction], None);
assert_eq!(
parse_vote(
&message.instructions[0],
&AccountKeys::new(&message.account_keys, None)
)
.unwrap(),
ParsedInstructionEnum {
instruction_type: "compactupdatevotestateswitch".to_string(),
info: json!({
"voteAccount": vote_pubkey.to_string(),
"voteAuthority": authorized_voter_pubkey.to_string(),
"voteStateUpdate": {
"lockouts": vote_state_update.lockouts,
"root": None::<u64>,
"hash": Hash::default().to_string(),
"timestamp": None::<u64>,
},
"hash": proof_hash.to_string(),
}),
}
);
assert!(parse_vote(
&message.instructions[0],
&AccountKeys::new(&message.account_keys[0..1], None)
)
.is_err());
let keys = message.account_keys.clone();
message.instructions[0].accounts.pop();
assert!(parse_vote(&message.instructions[0], &AccountKeys::new(&keys, None)).is_err());
}
}