light_registry/protocol_config/
state.rs

1use aligned_sized::aligned_sized;
2use anchor_lang::prelude::*;
3
4use crate::errors::RegistryError;
5
6#[aligned_sized(anchor)]
7#[derive(Debug)]
8#[account]
9pub struct ProtocolConfigPda {
10    pub authority: Pubkey,
11    pub bump: u8,
12    pub config: ProtocolConfig,
13}
14
15/// Epoch Phases:
16/// 1. Registration
17/// 2. Active
18/// 3. Report Work
19/// 4. Post (Epoch has ended, and rewards can be claimed.)
20/// - There is always an active phase in progress, registration and report work
21///   phases run in parallel to a currently active phase.
22#[derive(Debug, Clone, Copy, PartialEq, Eq, AnchorSerialize, AnchorDeserialize)]
23pub struct ProtocolConfig {
24    /// Solana slot when the protocol starts operating.
25    pub genesis_slot: u64,
26    /// Minimum weight required for a forester to register to an epoch.
27    pub min_weight: u64,
28    /// Light protocol slot length
29    pub slot_length: u64,
30    /// Foresters can register for this phase.
31    pub registration_phase_length: u64,
32    /// Foresters can perform work in this phase.
33    pub active_phase_length: u64,
34    /// Foresters can report work to receive performance based rewards in this
35    /// phase.
36    pub report_work_phase_length: u64,
37    pub network_fee: u64,
38    pub cpi_context_size: u64,
39    pub finalize_counter_limit: u64,
40    /// Placeholder for future protocol updates.
41    pub place_holder: Pubkey,
42    pub place_holder_a: u64,
43    pub place_holder_b: u64,
44    pub place_holder_c: u64,
45    pub place_holder_d: u64,
46    pub place_holder_e: u64,
47    pub place_holder_f: u64,
48}
49
50impl Default for ProtocolConfig {
51    fn default() -> Self {
52        Self {
53            genesis_slot: 0,
54            min_weight: 1,
55            slot_length: 10,
56            registration_phase_length: 100,
57            active_phase_length: 1000,
58            report_work_phase_length: 100,
59            network_fee: 5000,
60            cpi_context_size: 20 * 1024 + 8,
61            finalize_counter_limit: 100,
62            place_holder: Pubkey::default(),
63            place_holder_a: 0,
64            place_holder_b: 0,
65            place_holder_c: 0,
66            place_holder_d: 0,
67            place_holder_e: 0,
68            place_holder_f: 0,
69        }
70    }
71}
72
73impl ProtocolConfig {
74    pub fn testnet_default() -> Self {
75        Self {
76            genesis_slot: 0,
77            min_weight: 1,
78            slot_length: 60,
79            registration_phase_length: 100,
80            active_phase_length: 1000,
81            report_work_phase_length: 100,
82            network_fee: 5000,
83            cpi_context_size: 20 * 1024 + 8,
84            finalize_counter_limit: 100,
85            place_holder: Pubkey::default(),
86            place_holder_a: 0,
87            place_holder_b: 0,
88            place_holder_c: 0,
89            place_holder_d: 0,
90            place_holder_e: 0,
91            place_holder_f: 0,
92        }
93    }
94}
95
96#[derive(Debug, Default, Clone, PartialEq, Eq)]
97pub enum EpochState {
98    Registration,
99    Active,
100    ReportWork,
101    Post,
102    #[default]
103    Pre,
104}
105
106/// Light Epoch Example:
107///
108///  Diagram of epochs 0 and 1.
109/// Registration 0 starts at genesis slot.
110/// |---- Registration 0 ----|------------------ Active 0 ------|---- Report Work 0 ----|---- Post 0 ----
111///                                        |-- Registration 1 --|------------------ Active 1 -----------------
112/// (Post epoch does not end unlike the other phases.)
113///
114/// let genesis = 0;
115/// let registration_phase_length = 100;
116/// let active_phase_length = 1000;
117/// let report_work_phase_length = 100;
118/// let slot = 10;
119///
120/// To get the latest registry epoch:
121/// - slot = 0;
122///   let current_registry_epoch = (slot - genesis) / active_phase_length;
123///   current_registry_epoch =  (0 - 0) / 1000 = 0;
124///   first active phase starts at genesis + registration_phase_length
125///   = 0 + 100 = 100;
126///
127/// To get the current active epoch:
128/// - slot = 100;
129///   let current_active_epoch =
130///   (slot - genesis - registration_phase_length) / active_phase_length;
131///   current_active_epoch = (100 - 0 - 100) / 1000 = 0;
132///
133/// Epoch 0:
134/// - Registration 0: 0 - 100
135/// - Active 0: 100 - 1100
136/// - Report Work 0: 1100 - 1200
137/// - Post 0: 1200 - inf
138///
139/// Epoch 1:
140/// - Registration 1: 1000 - 1100
141/// - Active 1: 1100 - 2100
142/// - Report Work 1: 2100 - 2200
143/// - Post 1: 2200 - inf
144///
145/// Epoch 2:
146/// - Registration 2: 2000 - 2100
147/// - Active 2: 2100 - 3100
148/// - Report Work 2: 3100 - 3200
149/// - Post 2: 3200 - inf
150///
151impl ProtocolConfig {
152    /// Current epoch including registration phase.
153    pub fn get_latest_register_epoch(&self, slot: u64) -> Result<u64> {
154        let slot = slot
155            .checked_sub(self.genesis_slot)
156            .ok_or(RegistryError::GetLatestRegisterEpochFailed)?;
157        Ok(slot / self.active_phase_length)
158    }
159
160    pub fn get_current_epoch(&self, slot: u64) -> u64 {
161        (slot.saturating_sub(self.genesis_slot)) / self.active_phase_length
162    }
163
164    pub fn get_current_active_epoch(&self, slot: u64) -> Result<u64> {
165        let slot = slot
166            .checked_sub(self.genesis_slot + self.registration_phase_length)
167            .ok_or(RegistryError::GetCurrentActiveEpochFailed)?;
168        Ok(slot / self.active_phase_length)
169    }
170
171    pub fn get_latest_register_epoch_progress(&self, slot: u64) -> Result<u64> {
172        Ok(slot
173            .checked_sub(self.genesis_slot)
174            .ok_or(RegistryError::ArithmeticUnderflow)?
175            % self.active_phase_length)
176    }
177
178    pub fn get_current_active_epoch_progress(&self, slot: u64) -> u64 {
179        (slot
180            .checked_sub(self.genesis_slot + self.registration_phase_length)
181            .unwrap())
182            % self.active_phase_length
183    }
184
185    /// In the last part of the active phase the registration phase starts.
186    /// Returns end slot of the registration phase/start slot of the next active phase.
187    pub fn is_registration_phase(&self, slot: u64) -> Result<u64> {
188        let latest_register_epoch = self.get_latest_register_epoch(slot)?;
189        let latest_register_epoch_progress = self.get_latest_register_epoch_progress(slot)?;
190        if latest_register_epoch_progress >= self.registration_phase_length {
191            return err!(RegistryError::NotInRegistrationPeriod);
192        }
193        Ok((latest_register_epoch) * self.active_phase_length
194            + self.genesis_slot
195            + self.registration_phase_length)
196    }
197
198    pub fn is_active_phase(&self, slot: u64, epoch: u64) -> Result<()> {
199        if self.get_current_active_epoch(slot)? != epoch {
200            return err!(RegistryError::NotInActivePhase);
201        }
202        Ok(())
203    }
204
205    pub fn is_report_work_phase(&self, slot: u64, epoch: u64) -> Result<()> {
206        self.is_active_phase(slot, epoch + 1)?;
207        let current_epoch_progress = self.get_current_active_epoch_progress(slot);
208        if current_epoch_progress >= self.report_work_phase_length {
209            return err!(RegistryError::NotInReportWorkPhase);
210        }
211        Ok(())
212    }
213
214    pub fn is_post_epoch(&self, slot: u64, epoch: u64) -> Result<()> {
215        if self.get_current_active_epoch(slot)? <= epoch {
216            return err!(RegistryError::InvalidEpoch);
217        }
218        Ok(())
219    }
220}