vote: add TowerSync ix (#365)

* vote: add TowerSync ix

* fork_id -> block_id
This commit is contained in:
Ashwin Sekar 2024-04-02 10:02:10 -07:00 committed by GitHub
parent 64765bf817
commit a468ff2999
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 318 additions and 8 deletions

View File

@ -97,6 +97,9 @@ impl SwitchForkDecision {
v,
))
}
(SwitchForkDecision::SameFork, VoteTransaction::TowerSync(t)) => Some(
vote_instruction::tower_sync(vote_account_pubkey, authorized_voter_pubkey, t),
),
(SwitchForkDecision::SwitchProof(switch_proof_hash), VoteTransaction::Vote(v)) => {
Some(vote_instruction::vote_switch(
vote_account_pubkey,
@ -114,6 +117,14 @@ impl SwitchForkDecision {
v,
*switch_proof_hash,
)),
(SwitchForkDecision::SwitchProof(switch_proof_hash), VoteTransaction::TowerSync(t)) => {
Some(vote_instruction::tower_sync_switch(
vote_account_pubkey,
authorized_voter_pubkey,
t,
*switch_proof_hash,
))
}
(SwitchForkDecision::SameFork, VoteTransaction::CompactVoteStateUpdate(v)) => {
Some(vote_instruction::compact_update_vote_state(
vote_account_pubkey,
@ -221,7 +232,7 @@ pub(crate) enum BlockhashStatus {
Blockhash(Hash),
}
#[frozen_abi(digest = "iZi6s9BvytU3HbRsibrAD71jwMLvrqHdCjVk6qKcVvd")]
#[frozen_abi(digest = "679XkZ4upGc389SwqAsjs5tr2qB4wisqjbwtei7fGhxC")]
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, AbiExample)]
pub struct Tower {
pub node_pubkey: Pubkey,

View File

@ -6,7 +6,7 @@ use {
},
};
#[frozen_abi(digest = "F83xHQA1wxoFDy25MTKXXmFXTc9Jbp6SXRXEPcehtKbQ")]
#[frozen_abi(digest = "4LayQwoKrE2jPhbNtg3TSpKrtEtjcPiwsVPJN7aCavri")]
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, AbiExample)]
pub struct Tower1_14_11 {
pub(crate) node_pubkey: Pubkey,

View File

@ -9,6 +9,7 @@ use {
sysvar_cache::get_sysvar_with_account_check,
},
solana_sdk::{
feature_set,
instruction::InstructionError,
program_utils::limited_deserialize,
pubkey::Pubkey,
@ -192,7 +193,17 @@ declare_process_instruction!(Entrypoint, DEFAULT_COMPUTE_UNITS, |invoke_context|
&invoke_context.feature_set,
)
}
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);
}
VoteInstruction::Withdraw(lamports) => {
instruction_context.check_number_of_instruction_accounts(2)?;
let rent_sysvar = invoke_context.get_sysvar_cache().get_rent()?;

View File

@ -28,13 +28,15 @@ use {
},
};
#[frozen_abi(digest = "2AuJFjx7SYrJ2ugCfH1jFh3Lr9UHMEPfKwwk1NcjqND1")]
#[frozen_abi(digest = "EcS3xgfomytEAQ1eVd8R76ZejwyHp2Ed8dHqQWh6zi5v")]
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, AbiEnumVisitor, AbiExample)]
pub enum VoteTransaction {
Vote(Vote),
VoteStateUpdate(VoteStateUpdate),
#[serde(with = "serde_compact_vote_state_update")]
CompactVoteStateUpdate(VoteStateUpdate),
#[serde(with = "serde_tower_sync")]
TowerSync(TowerSync),
}
impl VoteTransaction {
@ -43,6 +45,7 @@ impl VoteTransaction {
VoteTransaction::Vote(vote) => vote.slots.clone(),
VoteTransaction::VoteStateUpdate(vote_state_update) => vote_state_update.slots(),
VoteTransaction::CompactVoteStateUpdate(vote_state_update) => vote_state_update.slots(),
VoteTransaction::TowerSync(tower_sync) => tower_sync.slots(),
}
}
@ -53,6 +56,7 @@ impl VoteTransaction {
| VoteTransaction::CompactVoteStateUpdate(vote_state_update) => {
vote_state_update.lockouts[i].slot()
}
VoteTransaction::TowerSync(tower_sync) => tower_sync.lockouts[i].slot(),
}
}
@ -63,6 +67,7 @@ impl VoteTransaction {
| VoteTransaction::CompactVoteStateUpdate(vote_state_update) => {
vote_state_update.lockouts.len()
}
VoteTransaction::TowerSync(tower_sync) => tower_sync.lockouts.len(),
}
}
@ -73,6 +78,7 @@ impl VoteTransaction {
| VoteTransaction::CompactVoteStateUpdate(vote_state_update) => {
vote_state_update.lockouts.is_empty()
}
VoteTransaction::TowerSync(tower_sync) => tower_sync.lockouts.is_empty(),
}
}
@ -81,6 +87,7 @@ impl VoteTransaction {
VoteTransaction::Vote(vote) => vote.hash,
VoteTransaction::VoteStateUpdate(vote_state_update) => vote_state_update.hash,
VoteTransaction::CompactVoteStateUpdate(vote_state_update) => vote_state_update.hash,
VoteTransaction::TowerSync(tower_sync) => tower_sync.hash,
}
}
@ -91,6 +98,7 @@ impl VoteTransaction {
| VoteTransaction::CompactVoteStateUpdate(vote_state_update) => {
vote_state_update.timestamp
}
VoteTransaction::TowerSync(tower_sync) => tower_sync.timestamp,
}
}
@ -101,6 +109,7 @@ impl VoteTransaction {
| VoteTransaction::CompactVoteStateUpdate(vote_state_update) => {
vote_state_update.timestamp = ts
}
VoteTransaction::TowerSync(tower_sync) => tower_sync.timestamp = ts,
}
}
@ -111,6 +120,7 @@ impl VoteTransaction {
| VoteTransaction::CompactVoteStateUpdate(vote_state_update) => {
vote_state_update.last_voted_slot()
}
VoteTransaction::TowerSync(tower_sync) => tower_sync.last_voted_slot(),
}
}
@ -131,6 +141,12 @@ impl From<VoteStateUpdate> for VoteTransaction {
}
}
impl From<TowerSync> for VoteTransaction {
fn from(tower_sync: TowerSync) -> Self {
VoteTransaction::TowerSync(tower_sync)
}
}
// utility function, used by Stakes, tests
pub fn from<T: ReadableAccount>(account: &T) -> Option<VoteState> {
VoteState::deserialize(account.data()).ok()

View File

@ -1,6 +1,7 @@
//! Vote program instructions
use {
super::state::TowerSync,
crate::{
clock::{Slot, UnixTimestamp},
hash::Hash,
@ -10,7 +11,7 @@ use {
vote::{
program::id,
state::{
serde_compact_vote_state_update, Vote, VoteAuthorize,
serde_compact_vote_state_update, serde_tower_sync, Vote, VoteAuthorize,
VoteAuthorizeCheckedWithSeedArgs, VoteAuthorizeWithSeedArgs, VoteInit,
VoteStateUpdate, VoteStateVersions,
},
@ -146,6 +147,21 @@ pub enum VoteInstruction {
#[serde(with = "serde_compact_vote_state_update")] VoteStateUpdate,
Hash,
),
/// Sync the onchain vote state with local tower
///
/// # Account references
/// 0. `[Write]` Vote account to vote with
/// 1. `[SIGNER]` Vote authority
#[serde(with = "serde_tower_sync")]
TowerSync(TowerSync),
/// Sync the onchain vote state with local tower along with a switching proof
///
/// # Account references
/// 0. `[Write]` Vote account to vote with
/// 1. `[SIGNER]` Vote authority
TowerSyncSwitch(#[serde(with = "serde_tower_sync")] TowerSync, Hash),
}
impl VoteInstruction {
@ -157,7 +173,9 @@ impl VoteInstruction {
| Self::UpdateVoteState(_)
| Self::UpdateVoteStateSwitch(_, _)
| Self::CompactUpdateVoteState(_)
| Self::CompactUpdateVoteStateSwitch(_, _),
| Self::CompactUpdateVoteStateSwitch(_, _)
| Self::TowerSync(_)
| Self::TowerSyncSwitch(_, _),
)
}
@ -167,7 +185,9 @@ impl VoteInstruction {
Self::UpdateVoteState(_)
| Self::UpdateVoteStateSwitch(_, _)
| Self::CompactUpdateVoteState(_)
| Self::CompactUpdateVoteStateSwitch(_, _),
| Self::CompactUpdateVoteStateSwitch(_, _)
| Self::TowerSync(_)
| Self::TowerSyncSwitch(_, _),
)
}
@ -182,6 +202,9 @@ impl VoteInstruction {
| Self::CompactUpdateVoteStateSwitch(vote_state_update, _) => {
vote_state_update.last_voted_slot()
}
Self::TowerSync(tower_sync) | Self::TowerSyncSwitch(tower_sync, _) => {
tower_sync.last_voted_slot()
}
_ => panic!("Tried to get slot on non simple vote instruction"),
}
}
@ -197,6 +220,9 @@ impl VoteInstruction {
| Self::CompactUpdateVoteStateSwitch(vote_state_update, _) => {
vote_state_update.timestamp
}
Self::TowerSync(tower_sync) | Self::TowerSyncSwitch(tower_sync, _) => {
tower_sync.timestamp
}
_ => panic!("Tried to get timestamp on non simple vote instruction"),
}
}
@ -514,6 +540,37 @@ pub fn compact_update_vote_state_switch(
)
}
pub fn tower_sync(
vote_pubkey: &Pubkey,
authorized_voter_pubkey: &Pubkey,
tower_sync: TowerSync,
) -> Instruction {
let account_metas = vec![
AccountMeta::new(*vote_pubkey, false),
AccountMeta::new_readonly(*authorized_voter_pubkey, true),
];
Instruction::new_with_bincode(id(), &VoteInstruction::TowerSync(tower_sync), account_metas)
}
pub fn tower_sync_switch(
vote_pubkey: &Pubkey,
authorized_voter_pubkey: &Pubkey,
tower_sync: TowerSync,
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::TowerSyncSwitch(tower_sync, proof_hash),
account_metas,
)
}
pub fn withdraw(
vote_pubkey: &Pubkey,
authorized_withdrawer_pubkey: &Pubkey,

View File

@ -207,6 +207,66 @@ impl VoteStateUpdate {
}
}
#[frozen_abi(digest = "5VUusSTenF9vZ9eHiCprVe9ABJUHCubeDNCCDxykybZY")]
#[derive(Serialize, Default, Deserialize, Debug, PartialEq, Eq, Clone, AbiExample)]
pub struct TowerSync {
/// The proposed tower
pub lockouts: VecDeque<Lockout>,
/// The proposed root
pub root: Option<Slot>,
/// signature of the bank's state at the last slot
pub hash: Hash,
/// processing timestamp of last slot
pub timestamp: Option<UnixTimestamp>,
/// the unique identifier for the chain up to and
/// including this block. Does not require replaying
/// in order to compute.
pub block_id: Hash,
}
impl From<Vec<(Slot, u32)>> for TowerSync {
fn from(recent_slots: Vec<(Slot, u32)>) -> Self {
let lockouts: VecDeque<Lockout> = recent_slots
.into_iter()
.map(|(slot, confirmation_count)| {
Lockout::new_with_confirmation_count(slot, confirmation_count)
})
.collect();
Self {
lockouts,
root: None,
hash: Hash::default(),
timestamp: None,
block_id: Hash::default(),
}
}
}
impl TowerSync {
pub fn new(
lockouts: VecDeque<Lockout>,
root: Option<Slot>,
hash: Hash,
block_id: Hash,
) -> Self {
Self {
lockouts,
root,
hash,
timestamp: None,
block_id,
}
}
pub fn slots(&self) -> Vec<Slot> {
self.lockouts.iter().map(|lockout| lockout.slot()).collect()
}
pub fn last_voted_slot(&self) -> Option<Slot> {
self.lockouts.back().map(|l| l.slot())
}
}
#[derive(Default, Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy)]
pub struct VoteInit {
pub node_pubkey: Pubkey,
@ -904,6 +964,103 @@ pub mod serde_compact_vote_state_update {
}
}
pub mod serde_tower_sync {
use {
super::*,
crate::{
clock::{Slot, UnixTimestamp},
serde_varint, short_vec,
vote::state::Lockout,
},
serde::{Deserialize, Deserializer, Serialize, Serializer},
};
#[derive(Deserialize, Serialize, AbiExample)]
struct LockoutOffset {
#[serde(with = "serde_varint")]
offset: Slot,
confirmation_count: u8,
}
#[derive(Deserialize, Serialize)]
struct CompactTowerSync {
root: Slot,
#[serde(with = "short_vec")]
lockout_offsets: Vec<LockoutOffset>,
hash: Hash,
timestamp: Option<UnixTimestamp>,
block_id: Hash,
}
pub fn serialize<S>(tower_sync: &TowerSync, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let lockout_offsets = tower_sync.lockouts.iter().scan(
tower_sync.root.unwrap_or_default(),
|slot, lockout| {
let Some(offset) = lockout.slot().checked_sub(*slot) else {
return Some(Err(serde::ser::Error::custom("Invalid vote lockout")));
};
let Ok(confirmation_count) = u8::try_from(lockout.confirmation_count()) else {
return Some(Err(serde::ser::Error::custom("Invalid confirmation count")));
};
let lockout_offset = LockoutOffset {
offset,
confirmation_count,
};
*slot = lockout.slot();
Some(Ok(lockout_offset))
},
);
let compact_tower_sync = CompactTowerSync {
root: tower_sync.root.unwrap_or(Slot::MAX),
lockout_offsets: lockout_offsets.collect::<Result<_, _>>()?,
hash: tower_sync.hash,
timestamp: tower_sync.timestamp,
block_id: tower_sync.block_id,
};
compact_tower_sync.serialize(serializer)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<TowerSync, D::Error>
where
D: Deserializer<'de>,
{
let CompactTowerSync {
root,
lockout_offsets,
hash,
timestamp,
block_id,
} = CompactTowerSync::deserialize(deserializer)?;
let root = (root != Slot::MAX).then_some(root);
let lockouts =
lockout_offsets
.iter()
.scan(root.unwrap_or_default(), |slot, lockout_offset| {
*slot = match slot.checked_add(lockout_offset.offset) {
None => {
return Some(Err(serde::de::Error::custom("Invalid lockout offset")))
}
Some(slot) => slot,
};
let lockout = Lockout::new_with_confirmation_count(
*slot,
u32::from(lockout_offset.confirmation_count),
);
Some(Ok(lockout))
});
Ok(TowerSync {
root,
lockouts: lockouts.collect::<Result<_, _>>()?,
hash,
timestamp,
block_id,
})
}
}
#[cfg(test)]
mod tests {
use {super::*, itertools::Itertools, rand::Rng};

View File

@ -781,6 +781,10 @@ pub mod remove_rounding_in_fee_calculation {
solana_sdk::declare_id!("BtVN7YjDzNE6Dk7kTT7YTDgMNUZTNgiSJgsdzAeTg2jF");
}
pub mod enable_tower_sync_ix {
solana_sdk::declare_id!("tSynMCspg4xFiCj1v3TDb4c7crMR5tSBhLz4sF7rrNA");
}
pub mod deprecate_unused_legacy_vote_plumbing {
solana_sdk::declare_id!("6Uf8S75PVh91MYgPQSHnjRAPQq6an5BDv9vomrCwDqLe");
}
@ -976,6 +980,7 @@ lazy_static! {
(enable_chained_merkle_shreds::id(), "Enable chained Merkle shreds #34916"),
(remove_rounding_in_fee_calculation::id(), "Removing unwanted rounding in fee calculation #34982"),
(deprecate_unused_legacy_vote_plumbing::id(), "Deprecate unused legacy vote tx plumbing"),
(enable_tower_sync_ix::id(), "Enable tower sync vote instruction"),
/*************** ADD NEW FEATURES HERE ***************/
]
.iter()

View File

@ -171,6 +171,43 @@ pub fn parse_vote(
}),
})
}
VoteInstruction::TowerSync(tower_sync) => {
check_num_vote_accounts(&instruction.accounts, 2)?;
let tower_sync = json!({
"lockouts": tower_sync.lockouts,
"root": tower_sync.root,
"hash": tower_sync.hash.to_string(),
"timestamp": tower_sync.timestamp,
"blockId": tower_sync.block_id,
});
Ok(ParsedInstructionEnum {
instruction_type: "towersync".to_string(),
info: json!({
"voteAccount": account_keys[instruction.accounts[0] as usize].to_string(),
"voteAuthority": account_keys[instruction.accounts[1] as usize].to_string(),
"towerSync": tower_sync,
}),
})
}
VoteInstruction::TowerSyncSwitch(tower_sync, hash) => {
check_num_vote_accounts(&instruction.accounts, 2)?;
let tower_sync = json!({
"lockouts": tower_sync.lockouts,
"root": tower_sync.root,
"hash": tower_sync.hash.to_string(),
"timestamp": tower_sync.timestamp,
"blockId": tower_sync.block_id,
});
Ok(ParsedInstructionEnum {
instruction_type: "towersyncswitch".to_string(),
info: json!({
"voteAccount": account_keys[instruction.accounts[0] as usize].to_string(),
"voteAuthority": account_keys[instruction.accounts[1] as usize].to_string(),
"towerSync": tower_sync,
"hash": hash.to_string(),
}),
})
}
VoteInstruction::Withdraw(lamports) => {
check_num_vote_accounts(&instruction.accounts, 3)?;
Ok(ParsedInstructionEnum {

View File

@ -62,6 +62,10 @@ fn parse_vote_instruction_data(
VoteInstruction::CompactUpdateVoteStateSwitch(vote_state_update, hash) => {
Some((VoteTransaction::from(vote_state_update), Some(hash)))
}
VoteInstruction::TowerSync(tower_sync) => Some((VoteTransaction::from(tower_sync), None)),
VoteInstruction::TowerSyncSwitch(tower_sync, hash) => {
Some((VoteTransaction::from(tower_sync), Some(hash)))
}
VoteInstruction::Authorize(_, _)
| VoteInstruction::AuthorizeChecked(_)
| VoteInstruction::AuthorizeWithSeed(_)

View File

@ -3,13 +3,14 @@ use {
clock::{Slot, UnixTimestamp},
hash::Hash,
},
solana_vote_program::vote_state::{Vote, VoteStateUpdate},
solana_vote_program::vote_state::{TowerSync, Vote, VoteStateUpdate},
};
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum VoteTransaction {
Vote(Vote),
VoteStateUpdate(VoteStateUpdate),
TowerSync(TowerSync),
}
impl VoteTransaction {
@ -21,6 +22,7 @@ impl VoteTransaction {
.iter()
.map(|lockout| lockout.slot())
.collect(),
VoteTransaction::TowerSync(tower_sync) => tower_sync.slots(),
}
}
@ -30,6 +32,7 @@ impl VoteTransaction {
VoteTransaction::VoteStateUpdate(vote_state_update) => {
vote_state_update.lockouts.is_empty()
}
VoteTransaction::TowerSync(tower_sync) => tower_sync.lockouts.is_empty(),
}
}
@ -37,6 +40,7 @@ impl VoteTransaction {
match self {
VoteTransaction::Vote(vote) => vote.hash,
VoteTransaction::VoteStateUpdate(vote_state_update) => vote_state_update.hash,
VoteTransaction::TowerSync(tower_sync) => tower_sync.hash,
}
}
@ -44,6 +48,7 @@ impl VoteTransaction {
match self {
VoteTransaction::Vote(vote) => vote.timestamp,
VoteTransaction::VoteStateUpdate(vote_state_update) => vote_state_update.timestamp,
VoteTransaction::TowerSync(tower_sync) => tower_sync.timestamp,
}
}
@ -53,6 +58,7 @@ impl VoteTransaction {
VoteTransaction::VoteStateUpdate(vote_state_update) => {
Some(vote_state_update.lockouts.back()?.slot())
}
VoteTransaction::TowerSync(tower_sync) => tower_sync.last_voted_slot(),
}
}
@ -72,3 +78,9 @@ impl From<VoteStateUpdate> for VoteTransaction {
VoteTransaction::VoteStateUpdate(vote_state_update)
}
}
impl From<TowerSync> for VoteTransaction {
fn from(tower_sync: TowerSync) -> Self {
VoteTransaction::TowerSync(tower_sync)
}
}