Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions mev-programs/Anchor.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ test = "yarn ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.{ts,js}"

[test.validator]
slots_per_epoch = "32"
bind_address = "127.0.0.1"

[features]
resolution = false
Expand Down
5 changes: 3 additions & 2 deletions mev-programs/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions mev-programs/programs/priority-fee-distribution/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,9 @@ pub mod jito_priority_fee_distribution {
return Err(MaxValidatorCommissionFeeBpsExceeded.into());
}

let validator_vote_state = VoteState::deserialize(&ctx.accounts.validator_vote_account)?;
if &validator_vote_state.node_pubkey != ctx.accounts.signer.key {
let validator_vote_account_node_pubkey =
VoteState::deserialize_node_pubkey(&ctx.accounts.validator_vote_account)?;
Comment thread
buffalu marked this conversation as resolved.
if validator_vote_account_node_pubkey != *ctx.accounts.signer.key {
return Err(Unauthorized.into());
}

Expand Down
5 changes: 3 additions & 2 deletions mev-programs/programs/tip-distribution/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,9 @@ pub mod jito_tip_distribution {
return Err(MaxValidatorCommissionFeeBpsExceeded.into());
}

let validator_vote_state = VoteState::deserialize(&ctx.accounts.validator_vote_account)?;
if &validator_vote_state.node_pubkey != ctx.accounts.signer.key {
let validator_vote_account_node_pubkey =
VoteState::deserialize_node_pubkey(&ctx.accounts.validator_vote_account)?;
if validator_vote_account_node_pubkey != *ctx.accounts.signer.key {
return Err(Unauthorized.into());
}

Expand Down
1 change: 1 addition & 0 deletions mev-programs/programs/vote-state/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ anchor-lang = { workspace = true }
bincode = { workspace = true }
serde = { workspace = true }
serde_derive = { workspace = true }
solana-sdk-ids = { workspace = true }
276 changes: 8 additions & 268 deletions mev-programs/programs/vote-state/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,281 +1,21 @@
#![allow(dead_code)]
//! This code was mostly copy-pasta'd from [here](https://github.com/solana-labs/solana/blob/df128573127c324cb5b53634a7e2d77427c6f2d8/programs/vote/src/vote_state/mod.rs#L1).
//! In all current releases [VoteState] is defined in the `solana-vote-program` crate which is not compatible
//! with programs targeting BPF bytecode due to some BPF-incompatible libraries being pulled in.

use std::collections::{BTreeMap, VecDeque};

use anchor_lang::{
error::ErrorCode::{AccountDidNotDeserialize, ConstraintOwner},
prelude::*,
solana_program,
prelude::{AccountInfo, Pubkey, Result},
};
use bincode::deserialize;
use serde_derive::Deserialize;

type Epoch = u64;
type Slot = u64;
type UnixTimestamp = i64;

#[derive(Clone, Deserialize)]
pub struct Lockout {
pub slot: Slot,
pub confirmation_count: u32,
}

#[derive(Clone, Default, Deserialize)]
struct AuthorizedVoters {
authorized_voters: BTreeMap<Epoch, Pubkey>,
}

impl AuthorizedVoters {
pub fn new(epoch: Epoch, pubkey: Pubkey) -> Self {
let mut authorized_voters = BTreeMap::new();
authorized_voters.insert(epoch, pubkey);
Self { authorized_voters }
}
}

const MAX_ITEMS: usize = 32;

#[derive(Default, Deserialize)]
pub struct CircBuf<I> {
buf: [I; MAX_ITEMS],
/// next pointer
idx: usize,
is_empty: bool,
}

#[derive(Clone, Deserialize, Default)]
pub struct BlockTimestamp {
pub slot: Slot,
pub timestamp: UnixTimestamp,
}

#[derive(Deserialize)]
pub enum VoteStateVersions {
V0_23_5(Box<VoteState0_23_5>),
V1_14_11(Box<VoteState1_14_11>),
Current(Box<VoteState>),
}

impl VoteStateVersions {
pub fn convert_to_current(self) -> Box<VoteState> {
match self {
Self::V0_23_5(state) => {
let authorized_voters =
AuthorizedVoters::new(state.authorized_voter_epoch, state.authorized_voter);

Box::new(VoteState {
node_pubkey: state.node_pubkey,

authorized_withdrawer: state.authorized_withdrawer,

commission: state.commission,

votes: Self::landed_votes_from_lockouts(state.votes),

root_slot: state.root_slot,

authorized_voters,

prior_voters: CircBuf::default(),

epoch_credits: state.epoch_credits.clone(),

last_timestamp: state.last_timestamp.clone(),
})
}
Self::V1_14_11(state) => Box::new(VoteState {
node_pubkey: state.node_pubkey,
authorized_withdrawer: state.authorized_withdrawer,
commission: state.commission,

votes: Self::landed_votes_from_lockouts(state.votes),

root_slot: state.root_slot,

authorized_voters: state.authorized_voters.clone(),

prior_voters: state.prior_voters,

epoch_credits: state.epoch_credits,

last_timestamp: state.last_timestamp,
}),
Self::Current(state) => state,
}
}

fn landed_votes_from_lockouts(lockouts: VecDeque<Lockout>) -> VecDeque<LandedVote> {
lockouts.into_iter().map(|lockout| lockout.into()).collect()
}
}

#[derive(Deserialize)]
pub struct VoteState1_14_11 {
/// the node that votes in this account
pub node_pubkey: Pubkey,

/// the signer for withdrawals
#[serde(skip_deserializing)]
pub authorized_withdrawer: Pubkey,
/// percentage (0-100) that represents what part of a rewards
/// payout should be given to this VoteAccount
#[serde(skip_deserializing)]
pub commission: u8,
#[serde(skip_deserializing)]
pub votes: VecDeque<Lockout>,

/// This usually the last Lockout which was popped from self.votes.
/// However, it can be arbitrary slot, when being used inside Tower
#[serde(skip_deserializing)]
pub root_slot: Option<Slot>,

/// the signer for vote transactions
#[serde(skip_deserializing)]
authorized_voters: AuthorizedVoters,

/// history of prior authorized voters and the epochs for which
/// they were set, the bottom end of the range is inclusive,
/// the top of the range is exclusive
#[serde(skip_deserializing)]
prior_voters: CircBuf<(Pubkey, Epoch, Epoch)>,

/// history of how many credits earned by the end of each epoch
/// each tuple is (Epoch, credits, prev_credits)
#[serde(skip_deserializing)]
pub(crate) epoch_credits: Vec<(Epoch, u64, u64)>,

/// most recent timestamp submitted with a vote
#[serde(skip_deserializing)]
pub last_timestamp: BlockTimestamp,
}

#[derive(Deserialize)]
pub struct VoteState0_23_5 {
/// the node that votes in this account
pub node_pubkey: Pubkey,

/// the signer for vote transactions
#[serde(skip_deserializing)]
pub authorized_voter: Pubkey,
/// when the authorized voter was set/initialized
#[serde(skip_deserializing)]
pub authorized_voter_epoch: Epoch,

/// history of prior authorized voters and the epoch ranges for which
/// they were set
#[serde(skip_deserializing)]
pub prior_voters: CircBuf<(Pubkey, Epoch, Epoch, Slot)>,

/// the signer for withdrawals
#[serde(skip_deserializing)]
pub authorized_withdrawer: Pubkey,
/// percentage (0-100) that represents what part of a rewards
/// payout should be given to this VoteAccount
#[serde(skip_deserializing)]
pub commission: u8,

#[serde(skip_deserializing)]
pub votes: VecDeque<Lockout>,
#[serde(skip_deserializing)]
pub root_slot: Option<u64>,

/// history of how many credits earned by the end of each epoch
/// each tuple is (Epoch, credits, prev_credits)
#[serde(skip_deserializing)]
pub epoch_credits: Vec<(Epoch, u64, u64)>,

/// most recent timestamp submitted with a vote
#[serde(skip_deserializing)]
pub last_timestamp: BlockTimestamp,
}

#[derive(Deserialize)]
pub struct VoteState {
/// the node that votes in this account
pub node_pubkey: Pubkey,

/// the signer for withdrawals
#[serde(skip_deserializing)]
pub authorized_withdrawer: Pubkey,
/// percentage (0-100) that represents what part of a rewards
/// payout should be given to this VoteAccount
#[serde(skip_deserializing)]
pub commission: u8,

#[serde(skip_deserializing)]
pub votes: VecDeque<LandedVote>,

// This usually the last Lockout which was popped from self.votes.
// However, it can be arbitrary slot, when being used inside Tower
#[serde(skip_deserializing)]
pub root_slot: Option<Slot>,

/// the signer for vote transactions
#[serde(skip_deserializing)]
authorized_voters: AuthorizedVoters,

/// history of prior authorized voters and the epochs for which
/// they were set, the bottom end of the range is inclusive,
/// the top of the range is exclusive
#[serde(skip_deserializing)]
prior_voters: CircBuf<(Pubkey, Epoch, Epoch)>,

/// history of how many credits earned by the end of each epoch
/// each tuple is (Epoch, credits, prev_credits)
#[serde(skip_deserializing)]
pub epoch_credits: Vec<(Epoch, u64, u64)>,

/// most recent timestamp submitted with a vote
#[serde(skip_deserializing)]
pub last_timestamp: BlockTimestamp,
}

#[derive(Deserialize)]
pub struct LandedVote {
// Latency is the difference in slot number between the slot that was voted on (lockout.slot) and the slot in
// which the vote that added this Lockout landed. For votes which were cast before versions of the validator
// software which recorded vote latencies, latency is recorded as 0.
pub latency: u8,
pub lockout: Lockout,
}

impl LandedVote {
pub const fn slot(&self) -> Slot {
self.lockout.slot
}

pub const fn confirmation_count(&self) -> u32 {
self.lockout.confirmation_count
}
}

impl From<LandedVote> for Lockout {
fn from(landed_vote: LandedVote) -> Self {
landed_vote.lockout
}
}

impl From<Lockout> for LandedVote {
fn from(lockout: Lockout) -> Self {
Self {
latency: 0,
lockout,
}
}
}
pub struct VoteState;

impl VoteState {
pub fn deserialize(account_info: &AccountInfo) -> Result<Box<Self>> {
if account_info.owner != &solana_program::vote::program::id() {
pub fn deserialize_node_pubkey(account_info: &AccountInfo) -> Result<Pubkey> {
Comment thread
buffalu marked this conversation as resolved.
if Pubkey::from(account_info.owner.to_bytes())
!= Pubkey::from(solana_sdk_ids::vote::id().to_bytes())
{
return Err(ConstraintOwner.into());
}

// The first 4 bytes are the enumeration type and the next 32 bytes of the vote state are the node pubkey.
let data = account_info.data.borrow();
deserialize::<Box<VoteStateVersions>>(&data)
.map(|v| v.convert_to_current())
.map_err(|_| AccountDidNotDeserialize.into())
deserialize::<Pubkey>(&data[4..36]).map_err(|_| AccountDidNotDeserialize.into())
}
}
Loading