Skip to content

Commit

Permalink
feat: add epoch start timestamp (#23)
Browse files Browse the repository at this point in the history
### Context:
This PR introduces `epoch_start_timestamp` within `ClusterHistoryEntry`
to enhance APY tracking, as detailed in
#18

The `epoch_start_timestamp` is assigned during the execution of the
`copy_cluster_info` ix for the current epoch

### Tests:
A new function has been added to modify the Clock sysvar. This was
necessary because `advance_num_epochs` did not update the Clock sysvar
during tests

### Dependencies
This PR depends on the successful merge of
#22. After merge, I will
update this accordingly.
  • Loading branch information
butonium authored Feb 29, 2024
1 parent a5d1f41 commit 52e9718
Show file tree
Hide file tree
Showing 6 changed files with 165 additions and 67 deletions.
6 changes: 5 additions & 1 deletion programs/validator-history/idl/validator_history.json
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,10 @@
"name": "totalBlocks",
"type": "u32"
},
{
"name": "epochStartTimestamp",
"type": "u32"
},
{
"name": "epoch",
"type": "u16"
Expand All @@ -658,7 +662,7 @@
"type": {
"array": [
"u8",
250
246
]
}
}
Expand Down
17 changes: 12 additions & 5 deletions programs/validator-history/src/instructions/copy_cluster_info.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
use anchor_lang::{
prelude::*,
solana_program::{clock::Clock, slot_history::Check},
use {
crate::{
errors::ValidatorHistoryError,
utils::{cast_epoch, cast_epoch_start_timestamp},
ClusterHistory,
},
anchor_lang::{
prelude::*,
solana_program::{clock::Clock, slot_history::Check},
},
};

use crate::{errors::ValidatorHistoryError, utils::cast_epoch, ClusterHistory};

#[derive(Accounts)]
pub struct CopyClusterInfo<'info> {
#[account(
Expand All @@ -29,12 +34,14 @@ pub fn handle_copy_cluster_info(ctx: Context<CopyClusterInfo>) -> Result<()> {

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
.set_blocks(epoch - 1, blocks_in_epoch(epoch - 1, &slot_history)?)?;
}
cluster_history_account.set_blocks(epoch, blocks_in_epoch(epoch, &slot_history)?)?;
cluster_history_account.set_epoch_start_timestamp(epoch, epoch_start_timestamp)?;

cluster_history_account.cluster_history_last_update_slot = clock.slot;

Expand Down
38 changes: 26 additions & 12 deletions programs/validator-history/src/state.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
use std::{cmp::Ordering, collections::HashMap, mem::size_of, net::IpAddr};

use anchor_lang::prelude::*;
use borsh::{BorshDeserialize, BorshSerialize};
use type_layout::TypeLayout;

use crate::{
crds_value::{ContactInfo, LegacyContactInfo, LegacyVersion, Version2},
errors::ValidatorHistoryError,
utils::cast_epoch,
use {
crate::{
crds_value::{ContactInfo, LegacyContactInfo, LegacyVersion, Version2},
errors::ValidatorHistoryError,
utils::cast_epoch,
},
anchor_lang::prelude::*,
borsh::{BorshDeserialize, BorshSerialize},
std::{cmp::Ordering, collections::HashMap, mem::size_of, net::IpAddr},
type_layout::TypeLayout,
};

static_assertions::const_assert_eq!(size_of::<Config>(), 104);
Expand Down Expand Up @@ -654,16 +654,18 @@ pub struct ClusterHistory {
#[zero_copy]
pub struct ClusterHistoryEntry {
pub total_blocks: u32,
pub epoch_start_timestamp: u32,
pub epoch: u16,
pub padding: [u8; 250],
pub padding: [u8; 246],
}

impl Default for ClusterHistoryEntry {
fn default() -> Self {
Self {
total_blocks: u32::MAX,
epoch: u16::MAX,
padding: [u8::MAX; 250],
epoch_start_timestamp: u32::MAX,
padding: [u8::MAX; 246],
}
}
}
Expand Down Expand Up @@ -793,6 +795,18 @@ impl ClusterHistory {

Ok(())
}
pub fn set_epoch_start_timestamp(
&mut self,
epoch: u16,
epoch_start_timestamp: u32,
) -> Result<()> {
if let Some(entry) = self.history.last_mut() {
if entry.epoch == epoch {
entry.epoch_start_timestamp = epoch_start_timestamp;
}
}
Ok(())
}
}

#[cfg(test)]
Expand Down
10 changes: 8 additions & 2 deletions programs/validator-history/src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
use anchor_lang::prelude::{AccountInfo, Pubkey};
use anchor_lang::solana_program::native_token::lamports_to_sol;
use anchor_lang::{
prelude::{AccountInfo, Pubkey},
solana_program::native_token::lamports_to_sol,
};

pub fn cast_epoch(epoch: u64) -> u16 {
(epoch % u16::MAX as u64).try_into().unwrap()
}

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

pub fn fixed_point_sol(lamports: u64) -> u32 {
// convert to sol
let mut sol = lamports_to_sol(lamports);
Expand Down
57 changes: 39 additions & 18 deletions tests/src/fixtures.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
#![allow(clippy::await_holding_refcell_ref)]
use anchor_lang::{
solana_program::{
clock::Clock,
pubkey::Pubkey,
vote::state::{VoteInit, VoteState, VoteStateVersions},
use {
anchor_lang::{
solana_program::{
clock::Clock,
pubkey::Pubkey,
vote::state::{VoteInit, VoteState, VoteStateVersions},
},
AccountSerialize, InstructionData, ToAccountMetas,
},
AccountSerialize, InstructionData, ToAccountMetas,
};
use solana_program_test::*;
use solana_sdk::{
account::Account, epoch_schedule::EpochSchedule, instruction::Instruction, signature::Keypair,
signer::Signer, transaction::Transaction,
};
use std::{cell::RefCell, rc::Rc};

use jito_tip_distribution::{
sdk::derive_tip_distribution_account_address,
state::{MerkleRoot, TipDistributionAccount},
jito_tip_distribution::{
sdk::derive_tip_distribution_account_address,
state::{MerkleRoot, TipDistributionAccount},
},
solana_program_test::*,
solana_sdk::{
account::Account, epoch_schedule::EpochSchedule, instruction::Instruction,
signature::Keypair, signer::Signer, transaction::Transaction,
},
std::{cell::RefCell, rc::Rc},
validator_history::{self, constants::MAX_ALLOC_BYTES, ClusterHistory, ValidatorHistory},
};
use validator_history::{self, constants::MAX_ALLOC_BYTES, ClusterHistory, ValidatorHistory};

pub struct TestFixture {
pub ctx: Rc<RefCell<ProgramTestContext>>,
Expand Down Expand Up @@ -248,6 +249,26 @@ impl TestFixture {
.expect("Failed warping to future epoch");
}

pub async fn advance_clock(&self, num_epochs: u64, ms_per_slot: u64) -> u64 {
let mut clock: Clock = self
.ctx
.borrow_mut()
.banks_client
.get_sysvar()
.await
.expect("Failed getting clock");

let epoch_schedule: EpochSchedule = self.ctx.borrow().genesis_config().epoch_schedule;
let target_epoch = clock.epoch + num_epochs;
let dif_slots = epoch_schedule.get_first_slot_in_epoch(target_epoch) - clock.slot;

clock.epoch_start_timestamp += (dif_slots * ms_per_slot) as i64;
clock.unix_timestamp += (dif_slots * ms_per_slot) as i64;
self.ctx.borrow_mut().set_sysvar(&clock);

dif_slots
}

pub async fn submit_transaction_assert_success(&self, transaction: Transaction) {
let mut ctx = self.ctx.borrow_mut();
if let Err(e) = ctx
Expand Down
104 changes: 75 additions & 29 deletions tests/tests/test_cluster_history.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,41 @@
#![allow(clippy::await_holding_refcell_ref)]
use anchor_lang::{
solana_program::{instruction::Instruction, slot_history::SlotHistory},
InstructionData, ToAccountMetas,
use {
anchor_lang::{
solana_program::{instruction::Instruction, slot_history::SlotHistory},
InstructionData, ToAccountMetas,
},
solana_program_test::*,
solana_sdk::{
clock::Clock, compute_budget::ComputeBudgetInstruction, signer::Signer,
transaction::Transaction,
},
tests::fixtures::TestFixture,
validator_history::ClusterHistory,
};
use solana_program_test::*;
use solana_sdk::{
clock::Clock, compute_budget::ComputeBudgetInstruction, signer::Signer,
transaction::Transaction,
};
use tests::fixtures::TestFixture;
use validator_history::ClusterHistory;

const MS_PER_SLOT: u64 = 400;

fn create_copy_cluster_history_transaction(fixture: &TestFixture) -> Transaction {
let instruction = Instruction {
program_id: validator_history::id(),
data: validator_history::instruction::CopyClusterInfo {}.data(),
accounts: validator_history::accounts::CopyClusterInfo {
cluster_history_account: fixture.cluster_history_account,
slot_history: anchor_lang::solana_program::sysvar::slot_history::id(),
signer: fixture.keypair.pubkey(),
}
.to_account_metas(None),
};
let heap_request_ix = ComputeBudgetInstruction::request_heap_frame(256 * 1024);
let compute_budget_ix = ComputeBudgetInstruction::set_compute_unit_limit(300_000);

Transaction::new_signed_with_payer(
&[heap_request_ix, compute_budget_ix, instruction],
Some(&fixture.keypair.pubkey()),
&[&fixture.keypair],
fixture.ctx.borrow().last_blockhash,
)
}

#[tokio::test]
async fn test_copy_cluster_info() {
Expand All @@ -32,25 +58,7 @@ async fn test_copy_cluster_info() {
slot_history.add(latest_slot + 1);

// Submit instruction
let instruction = Instruction {
program_id: validator_history::id(),
data: validator_history::instruction::CopyClusterInfo {}.data(),
accounts: validator_history::accounts::CopyClusterInfo {
cluster_history_account: fixture.cluster_history_account,
slot_history: anchor_lang::solana_program::sysvar::slot_history::id(),
signer: fixture.keypair.pubkey(),
}
.to_account_metas(None),
};
let heap_request_ix = ComputeBudgetInstruction::request_heap_frame(256 * 1024);
let compute_budget_ix = ComputeBudgetInstruction::set_compute_unit_limit(300_000);

let transaction = Transaction::new_signed_with_payer(
&[heap_request_ix, compute_budget_ix, instruction],
Some(&fixture.keypair.pubkey()),
&[&fixture.keypair],
ctx.borrow().last_blockhash,
);
let transaction = create_copy_cluster_history_transaction(&fixture);

ctx.borrow_mut().set_sysvar(&slot_history);
fixture.submit_transaction_assert_success(transaction).await;
Expand All @@ -74,3 +82,41 @@ async fn test_copy_cluster_info() {
assert!(account.history.arr[1].total_blocks == 2);
assert_eq!(account.cluster_history_last_update_slot, latest_slot)
}

#[tokio::test]
async fn test_start_epoch_timestamp() {
// Initialize
let fixture = TestFixture::new().await;
let ctx = &fixture.ctx;
fixture.initialize_config().await;
fixture.initialize_cluster_history_account().await;

// Set SlotHistory sysvar
let slot_history = SlotHistory::default();
ctx.borrow_mut().set_sysvar(&slot_history);

// Submit epoch 0 instruction
let transaction = create_copy_cluster_history_transaction(&fixture);
fixture.submit_transaction_assert_success(transaction).await;

// Change epoch and set clock timestamps in the future
fixture.advance_num_epochs(1).await;
let dif_slots = fixture.advance_clock(1, MS_PER_SLOT).await;

// Submit epoch 1 instruction
let transaction = create_copy_cluster_history_transaction(&fixture);
fixture.submit_transaction_assert_success(transaction).await;

let account: ClusterHistory = fixture
.load_and_deserialize(&fixture.cluster_history_account)
.await;

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_eq!(
account.history.arr[0].epoch_start_timestamp,
account.history.arr[1].epoch_start_timestamp - (dif_slots * MS_PER_SLOT) as u32
);
}

0 comments on commit 52e9718

Please sign in to comment.