Skip to content

Commit 52e9718

Browse files
authoredFeb 29, 2024··
feat: add epoch start timestamp (#23)
### 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.
1 parent a5d1f41 commit 52e9718

File tree

6 files changed

+165
-67
lines changed

6 files changed

+165
-67
lines changed
 

‎programs/validator-history/idl/validator_history.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -649,6 +649,10 @@
649649
"name": "totalBlocks",
650650
"type": "u32"
651651
},
652+
{
653+
"name": "epochStartTimestamp",
654+
"type": "u32"
655+
},
652656
{
653657
"name": "epoch",
654658
"type": "u16"
@@ -658,7 +662,7 @@
658662
"type": {
659663
"array": [
660664
"u8",
661-
250
665+
246
662666
]
663667
}
664668
}

‎programs/validator-history/src/instructions/copy_cluster_info.rs

+12-5
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
1-
use anchor_lang::{
2-
prelude::*,
3-
solana_program::{clock::Clock, slot_history::Check},
1+
use {
2+
crate::{
3+
errors::ValidatorHistoryError,
4+
utils::{cast_epoch, cast_epoch_start_timestamp},
5+
ClusterHistory,
6+
},
7+
anchor_lang::{
8+
prelude::*,
9+
solana_program::{clock::Clock, slot_history::Check},
10+
},
411
};
512

6-
use crate::{errors::ValidatorHistoryError, utils::cast_epoch, ClusterHistory};
7-
813
#[derive(Accounts)]
914
pub struct CopyClusterInfo<'info> {
1015
#[account(
@@ -29,12 +34,14 @@ pub fn handle_copy_cluster_info(ctx: Context<CopyClusterInfo>) -> Result<()> {
2934

3035
let epoch = cast_epoch(clock.epoch);
3136

37+
let epoch_start_timestamp = cast_epoch_start_timestamp(clock.epoch_start_timestamp);
3238
// Sets the slot history for the previous epoch, since the current epoch is not yet complete.
3339
if epoch > 0 {
3440
cluster_history_account
3541
.set_blocks(epoch - 1, blocks_in_epoch(epoch - 1, &slot_history)?)?;
3642
}
3743
cluster_history_account.set_blocks(epoch, blocks_in_epoch(epoch, &slot_history)?)?;
44+
cluster_history_account.set_epoch_start_timestamp(epoch, epoch_start_timestamp)?;
3845

3946
cluster_history_account.cluster_history_last_update_slot = clock.slot;
4047

‎programs/validator-history/src/state.rs

+26-12
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
use std::{cmp::Ordering, collections::HashMap, mem::size_of, net::IpAddr};
2-
3-
use anchor_lang::prelude::*;
4-
use borsh::{BorshDeserialize, BorshSerialize};
5-
use type_layout::TypeLayout;
6-
7-
use crate::{
8-
crds_value::{ContactInfo, LegacyContactInfo, LegacyVersion, Version2},
9-
errors::ValidatorHistoryError,
10-
utils::cast_epoch,
1+
use {
2+
crate::{
3+
crds_value::{ContactInfo, LegacyContactInfo, LegacyVersion, Version2},
4+
errors::ValidatorHistoryError,
5+
utils::cast_epoch,
6+
},
7+
anchor_lang::prelude::*,
8+
borsh::{BorshDeserialize, BorshSerialize},
9+
std::{cmp::Ordering, collections::HashMap, mem::size_of, net::IpAddr},
10+
type_layout::TypeLayout,
1111
};
1212

1313
static_assertions::const_assert_eq!(size_of::<Config>(), 104);
@@ -654,16 +654,18 @@ pub struct ClusterHistory {
654654
#[zero_copy]
655655
pub struct ClusterHistoryEntry {
656656
pub total_blocks: u32,
657+
pub epoch_start_timestamp: u32,
657658
pub epoch: u16,
658-
pub padding: [u8; 250],
659+
pub padding: [u8; 246],
659660
}
660661

661662
impl Default for ClusterHistoryEntry {
662663
fn default() -> Self {
663664
Self {
664665
total_blocks: u32::MAX,
665666
epoch: u16::MAX,
666-
padding: [u8::MAX; 250],
667+
epoch_start_timestamp: u32::MAX,
668+
padding: [u8::MAX; 246],
667669
}
668670
}
669671
}
@@ -793,6 +795,18 @@ impl ClusterHistory {
793795

794796
Ok(())
795797
}
798+
pub fn set_epoch_start_timestamp(
799+
&mut self,
800+
epoch: u16,
801+
epoch_start_timestamp: u32,
802+
) -> Result<()> {
803+
if let Some(entry) = self.history.last_mut() {
804+
if entry.epoch == epoch {
805+
entry.epoch_start_timestamp = epoch_start_timestamp;
806+
}
807+
}
808+
Ok(())
809+
}
796810
}
797811

798812
#[cfg(test)]

‎programs/validator-history/src/utils.rs

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
1-
use anchor_lang::prelude::{AccountInfo, Pubkey};
2-
use anchor_lang::solana_program::native_token::lamports_to_sol;
1+
use anchor_lang::{
2+
prelude::{AccountInfo, Pubkey},
3+
solana_program::native_token::lamports_to_sol,
4+
};
35

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

10+
pub fn cast_epoch_start_timestamp(start_timestamp: i64) -> u32 {
11+
start_timestamp.try_into().unwrap()
12+
}
13+
814
pub fn fixed_point_sol(lamports: u64) -> u32 {
915
// convert to sol
1016
let mut sol = lamports_to_sol(lamports);

‎tests/src/fixtures.rs

+39-18
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,25 @@
11
#![allow(clippy::await_holding_refcell_ref)]
2-
use anchor_lang::{
3-
solana_program::{
4-
clock::Clock,
5-
pubkey::Pubkey,
6-
vote::state::{VoteInit, VoteState, VoteStateVersions},
2+
use {
3+
anchor_lang::{
4+
solana_program::{
5+
clock::Clock,
6+
pubkey::Pubkey,
7+
vote::state::{VoteInit, VoteState, VoteStateVersions},
8+
},
9+
AccountSerialize, InstructionData, ToAccountMetas,
710
},
8-
AccountSerialize, InstructionData, ToAccountMetas,
9-
};
10-
use solana_program_test::*;
11-
use solana_sdk::{
12-
account::Account, epoch_schedule::EpochSchedule, instruction::Instruction, signature::Keypair,
13-
signer::Signer, transaction::Transaction,
14-
};
15-
use std::{cell::RefCell, rc::Rc};
16-
17-
use jito_tip_distribution::{
18-
sdk::derive_tip_distribution_account_address,
19-
state::{MerkleRoot, TipDistributionAccount},
11+
jito_tip_distribution::{
12+
sdk::derive_tip_distribution_account_address,
13+
state::{MerkleRoot, TipDistributionAccount},
14+
},
15+
solana_program_test::*,
16+
solana_sdk::{
17+
account::Account, epoch_schedule::EpochSchedule, instruction::Instruction,
18+
signature::Keypair, signer::Signer, transaction::Transaction,
19+
},
20+
std::{cell::RefCell, rc::Rc},
21+
validator_history::{self, constants::MAX_ALLOC_BYTES, ClusterHistory, ValidatorHistory},
2022
};
21-
use validator_history::{self, constants::MAX_ALLOC_BYTES, ClusterHistory, ValidatorHistory};
2223

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

252+
pub async fn advance_clock(&self, num_epochs: u64, ms_per_slot: u64) -> u64 {
253+
let mut clock: Clock = self
254+
.ctx
255+
.borrow_mut()
256+
.banks_client
257+
.get_sysvar()
258+
.await
259+
.expect("Failed getting clock");
260+
261+
let epoch_schedule: EpochSchedule = self.ctx.borrow().genesis_config().epoch_schedule;
262+
let target_epoch = clock.epoch + num_epochs;
263+
let dif_slots = epoch_schedule.get_first_slot_in_epoch(target_epoch) - clock.slot;
264+
265+
clock.epoch_start_timestamp += (dif_slots * ms_per_slot) as i64;
266+
clock.unix_timestamp += (dif_slots * ms_per_slot) as i64;
267+
self.ctx.borrow_mut().set_sysvar(&clock);
268+
269+
dif_slots
270+
}
271+
251272
pub async fn submit_transaction_assert_success(&self, transaction: Transaction) {
252273
let mut ctx = self.ctx.borrow_mut();
253274
if let Err(e) = ctx

‎tests/tests/test_cluster_history.rs

+75-29
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,41 @@
11
#![allow(clippy::await_holding_refcell_ref)]
2-
use anchor_lang::{
3-
solana_program::{instruction::Instruction, slot_history::SlotHistory},
4-
InstructionData, ToAccountMetas,
2+
use {
3+
anchor_lang::{
4+
solana_program::{instruction::Instruction, slot_history::SlotHistory},
5+
InstructionData, ToAccountMetas,
6+
},
7+
solana_program_test::*,
8+
solana_sdk::{
9+
clock::Clock, compute_budget::ComputeBudgetInstruction, signer::Signer,
10+
transaction::Transaction,
11+
},
12+
tests::fixtures::TestFixture,
13+
validator_history::ClusterHistory,
514
};
6-
use solana_program_test::*;
7-
use solana_sdk::{
8-
clock::Clock, compute_budget::ComputeBudgetInstruction, signer::Signer,
9-
transaction::Transaction,
10-
};
11-
use tests::fixtures::TestFixture;
12-
use validator_history::ClusterHistory;
15+
16+
const MS_PER_SLOT: u64 = 400;
17+
18+
fn create_copy_cluster_history_transaction(fixture: &TestFixture) -> Transaction {
19+
let instruction = Instruction {
20+
program_id: validator_history::id(),
21+
data: validator_history::instruction::CopyClusterInfo {}.data(),
22+
accounts: validator_history::accounts::CopyClusterInfo {
23+
cluster_history_account: fixture.cluster_history_account,
24+
slot_history: anchor_lang::solana_program::sysvar::slot_history::id(),
25+
signer: fixture.keypair.pubkey(),
26+
}
27+
.to_account_metas(None),
28+
};
29+
let heap_request_ix = ComputeBudgetInstruction::request_heap_frame(256 * 1024);
30+
let compute_budget_ix = ComputeBudgetInstruction::set_compute_unit_limit(300_000);
31+
32+
Transaction::new_signed_with_payer(
33+
&[heap_request_ix, compute_budget_ix, instruction],
34+
Some(&fixture.keypair.pubkey()),
35+
&[&fixture.keypair],
36+
fixture.ctx.borrow().last_blockhash,
37+
)
38+
}
1339

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

3460
// Submit instruction
35-
let instruction = Instruction {
36-
program_id: validator_history::id(),
37-
data: validator_history::instruction::CopyClusterInfo {}.data(),
38-
accounts: validator_history::accounts::CopyClusterInfo {
39-
cluster_history_account: fixture.cluster_history_account,
40-
slot_history: anchor_lang::solana_program::sysvar::slot_history::id(),
41-
signer: fixture.keypair.pubkey(),
42-
}
43-
.to_account_metas(None),
44-
};
45-
let heap_request_ix = ComputeBudgetInstruction::request_heap_frame(256 * 1024);
46-
let compute_budget_ix = ComputeBudgetInstruction::set_compute_unit_limit(300_000);
47-
48-
let transaction = Transaction::new_signed_with_payer(
49-
&[heap_request_ix, compute_budget_ix, instruction],
50-
Some(&fixture.keypair.pubkey()),
51-
&[&fixture.keypair],
52-
ctx.borrow().last_blockhash,
53-
);
61+
let transaction = create_copy_cluster_history_transaction(&fixture);
5462

5563
ctx.borrow_mut().set_sysvar(&slot_history);
5664
fixture.submit_transaction_assert_success(transaction).await;
@@ -74,3 +82,41 @@ async fn test_copy_cluster_info() {
7482
assert!(account.history.arr[1].total_blocks == 2);
7583
assert_eq!(account.cluster_history_last_update_slot, latest_slot)
7684
}
85+
86+
#[tokio::test]
87+
async fn test_start_epoch_timestamp() {
88+
// Initialize
89+
let fixture = TestFixture::new().await;
90+
let ctx = &fixture.ctx;
91+
fixture.initialize_config().await;
92+
fixture.initialize_cluster_history_account().await;
93+
94+
// Set SlotHistory sysvar
95+
let slot_history = SlotHistory::default();
96+
ctx.borrow_mut().set_sysvar(&slot_history);
97+
98+
// Submit epoch 0 instruction
99+
let transaction = create_copy_cluster_history_transaction(&fixture);
100+
fixture.submit_transaction_assert_success(transaction).await;
101+
102+
// Change epoch and set clock timestamps in the future
103+
fixture.advance_num_epochs(1).await;
104+
let dif_slots = fixture.advance_clock(1, MS_PER_SLOT).await;
105+
106+
// Submit epoch 1 instruction
107+
let transaction = create_copy_cluster_history_transaction(&fixture);
108+
fixture.submit_transaction_assert_success(transaction).await;
109+
110+
let account: ClusterHistory = fixture
111+
.load_and_deserialize(&fixture.cluster_history_account)
112+
.await;
113+
114+
assert_eq!(account.history.arr[0].epoch, 0);
115+
assert_eq!(account.history.arr[1].epoch, 1);
116+
assert_ne!(account.history.arr[0].epoch_start_timestamp, u32::MAX);
117+
assert_ne!(account.history.arr[1].epoch_start_timestamp, u32::MAX);
118+
assert_eq!(
119+
account.history.arr[0].epoch_start_timestamp,
120+
account.history.arr[1].epoch_start_timestamp - (dif_slots * MS_PER_SLOT) as u32
121+
);
122+
}

0 commit comments

Comments
 (0)
Please sign in to comment.