1use {
2 agave_feature_set::{self as feature_set, FeatureSet},
3 solana_clock::Slot,
4 solana_cost_model::cost_tracker::CostTrackerLimits,
5 solana_epoch_schedule::EpochSchedule,
6 solana_pubkey::Pubkey,
7 std::{
8 collections::BTreeMap,
9 ops::Bound::{Excluded, Included},
10 },
11};
12
13pub const DEFAULT_MAX_ENTRY_BYTES_PER_SLOT: u64 = 20 * 1024 * 1024; #[derive(Clone, Copy, Debug, PartialEq)]
20pub struct SlotParams {
21 pub(crate) ns_per_slot: u128,
22 pub(crate) slots_per_year: f64,
23 pub(crate) hashes_per_tick: Option<u64>,
24 pub(crate) cost_tracker_limits: CostTrackerLimits,
25 pub(crate) max_data_shreds_per_slot: u32,
26 pub(crate) max_code_shreds_per_slot: u32,
27 pub(crate) max_entry_bytes_per_slot: u64,
28 pub(crate) partitioned_epoch_rewards_stake_account_stores_per_block: u64,
29 pub(crate) vat_to_burn_per_epoch: u64,
30}
31
32impl SlotParams {
33 pub(crate) fn genesis_baseline(
39 ns_per_slot: u128,
40 slots_per_year: f64,
41 hashes_per_tick: Option<u64>,
42 partitioned_epoch_rewards_stake_account_stores_per_block: u64,
43 ) -> Self {
44 Self {
45 ns_per_slot,
46 slots_per_year,
47 hashes_per_tick,
48 partitioned_epoch_rewards_stake_account_stores_per_block,
49 ..LEGACY_SLOT_PARAMS
50 }
51 }
52
53 pub const fn ns_per_slot(&self) -> u128 {
55 self.ns_per_slot
56 }
57
58 pub const fn slots_per_year(&self) -> f64 {
60 self.slots_per_year
61 }
62
63 pub const fn hashes_per_tick(&self) -> Option<u64> {
65 self.hashes_per_tick
66 }
67
68 pub const fn max_data_shreds_per_slot(&self) -> u32 {
70 self.max_data_shreds_per_slot
71 }
72
73 pub const fn max_code_shreds_per_slot(&self) -> u32 {
75 self.max_code_shreds_per_slot
76 }
77
78 pub const fn max_entry_bytes_per_slot(&self) -> u64 {
80 self.max_entry_bytes_per_slot
81 }
82
83 pub const fn partitioned_epoch_rewards_stake_account_stores_per_block(&self) -> u64 {
85 self.partitioned_epoch_rewards_stake_account_stores_per_block
86 }
87
88 pub const fn vat_to_burn_per_epoch(&self) -> u64 {
90 self.vat_to_burn_per_epoch
91 }
92
93 pub(crate) const fn cost_limits(self, raise_block_limits_to_100m: bool) -> CostTrackerLimits {
95 let cost_tracker_limits = self.cost_tracker_limits;
96 let (account_cost, block_cost) = if raise_block_limits_to_100m {
97 (
98 cost_tracker_limits
99 .account_cost
100 .saturating_mul(100)
101 .saturating_div(60),
102 cost_tracker_limits
103 .block_cost
104 .saturating_mul(100)
105 .saturating_div(60),
106 )
107 } else {
108 (
109 cost_tracker_limits.account_cost,
110 cost_tracker_limits.block_cost,
111 )
112 };
113
114 CostTrackerLimits::new(
115 account_cost,
116 block_cost,
117 cost_tracker_limits.allocated_data_size,
118 )
119 }
120}
121
122pub const LEGACY_HASHES_PER_TICK: u64 = 62_500;
123pub(crate) const LEGACY_SLOT_PARAMS: SlotParams = SlotParams {
124 ns_per_slot: 400_000_000,
125 slots_per_year: 78_892_314.984,
126 hashes_per_tick: Some(LEGACY_HASHES_PER_TICK),
127 cost_tracker_limits: CostTrackerLimits::new(24_000_000, 60_000_000, 100_000_000),
128 max_data_shreds_per_slot: 32_768,
129 max_code_shreds_per_slot: 32_768,
130 max_entry_bytes_per_slot: 20 * 1024 * 1024,
131 partitioned_epoch_rewards_stake_account_stores_per_block: 4096,
132 vat_to_burn_per_epoch: 1_600_000_000,
133};
134
135pub(crate) const SLOT_PARAMS_350MS: SlotParams = SlotParams {
136 ns_per_slot: 350_000_000,
137 slots_per_year: 90_162_645.696,
138 hashes_per_tick: Some(54_687),
139 cost_tracker_limits: CostTrackerLimits::new(21_000_000, 52_500_000, 87_500_000),
140 max_data_shreds_per_slot: 28_672,
141 max_code_shreds_per_slot: 28_672,
142 max_entry_bytes_per_slot: 18_350_080,
143 partitioned_epoch_rewards_stake_account_stores_per_block: 3_584,
144 vat_to_burn_per_epoch: 1_400_000_000,
145};
146
147pub(crate) const SLOT_PARAMS_300MS: SlotParams = SlotParams {
148 ns_per_slot: 300_000_000,
149 slots_per_year: 105_189_753.312,
150 hashes_per_tick: Some(46_875),
151 cost_tracker_limits: CostTrackerLimits::new(18_000_000, 45_000_000, 75_000_000),
152 max_data_shreds_per_slot: 24_576,
153 max_code_shreds_per_slot: 24_576,
154 max_entry_bytes_per_slot: 15_728_640,
155 partitioned_epoch_rewards_stake_account_stores_per_block: 3_072,
156 vat_to_burn_per_epoch: 1_200_000_000,
157};
158
159pub(crate) const SLOT_PARAMS_250MS: SlotParams = SlotParams {
160 ns_per_slot: 250_000_000,
161 slots_per_year: 126_227_703.974,
162 hashes_per_tick: Some(39_062),
163 cost_tracker_limits: CostTrackerLimits::new(15_000_000, 37_500_000, 62_500_000),
164 max_data_shreds_per_slot: 20_480,
165 max_code_shreds_per_slot: 20_480,
166 max_entry_bytes_per_slot: 13_107_200,
167 partitioned_epoch_rewards_stake_account_stores_per_block: 2_560,
168 vat_to_burn_per_epoch: 1_000_000_000,
169};
170
171pub(crate) const SLOT_PARAMS_200MS: SlotParams = SlotParams {
172 ns_per_slot: 200_000_000,
173 slots_per_year: 157_784_629.968,
174 hashes_per_tick: Some(31_250),
175 cost_tracker_limits: CostTrackerLimits::new(12_000_000, 30_000_000, 50_000_000),
176 max_data_shreds_per_slot: 16_384,
177 max_code_shreds_per_slot: 16_384,
178 max_entry_bytes_per_slot: 10_485_760,
179 partitioned_epoch_rewards_stake_account_stores_per_block: 2_048,
180 vat_to_burn_per_epoch: 800_000_000,
181};
182
183const SLOT_TIME_REDUCTION_PARAMS: [(Pubkey, SlotParams); 4] = [
185 (
186 feature_set::reduce_slot_time_to_350ms::ID,
187 SLOT_PARAMS_350MS,
188 ),
189 (
190 feature_set::reduce_slot_time_to_300ms::ID,
191 SLOT_PARAMS_300MS,
192 ),
193 (
194 feature_set::reduce_slot_time_to_250ms::ID,
195 SLOT_PARAMS_250MS,
196 ),
197 (
198 feature_set::reduce_slot_time_to_200ms::ID,
199 SLOT_PARAMS_200MS,
200 ),
201];
202
203pub fn slot_time_feature_gates() -> [(Pubkey, SlotParams); 4] {
205 SLOT_TIME_REDUCTION_PARAMS
206}
207
208pub fn slot_time_feature_ids() -> [Pubkey; 4] {
210 SLOT_TIME_REDUCTION_PARAMS.map(|(feature_id, _)| feature_id)
211}
212
213#[derive(Clone, Debug)]
220pub(crate) struct SlotParamsArchive {
221 param_transitions: BTreeMap<Slot, SlotParams>,
229}
230
231impl Default for SlotParamsArchive {
232 fn default() -> Self {
233 Self::new(
234 &FeatureSet::default(),
235 &EpochSchedule::default(),
236 LEGACY_SLOT_PARAMS,
237 )
238 }
239}
240
241impl SlotParamsArchive {
242 pub(crate) fn new(
244 feature_set: &FeatureSet,
245 epoch_schedule: &EpochSchedule,
246 baseline_params: SlotParams,
247 ) -> Self {
248 let mut param_transitions = BTreeMap::from([(0, baseline_params)]);
249 let mut earliest_same_or_shorter_slot = Slot::MAX;
250
251 for (feature_id, params) in slot_time_feature_gates().into_iter().rev() {
255 if params.ns_per_slot > baseline_params.ns_per_slot {
256 continue;
257 }
258 let Some(activation_slot) = feature_set.activated_slot(&feature_id) else {
259 continue;
260 };
261 let effective_slot = Self::feature_effective_slot(epoch_schedule, activation_slot);
262 if effective_slot < earliest_same_or_shorter_slot {
263 param_transitions.insert(effective_slot, params);
264 earliest_same_or_shorter_slot = effective_slot;
265 }
266 }
267
268 Self { param_transitions }
269 }
270
271 pub(crate) fn baseline_params(&self) -> SlotParams {
273 self.param_transitions
274 .first_key_value()
275 .map(|(_, params)| *params)
276 .unwrap_or(LEGACY_SLOT_PARAMS)
277 }
278
279 pub(crate) fn params_at_slot(&self, slot: Slot) -> SlotParams {
281 self.param_transitions
282 .range(..=slot)
283 .next_back()
284 .map(|(_, params)| *params)
285 .unwrap_or(LEGACY_SLOT_PARAMS)
286 }
287
288 pub(crate) fn param_transitions(&self) -> impl Iterator<Item = (Slot, SlotParams)> + '_ {
290 self.param_transitions
291 .iter()
292 .map(|(slot, params)| (*slot, *params))
293 }
294
295 pub(crate) fn slot_range_duration_nanos(&self, start_slot: Slot, end_slot: Slot) -> u128 {
298 if start_slot > end_slot {
299 return 0;
300 }
301
302 let mut cursor = start_slot;
303 let mut params = self.params_at_slot(start_slot);
304 let mut duration = 0u128;
305
306 for (&effective_slot, &effective_params) in self
307 .param_transitions
308 .range((Excluded(start_slot), Included(end_slot)))
309 {
310 duration = duration.saturating_add(
311 u128::from(effective_slot.saturating_sub(cursor))
312 .saturating_mul(params.ns_per_slot()),
313 );
314 cursor = effective_slot;
315 params = effective_params;
316 }
317
318 let remaining_slots = u128::from(end_slot.saturating_sub(cursor)) + 1;
319 duration.saturating_add(remaining_slots.saturating_mul(params.ns_per_slot()))
320 }
321
322 fn feature_effective_slot(epoch_schedule: &EpochSchedule, activation_slot: Slot) -> Slot {
328 let activation_epoch = epoch_schedule.get_epoch(activation_slot);
329 epoch_schedule.get_first_slot_in_epoch(activation_epoch.saturating_add(1))
330 }
331
332 pub(crate) fn any_slot_time_reduction_effective(
337 epoch_schedule: &EpochSchedule,
338 slot: Slot,
339 feature_set: &FeatureSet,
340 ns_per_slot: u128,
341 ) -> bool {
342 slot_time_feature_gates()
343 .into_iter()
344 .filter(|(_, params)| params.ns_per_slot() <= ns_per_slot)
345 .filter_map(|(feature_id, _)| feature_set.activated_slot(&feature_id))
346 .any(|activation_slot| {
347 Self::feature_effective_slot(epoch_schedule, activation_slot) <= slot
348 })
349 }
350}
351
352#[cfg(test)]
353mod tests {
354 use super::*;
355
356 #[test]
357 fn test_cost_limits_scaling_matches_simd_0525() {
358 for (params, expected_account_limit, expected_block_limit) in [
359 (LEGACY_SLOT_PARAMS, 40_000_000, 100_000_000),
360 (SLOT_PARAMS_350MS, 35_000_000, 87_500_000),
361 (SLOT_PARAMS_300MS, 30_000_000, 75_000_000),
362 (SLOT_PARAMS_250MS, 25_000_000, 62_500_000),
363 (SLOT_PARAMS_200MS, 20_000_000, 50_000_000),
364 ] {
365 let data_size_limit = params.cost_limits(false).allocated_data_size;
366 assert_eq!(
367 params.cost_limits(true),
368 CostTrackerLimits::new(
369 expected_account_limit,
370 expected_block_limit,
371 data_size_limit
372 )
373 );
374 }
375 }
376}