Skip to content

DeactivateDelinquent for Closed Vote Accounts [SIMD-0608] - #532

Open
grod220 wants to merge 10 commits into
mainfrom
dd-closed-vt-accnt
Open

grod220 wants to merge 10 commits into
mainfrom
dd-closed-vt-accnt

Conversation

@grod220

@grod220 grod220 commented Sep 9, 2026 •

Copy link
Copy Markdown
Member

Implements SIMD-0608. The DeactivateDelinquent instruction now also accepts stake delegated to a closed vote account, restoring a cleanup path for delegations orphaned in this edge case.

New Behavior

The delinquency condition is satisfied when the delegated vote account:

  • is not owned by the Vote Program (the normal result of closure) or
  • is Vote program owned with either a) zero four-byte discriminator, b) all zero data shorter than four bytes, or c) empty data. Only the discriminator space is inspected, so a large zero-filled shell stays within the compute budget.

All other Vote owned data goes through the existing vote-state decoding and five-epoch credit checks.

Comment thread interface/src/tools.rs
Comment on lines +50 to +68
/// Deserialize a Vote Program-owned account as [`VoteStateV4`].
#[cfg(feature = "bincode")]
pub fn get_vote_state(vote_account_info: &AccountInfo) -> Result<Box<VoteStateV4>, ProgramError> {
if *vote_account_info.owner != solana_vote_interface::program::id() {
return Err(ProgramError::IncorrectProgramId);
}

let mut vote_state = Box::new(MaybeUninit::uninit());
VoteStateV4::deserialize_into_uninit(
&vote_account_info.try_borrow_data()?,
vote_state.as_mut(),
vote_account_info.key,
)
.map_err(|_| ProgramError::InvalidAccountData)?;
let vote_state = unsafe { Box::from_raw(Box::into_raw(vote_state).cast::<VoteStateV4>()) };

Ok(vote_state)
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved from program/src/processor.rs so helpers here can use it

Comment thread program/src/processor.rs
Comment on lines -1211 to -1234
if let StakeStateV2::Stake(meta, mut stake, stake_flags) =
let StakeStateV2::Stake(meta, mut stake, stake_flags) =
get_stake_state(stake_account_info)?
{
if stake.delegation.voter_pubkey != *delinquent_vote_account_info.key {
return Err(StakeError::VoteAddressMismatch.into());
}
else {
return Err(ProgramError::InvalidAccountData);
};

// Deactivate the stake account if its delegated vote account has never voted or
// has not voted in the last
// `MINIMUM_DELINQUENT_EPOCHS_FOR_DEACTIVATION`
if eligible_for_deactivate_delinquent(&delinquent_vote_state.epoch_credits, clock.epoch)
{
stake.deactivate(clock.epoch)?;
if stake.delegation.voter_pubkey != *delinquent_vote_account_info.key {
return Err(StakeError::VoteAddressMismatch.into());
}

set_stake_state(
stake_account_info,
&StakeStateV2::Stake(meta, stake, stake_flags),
)
} else {
Err(StakeError::MinimumDelinquentEpochsForDeactivationNotMet.into())
}
} else {
Err(ProgramError::InvalidAccountData)
}?;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inverted some of this control flow for easier reading

Comment on lines +451 to +460
&Pubkey::new_unique(),
),
Err(ProgramError::IncorrectProgramId),
);
process_instruction_as_one_arg(
&mollusk,
&instruction::deactivate_delinquent_stake(
&Pubkey::new_unique(),
&invalid_vote_state_pubkey(),
&Pubkey::new_unique(),
&invalid_vote_state_pubkey(),

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error order changes because the reference vote account is now checked before the delinquent account in the processor.

@joncinque joncinque left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great overall! Just a few small questions

Comment thread interface/src/tools.rs Resolved
Comment thread interface/src/tools.rs Resolved
Comment thread interface/src/tools.rs Resolved
Comment thread interface/src/tools.rs Resolved
Comment thread interface/src/tools.rs Resolved
Comment thread interface/Cargo.toml Resolved
Comment thread program/Cargo.toml Resolved
Comment thread program/tests/stake_instruction.rs Resolved
Comment thread program/tests/stake_instruction.rs Resolved

@joncinque joncinque left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, just a couple more things that I noticed

Comment thread interface/src/tools.rs Resolved
Comment thread Cargo.lock Resolved
@grod220

grod220 commented Sep 15, 2026

Copy link
Copy Markdown
Member Author

Given my upcoming leave and need to shepherd the feature gate in, moving this to draft until beginning of Nov

@grod220
grod220 marked this pull request as draft September 15, 2026 14:42
@grod220
grod220 force-pushed the dd-closed-vt-accnt branch from 3787289 to 171adf5 Compare November 3, 2026 13:49
@grod220
grod220 force-pushed the dd-closed-vt-accnt branch from 171adf5 to ddf7128 Compare November 3, 2026 13:52
@grod220

grod220 commented Nov 3, 2026

Copy link
Copy Markdown
Member Author

A few updates since returning on this:

  • Rebased on latest main
  • vote-interface helpers now support wincode alone, so can match those cfg attributes
  • Reviewed impact of VoteStateV5. This PR can land independently of that work. The current proposal specifically calls out a) retaining the epoch information needed by the existing checks and b) the coordinated upgrade needed for DeactivateDelinquent regardless of whether this PR lands first. The closure conditions are still applicable. That said, if VoteStateV5 is coming soon, it seems practical to bundle the stake program release w/ both SIMD changes.

@grod220
grod220 marked this pull request as ready for review November 3, 2026 13:55
@grod220
grod220 requested a review from joncinque November 3, 2026 13:56
Comment thread interface/src/tools.rs
address: &Pubkey,
owner: &Pubkey,
data: &[u8],
) -> Result<Box<VoteStateV4>, ProgramError> {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With VoteStateV5, we'll have to have a v2 of this fn. Given the breaking change, I suspect we'll need to have a custom type that handles both the v4 and v5 deserialization and differing field accessing cases for us.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants