Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Miscellaneous fixes and cleanup #24

Merged
merged 10 commits into from
Mar 5, 2024
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
4 changes: 2 additions & 2 deletions Cargo.lock

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

3 changes: 2 additions & 1 deletion keepers/validator-keeper/src/gossip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,8 @@ impl GossipEntry {
validator_history_account: self.validator_history_account,
vote_account: self.vote_account,
instructions: solana_program::sysvar::instructions::id(),
signer: self.signer,
config: self.config,
oracle_authority: self.signer,
}
.to_account_metas(None),
data: validator_history::instruction::CopyGossipContactInfo {}.data(),
Expand Down
31 changes: 25 additions & 6 deletions programs/validator-history/idl/validator_history.json
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,12 @@
"isSigner": false
},
{
"name": "signer",
"name": "config",
"isMut": false,
"isSigner": false
},
{
"name": "oracleAuthority",
"isMut": true,
"isSigner": true
}
Expand Down Expand Up @@ -649,20 +654,29 @@
"name": "totalBlocks",
"type": "u32"
},
{
"name": "epochStartTimestamp",
"type": "u32"
},
{
"name": "epoch",
"type": "u16"
},
{
"name": "padding0",
"type": {
"array": [
"u8",
2
]
}
},
{
"name": "epochStartTimestamp",
"type": "u64"
},
{
"name": "padding",
"type": {
"array": [
"u8",
246
240
]
}
}
Expand Down Expand Up @@ -935,6 +949,11 @@
"code": 6010,
"name": "SlotHistoryOutOfDate",
"msg": "Slot history sysvar is not containing expected slots"
},
{
"code": 6011,
"name": "EpochTooLarge",
"msg": "Epoch larger than 65535, cannot be stored"
}
]
}
2 changes: 2 additions & 0 deletions programs/validator-history/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,6 @@ pub enum ValidatorHistoryError {
ArithmeticError,
#[msg("Slot history sysvar is not containing expected slots")]
SlotHistoryOutOfDate,
#[msg("Epoch larger than 65535, cannot be stored")]
EpochTooLarge,
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ pub fn handle_backfill_total_blocks(
) -> Result<()> {
let mut cluster_history_account = ctx.accounts.cluster_history_account.load_mut()?;

let epoch = cast_epoch(epoch);
let epoch = cast_epoch(epoch)?;

// Need to backfill in ascending order when initially filling in the account otherwise entries will be out of order
if !cluster_history_account.history.is_empty()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,10 @@ pub fn handle_copy_cluster_info(ctx: Context<CopyClusterInfo>) -> Result<()> {

let clock = Clock::get()?;

let epoch = cast_epoch(clock.epoch);
let epoch = cast_epoch(clock.epoch)?;

let epoch_start_timestamp = cast_epoch_start_timestamp(clock.epoch_start_timestamp);

// Sets the slot history for the previous epoch, since the current epoch is not yet complete.
if epoch > 0 {
cluster_history_account
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,34 @@ use anchor_lang::{
prelude::*,
solana_program::{self, clock::Clock, pubkey::Pubkey, sysvar},
};
use bytemuck::{from_bytes, Pod, Zeroable};

use crate::{
crds_value::CrdsData, errors::ValidatorHistoryError, state::ValidatorHistory, utils::cast_epoch,
crds_value::CrdsData, errors::ValidatorHistoryError, state::ValidatorHistory,
utils::cast_epoch, Config,
};
use validator_history_vote_state::VoteStateVersions;

// Structs and constants copied from solana_sdk::ed25519_instruction. Copied in order to make fields public. Compilation issues hit when importing solana_sdk
#[derive(Default, Debug, Copy, Clone, Zeroable, Pod, Eq, PartialEq)]
#[repr(C)]
pub struct Ed25519SignatureOffsets {
buffalu marked this conversation as resolved.
Show resolved Hide resolved
pub signature_offset: u16, // offset to ed25519 signature of 64 bytes
pub signature_instruction_index: u16, // instruction index to find signature
pub public_key_offset: u16, // offset to public key of 32 bytes
pub public_key_instruction_index: u16, // instruction index to find public key
pub message_data_offset: u16, // offset to start of message data
pub message_data_size: u16, // size of message data
pub message_instruction_index: u16, // index of instruction data to get message data
}

pub const PUBKEY_SERIALIZED_SIZE: usize = 32;
pub const SIGNATURE_SERIALIZED_SIZE: usize = 64;
pub const SIGNATURE_OFFSETS_SERIALIZED_SIZE: usize = 14;
// bytemuck requires structures to be aligned
pub const SIGNATURE_OFFSETS_START: usize = 2;
pub const DATA_START: usize = SIGNATURE_OFFSETS_SERIALIZED_SIZE + SIGNATURE_OFFSETS_START;

#[derive(Accounts)]
pub struct CopyGossipContactInfo<'info> {
#[account(
Expand All @@ -22,15 +44,21 @@ pub struct CopyGossipContactInfo<'info> {
/// CHECK: Safe because it's a sysvar account
#[account(address = sysvar::instructions::ID)]
pub instructions: UncheckedAccount<'info>,
#[account(
seeds = [Config::SEED],
bump = config.bump,
has_one = oracle_authority
)]
pub config: Account<'info, Config>,
#[account(mut)]
pub signer: Signer<'info>,
pub oracle_authority: Signer<'info>,
}

pub fn handle_copy_gossip_contact_info(ctx: Context<CopyGossipContactInfo>) -> Result<()> {
let mut validator_history_account = ctx.accounts.validator_history_account.load_mut()?;
let instructions = ctx.accounts.instructions.to_account_info();
let clock = Clock::get()?;
let epoch = cast_epoch(clock.epoch);
let epoch = cast_epoch(clock.epoch)?;

let verify_instruction = sysvar::instructions::get_instruction_relative(-1, &instructions)?;

Expand All @@ -39,9 +67,30 @@ pub fn handle_copy_gossip_contact_info(ctx: Context<CopyGossipContactInfo>) -> R
return Err(ValidatorHistoryError::NotSigVerified.into());
}

let message_signer = Pubkey::try_from(&verify_instruction.data[16..48])
.map_err(|_| ValidatorHistoryError::GossipDataInvalid)?;
let message_data = &verify_instruction.data[112..];
let ed25519_offsets = from_bytes::<Ed25519SignatureOffsets>(
&verify_instruction.data
[SIGNATURE_OFFSETS_START..SIGNATURE_OFFSETS_START + SIGNATURE_OFFSETS_SERIALIZED_SIZE],
);

// Check offsets and indices are correct so an attacker cannot submit invalid data
if ed25519_offsets.signature_instruction_index != ed25519_offsets.public_key_instruction_index
|| ed25519_offsets.signature_instruction_index != ed25519_offsets.message_instruction_index
|| ed25519_offsets.public_key_offset
!= (SIGNATURE_OFFSETS_START + SIGNATURE_OFFSETS_SERIALIZED_SIZE) as u16
|| ed25519_offsets.signature_offset
!= ed25519_offsets.public_key_offset + PUBKEY_SERIALIZED_SIZE as u16
|| ed25519_offsets.message_data_offset
!= ed25519_offsets.signature_offset + SIGNATURE_SERIALIZED_SIZE as u16
{
return Err(ValidatorHistoryError::GossipDataInvalid.into());
}

let message_signer = Pubkey::try_from(
&verify_instruction.data[ed25519_offsets.public_key_offset as usize
..ed25519_offsets.public_key_offset as usize + PUBKEY_SERIALIZED_SIZE],
)
.map_err(|_| ValidatorHistoryError::GossipDataInvalid)?;
let message_data = &verify_instruction.data[ed25519_offsets.message_data_offset as usize..];

let crds_data: CrdsData =
bincode::deserialize(message_data).map_err(|_| ValidatorHistoryError::GossipDataInvalid)?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ pub fn handle_copy_tip_distribution_account(
if epoch > Clock::get()?.epoch {
return Err(ValidatorHistoryError::EpochOutOfRange.into());
}
let epoch = cast_epoch(epoch);
let epoch = cast_epoch(epoch)?;
let mut validator_history_account = ctx.accounts.validator_history_account.load_mut()?;

let mut tda_data: &[u8] = &ctx.accounts.tip_distribution_account.try_borrow_data()?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ pub struct CopyVoteAccount<'info> {
pub fn handle_copy_vote_account(ctx: Context<CopyVoteAccount>) -> Result<()> {
let mut validator_history_account = ctx.accounts.validator_history_account.load_mut()?;
let clock = Clock::get()?;
let epoch = cast_epoch(clock.epoch);
let epoch = cast_epoch(clock.epoch)?;

let commission = VoteStateVersions::deserialize_commission(&ctx.accounts.vote_account)?;
validator_history_account.set_commission_and_slot(epoch, commission, clock.slot)?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,23 @@ pub struct UpdateStakeHistory<'info> {
pub oracle_authority: Signer<'info>,
}

// NOTE: If using this instruction to backfill a new validator history account, you must ensure that epochs are added in ascending order.
// This is because new entries cannot be inserted for an epoch that is lower than the last entry's if missed.
pub fn handle_update_stake_history(
ctx: Context<UpdateStakeHistory>,
epoch: u64,
lamports: u64,
rank: u32,
is_superminority: bool,
) -> Result<()> {
let mut validator_history_account = ctx.accounts.validator_history_account.load_mut()?;
let mut validator_history_account: std::cell::RefMut<'_, ValidatorHistory> =
ctx.accounts.validator_history_account.load_mut()?;

// Cannot set stake for future epochs
if epoch > Clock::get()?.epoch {
return Err(ValidatorHistoryError::EpochOutOfRange.into());
}
let epoch = cast_epoch(epoch);
let epoch = cast_epoch(epoch)?;

validator_history_account.set_stake(epoch, lamports, rank, is_superminority)?;

Expand Down
18 changes: 11 additions & 7 deletions programs/validator-history/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,13 @@ pub struct ValidatorHistoryEntry {
pub rank: u32,
// Most recent updated slot for epoch credits and commission
pub vote_account_last_update_slot: u64,
// MEV earned, stored as 1/100th SOL. mev_earned = 100 means 1 SOL earned
// MEV earned, stored as 1/100th SOL. mev_earned = 100 means 1.00 SOL earned
pub mev_earned: u32,
pub padding1: [u8; 84],
}

// Default values for fields in `ValidatorHistoryEntry` are the type's max value.
// It's important to ensure that the max value is not a valid value for the field, so we can check if the field has been set.
impl Default for ValidatorHistoryEntry {
fn default() -> Self {
Self {
Expand Down Expand Up @@ -378,7 +380,7 @@ impl ValidatorHistory {
let epoch_credits_map: HashMap<u16, u32> =
HashMap::from_iter(epoch_credits.iter().map(|(epoch, cur, prev)| {
(
cast_epoch(*epoch),
cast_epoch(*epoch).unwrap(), // all epochs in list will be valid if current epoch is valid
(cur.checked_sub(*prev)
.ok_or(ValidatorHistoryError::InvalidEpochCredits)
.unwrap() as u32),
Expand Down Expand Up @@ -654,18 +656,20 @@ pub struct ClusterHistory {
#[zero_copy]
pub struct ClusterHistoryEntry {
pub total_blocks: u32,
pub epoch_start_timestamp: u32,
pub epoch: u16,
pub padding: [u8; 246],
pub padding0: [u8; 2],
pub epoch_start_timestamp: u64,
pub padding: [u8; 240],
}

impl Default for ClusterHistoryEntry {
fn default() -> Self {
Self {
total_blocks: u32::MAX,
epoch: u16::MAX,
epoch_start_timestamp: u32::MAX,
padding: [u8::MAX; 246],
padding0: [u8::MAX; 2],
epoch_start_timestamp: u64::MAX,
padding: [u8::MAX; 240],
}
}
}
Expand Down Expand Up @@ -798,7 +802,7 @@ impl ClusterHistory {
pub fn set_epoch_start_timestamp(
&mut self,
epoch: u16,
epoch_start_timestamp: u32,
epoch_start_timestamp: u64,
) -> Result<()> {
if let Some(entry) = self.history.last_mut() {
if entry.epoch == epoch {
Expand Down
16 changes: 12 additions & 4 deletions programs/validator-history/src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
use anchor_lang::{
prelude::{AccountInfo, Pubkey},
prelude::{AccountInfo, Pubkey, Result},
require,
solana_program::native_token::lamports_to_sol,
};

pub fn cast_epoch(epoch: u64) -> u16 {
(epoch % u16::MAX as u64).try_into().unwrap()
use crate::errors::ValidatorHistoryError;

pub fn cast_epoch(epoch: u64) -> Result<u16> {
buffalu marked this conversation as resolved.
Show resolved Hide resolved
require!(
epoch < (u16::MAX as u64),
ValidatorHistoryError::EpochTooLarge
);
let epoch_u16: u16 = (epoch % u16::MAX as u64).try_into().unwrap();
buffalu marked this conversation as resolved.
Show resolved Hide resolved
Ok(epoch_u16)
}

pub fn cast_epoch_start_timestamp(start_timestamp: i64) -> u32 {
pub fn cast_epoch_start_timestamp(start_timestamp: i64) -> u64 {
start_timestamp.try_into().unwrap()
}

Expand Down
6 changes: 3 additions & 3 deletions tests/tests/test_cluster_history.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,10 @@ async fn test_start_epoch_timestamp() {

assert_eq!(account.history.arr[0].epoch, 0);
assert_eq!(account.history.arr[1].epoch, 1);
assert_ne!(account.history.arr[0].epoch_start_timestamp, u32::MAX);
assert_ne!(account.history.arr[1].epoch_start_timestamp, u32::MAX);
assert_ne!(account.history.arr[0].epoch_start_timestamp, u64::MAX);
assert_ne!(account.history.arr[1].epoch_start_timestamp, u64::MAX);
assert_eq!(
account.history.arr[0].epoch_start_timestamp,
account.history.arr[1].epoch_start_timestamp - (dif_slots * MS_PER_SLOT) as u32
account.history.arr[1].epoch_start_timestamp - dif_slots * MS_PER_SLOT
);
}
Loading