use {
agave_feature_set::{self as feature_set, FeatureSet},
solana_clock::Slot,
solana_cost_model::cost_tracker::CostTrackerLimits,
solana_epoch_schedule::EpochSchedule,
solana_pubkey::Pubkey,
std::{
collections::BTreeMap,
ops::Bound::{Excluded, Included},
},
};
pub const DEFAULT_MAX_ENTRY_BYTES_PER_SLOT: u64 = 20 * 1024 * 1024;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct SlotParams {
pub(crate) ns_per_slot: u128,
pub(crate) slots_per_year: f64,
pub(crate) hashes_per_tick: Option<u64>,
pub(crate) cost_tracker_limits: CostTrackerLimits,
pub(crate) max_data_shreds_per_slot: u32,
pub(crate) max_code_shreds_per_slot: u32,
pub(crate) max_entry_bytes_per_slot: u64,
pub(crate) partitioned_epoch_rewards_stake_account_stores_per_block: u64,
pub(crate) vat_to_burn_per_epoch: u64,
}
impl SlotParams {
pub(crate) fn genesis_baseline(
ns_per_slot: u128,
slots_per_year: f64,
hashes_per_tick: Option<u64>,
partitioned_epoch_rewards_stake_account_stores_per_block: u64,
) -> Self {
Self {
ns_per_slot,
slots_per_year,
hashes_per_tick,
partitioned_epoch_rewards_stake_account_stores_per_block,
..LEGACY_SLOT_PARAMS
}
}
pub const fn ns_per_slot(&self) -> u128 {
self.ns_per_slot
}
pub const fn slots_per_year(&self) -> f64 {
self.slots_per_year
}
pub const fn hashes_per_tick(&self) -> Option<u64> {
self.hashes_per_tick
}
pub const fn max_data_shreds_per_slot(&self) -> u32 {
self.max_data_shreds_per_slot
}
pub const fn max_code_shreds_per_slot(&self) -> u32 {
self.max_code_shreds_per_slot
}
pub const fn max_entry_bytes_per_slot(&self) -> u64 {
self.max_entry_bytes_per_slot
}
pub const fn partitioned_epoch_rewards_stake_account_stores_per_block(&self) -> u64 {
self.partitioned_epoch_rewards_stake_account_stores_per_block
}
pub const fn vat_to_burn_per_epoch(&self) -> u64 {
self.vat_to_burn_per_epoch
}
pub(crate) const fn cost_limits(self, raise_block_limits_to_100m: bool) -> CostTrackerLimits {
let cost_tracker_limits = self.cost_tracker_limits;
let (account_cost, block_cost) = if raise_block_limits_to_100m {
(
cost_tracker_limits
.account_cost
.saturating_mul(100)
.saturating_div(60),
cost_tracker_limits
.block_cost
.saturating_mul(100)
.saturating_div(60),
)
} else {
(
cost_tracker_limits.account_cost,
cost_tracker_limits.block_cost,
)
};
CostTrackerLimits::new(
account_cost,
block_cost,
cost_tracker_limits.allocated_data_size,
)
}
}
pub const LEGACY_HASHES_PER_TICK: u64 = 62_500;
pub(crate) const LEGACY_SLOT_PARAMS: SlotParams = SlotParams {
ns_per_slot: 400_000_000,
slots_per_year: 78_892_314.984,
hashes_per_tick: Some(LEGACY_HASHES_PER_TICK),
cost_tracker_limits: CostTrackerLimits::new(24_000_000, 60_000_000, 100_000_000),
max_data_shreds_per_slot: 32_768,
max_code_shreds_per_slot: 32_768,
max_entry_bytes_per_slot: 20 * 1024 * 1024,
partitioned_epoch_rewards_stake_account_stores_per_block: 4096,
vat_to_burn_per_epoch: 1_600_000_000,
};
pub(crate) const SLOT_PARAMS_350MS: SlotParams = SlotParams {
ns_per_slot: 350_000_000,
slots_per_year: 90_162_645.696,
hashes_per_tick: Some(54_687),
cost_tracker_limits: CostTrackerLimits::new(21_000_000, 52_500_000, 87_500_000),
max_data_shreds_per_slot: 28_672,
max_code_shreds_per_slot: 28_672,
max_entry_bytes_per_slot: 18_350_080,
partitioned_epoch_rewards_stake_account_stores_per_block: 3_584,
vat_to_burn_per_epoch: 1_400_000_000,
};
pub(crate) const SLOT_PARAMS_300MS: SlotParams = SlotParams {
ns_per_slot: 300_000_000,
slots_per_year: 105_189_753.312,
hashes_per_tick: Some(46_875),
cost_tracker_limits: CostTrackerLimits::new(18_000_000, 45_000_000, 75_000_000),
max_data_shreds_per_slot: 24_576,
max_code_shreds_per_slot: 24_576,
max_entry_bytes_per_slot: 15_728_640,
partitioned_epoch_rewards_stake_account_stores_per_block: 3_072,
vat_to_burn_per_epoch: 1_200_000_000,
};
pub(crate) const SLOT_PARAMS_250MS: SlotParams = SlotParams {
ns_per_slot: 250_000_000,
slots_per_year: 126_227_703.974,
hashes_per_tick: Some(39_062),
cost_tracker_limits: CostTrackerLimits::new(15_000_000, 37_500_000, 62_500_000),
max_data_shreds_per_slot: 20_480,
max_code_shreds_per_slot: 20_480,
max_entry_bytes_per_slot: 13_107_200,
partitioned_epoch_rewards_stake_account_stores_per_block: 2_560,
vat_to_burn_per_epoch: 1_000_000_000,
};
pub(crate) const SLOT_PARAMS_200MS: SlotParams = SlotParams {
ns_per_slot: 200_000_000,
slots_per_year: 157_784_629.968,
hashes_per_tick: Some(31_250),
cost_tracker_limits: CostTrackerLimits::new(12_000_000, 30_000_000, 50_000_000),
max_data_shreds_per_slot: 16_384,
max_code_shreds_per_slot: 16_384,
max_entry_bytes_per_slot: 10_485_760,
partitioned_epoch_rewards_stake_account_stores_per_block: 2_048,
vat_to_burn_per_epoch: 800_000_000,
};
const SLOT_TIME_REDUCTION_PARAMS: [(Pubkey, SlotParams); 4] = [
(
feature_set::reduce_slot_time_to_350ms::ID,
SLOT_PARAMS_350MS,
),
(
feature_set::reduce_slot_time_to_300ms::ID,
SLOT_PARAMS_300MS,
),
(
feature_set::reduce_slot_time_to_250ms::ID,
SLOT_PARAMS_250MS,
),
(
feature_set::reduce_slot_time_to_200ms::ID,
SLOT_PARAMS_200MS,
),
];
pub fn slot_time_feature_gates() -> [(Pubkey, SlotParams); 4] {
SLOT_TIME_REDUCTION_PARAMS
}
pub fn slot_time_feature_ids() -> [Pubkey; 4] {
SLOT_TIME_REDUCTION_PARAMS.map(|(feature_id, _)| feature_id)
}
#[derive(Clone, Debug)]
pub(crate) struct SlotParamsArchive {
param_transitions: BTreeMap<Slot, SlotParams>,
}
impl Default for SlotParamsArchive {
fn default() -> Self {
Self::new(
&FeatureSet::default(),
&EpochSchedule::default(),
LEGACY_SLOT_PARAMS,
)
}
}
impl SlotParamsArchive {
pub(crate) fn new(
feature_set: &FeatureSet,
epoch_schedule: &EpochSchedule,
baseline_params: SlotParams,
) -> Self {
let mut param_transitions = BTreeMap::from([(0, baseline_params)]);
let mut earliest_same_or_shorter_slot = Slot::MAX;
for (feature_id, params) in slot_time_feature_gates().into_iter().rev() {
if params.ns_per_slot > baseline_params.ns_per_slot {
continue;
}
let Some(activation_slot) = feature_set.activated_slot(&feature_id) else {
continue;
};
let effective_slot = Self::feature_effective_slot(epoch_schedule, activation_slot);
if effective_slot < earliest_same_or_shorter_slot {
param_transitions.insert(effective_slot, params);
earliest_same_or_shorter_slot = effective_slot;
}
}
Self { param_transitions }
}
pub(crate) fn baseline_params(&self) -> SlotParams {
self.param_transitions
.first_key_value()
.map(|(_, params)| *params)
.unwrap_or(LEGACY_SLOT_PARAMS)
}
pub(crate) fn params_at_slot(&self, slot: Slot) -> SlotParams {
self.param_transitions
.range(..=slot)
.next_back()
.map(|(_, params)| *params)
.unwrap_or(LEGACY_SLOT_PARAMS)
}
pub(crate) fn param_transitions(&self) -> impl Iterator<Item = (Slot, SlotParams)> + '_ {
self.param_transitions
.iter()
.map(|(slot, params)| (*slot, *params))
}
pub(crate) fn slot_range_duration_nanos(&self, start_slot: Slot, end_slot: Slot) -> u128 {
if start_slot > end_slot {
return 0;
}
let mut cursor = start_slot;
let mut params = self.params_at_slot(start_slot);
let mut duration = 0u128;
for (&effective_slot, &effective_params) in self
.param_transitions
.range((Excluded(start_slot), Included(end_slot)))
{
duration = duration.saturating_add(
u128::from(effective_slot.saturating_sub(cursor))
.saturating_mul(params.ns_per_slot()),
);
cursor = effective_slot;
params = effective_params;
}
let remaining_slots = u128::from(end_slot.saturating_sub(cursor)) + 1;
duration.saturating_add(remaining_slots.saturating_mul(params.ns_per_slot()))
}
fn feature_effective_slot(epoch_schedule: &EpochSchedule, activation_slot: Slot) -> Slot {
let activation_epoch = epoch_schedule.get_epoch(activation_slot);
epoch_schedule.get_first_slot_in_epoch(activation_epoch.saturating_add(1))
}
pub(crate) fn any_slot_time_reduction_effective(
epoch_schedule: &EpochSchedule,
slot: Slot,
feature_set: &FeatureSet,
ns_per_slot: u128,
) -> bool {
slot_time_feature_gates()
.into_iter()
.filter(|(_, params)| params.ns_per_slot() <= ns_per_slot)
.filter_map(|(feature_id, _)| feature_set.activated_slot(&feature_id))
.any(|activation_slot| {
Self::feature_effective_slot(epoch_schedule, activation_slot) <= slot
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cost_limits_scaling_matches_simd_0525() {
for (params, expected_account_limit, expected_block_limit) in [
(LEGACY_SLOT_PARAMS, 40_000_000, 100_000_000),
(SLOT_PARAMS_350MS, 35_000_000, 87_500_000),
(SLOT_PARAMS_300MS, 30_000_000, 75_000_000),
(SLOT_PARAMS_250MS, 25_000_000, 62_500_000),
(SLOT_PARAMS_200MS, 20_000_000, 50_000_000),
] {
let data_size_limit = params.cost_limits(false).allocated_data_size;
assert_eq!(
params.cost_limits(true),
CostTrackerLimits::new(
expected_account_limit,
expected_block_limit,
data_size_limit
)
);
}
}
}