#[doc(hidden)]
pub const STR_002_MODULE_PRESENT: () = ();
use std::collections::HashMap;
use chia_protocol::Bytes32;
use parking_lot::RwLock;
use crate::arithmetic::l1_range_for_epoch;
use crate::constants::{EPOCH_L1_BLOCKS, GENESIS_HEIGHT};
use crate::error::EpochError;
use crate::phase::l1_progress_phase_for_network_epoch;
use crate::types::checkpoint_competition::CheckpointCompetition;
use crate::types::dfsp::DfspCloseSnapshot;
use crate::types::epoch_info::EpochInfo;
use crate::types::epoch_phase::{EpochPhase, PhaseTransition};
use crate::types::epoch_summary::EpochSummary;
use crate::types::events::EpochStats;
use crate::types::reward::RewardDistribution;
struct EpochManagerInner {
network_id: Bytes32,
genesis_l1_height: u32,
current_epoch: EpochInfo,
competition: CheckpointCompetition,
summaries: Vec<EpochSummary>,
rewards: HashMap<u64, RewardDistribution>,
}
pub struct EpochManager {
inner: RwLock<EpochManagerInner>,
}
impl EpochManager {
pub fn new(network_id: Bytes32, genesis_l1_height: u32, initial_state_root: Bytes32) -> Self {
let current_epoch =
EpochInfo::new(0, genesis_l1_height, GENESIS_HEIGHT, initial_state_root);
Self {
inner: RwLock::new(EpochManagerInner {
network_id,
genesis_l1_height,
current_epoch,
competition: CheckpointCompetition::new(0),
summaries: Vec::new(),
rewards: HashMap::new(),
}),
}
}
pub fn current_epoch(&self) -> u64 {
self.inner.read().current_epoch.epoch
}
pub fn current_epoch_info(&self) -> EpochInfo {
self.inner.read().current_epoch.clone()
}
pub fn current_phase(&self) -> EpochPhase {
self.inner.read().current_epoch.phase
}
pub fn genesis_l1_height(&self) -> u32 {
self.inner.read().genesis_l1_height
}
pub fn network_id(&self) -> Bytes32 {
self.inner.read().network_id
}
pub fn epoch_for_l1_height(&self, l1_height: u32) -> u64 {
let g = self.genesis_l1_height();
if l1_height <= g {
0
} else {
((l1_height - g) / EPOCH_L1_BLOCKS) as u64
}
}
pub fn l1_range_for_epoch(&self, epoch: u64) -> (u32, u32) {
l1_range_for_epoch(self.genesis_l1_height(), epoch)
}
pub fn update_phase(&self, l1_height: u32) -> Option<PhaseTransition> {
let mut inner = self.inner.write();
let old_phase = inner.current_epoch.phase;
let new_phase = l1_progress_phase_for_network_epoch(
inner.genesis_l1_height,
inner.current_epoch.epoch,
l1_height,
);
if new_phase != old_phase {
inner.current_epoch.phase = new_phase;
Some(PhaseTransition {
epoch: inner.current_epoch.epoch,
from: old_phase,
to: new_phase,
l1_height,
})
} else {
None
}
}
pub fn should_advance(&self, _l1_height: u32) -> bool {
self.current_phase() == EpochPhase::Complete
}
pub fn record_block(&self, fees: u64, tx_count: u64) -> Result<(), EpochError> {
let mut inner = self.inner.write();
if inner.current_epoch.phase != EpochPhase::BlockProduction {
return Err(EpochError::PhaseMismatch {
expected: EpochPhase::BlockProduction,
got: inner.current_epoch.phase,
});
}
inner.current_epoch.record_block(fees, tx_count);
Ok(())
}
pub fn set_current_epoch_chain_totals(&self, blocks: u32, fees: u64, txns: u64) {
let mut inner = self.inner.write();
inner.current_epoch.blocks_produced = blocks;
inner.current_epoch.total_fees = fees;
inner.current_epoch.total_transactions = txns;
}
pub fn set_current_epoch_dfsp_close_snapshot(
&self,
snap: DfspCloseSnapshot,
) -> Result<(), EpochError> {
let mut inner = self.inner.write();
if inner.current_epoch.phase != EpochPhase::Finalization {
return Err(EpochError::PhaseMismatch {
expected: EpochPhase::Finalization,
got: inner.current_epoch.phase,
});
}
inner.current_epoch.collateral_registry_root = snap.collateral_registry_root;
inner.current_epoch.cid_state_root = snap.cid_state_root;
inner.current_epoch.node_registry_root = snap.node_registry_root;
inner.current_epoch.namespace_epoch_root = snap.namespace_epoch_root;
inner.current_epoch.dfsp_issuance_total = snap.dfsp_issuance_total;
inner.current_epoch.active_cid_count = snap.active_cid_count;
inner.current_epoch.active_node_count = snap.active_node_count;
Ok(())
}
pub fn advance_epoch(&self, _l1_height: u32, state_root: Bytes32) -> Result<u64, EpochError> {
let mut inner = self.inner.write();
let current_epoch_num = inner.current_epoch.epoch;
if inner.current_epoch.phase != EpochPhase::Complete {
return Err(EpochError::EpochNotComplete(current_epoch_num));
}
if !inner.competition.is_finalized() {
return Err(EpochError::NoFinalizedCheckpoint(current_epoch_num));
}
let old_info = inner.current_epoch.clone();
let next_epoch = current_epoch_num + 1;
let next_start_l1 = inner.genesis_l1_height + (next_epoch as u32 * EPOCH_L1_BLOCKS);
let next_start_l2 = old_info.start_l2_height + crate::constants::BLOCKS_PER_EPOCH;
inner.summaries.push(EpochSummary::from(old_info));
inner.current_epoch = EpochInfo::new(next_epoch, next_start_l1, next_start_l2, state_root);
inner.competition = CheckpointCompetition::new(next_epoch);
Ok(next_epoch)
}
pub fn get_epoch_info(&self) -> EpochInfo {
self.current_epoch_info()
}
pub fn get_epoch_summary(&self, epoch: u64) -> Option<EpochSummary> {
self.inner
.read()
.summaries
.iter()
.find(|s| s.epoch == epoch)
.cloned()
}
pub fn recent_summaries(&self, n: usize) -> Vec<EpochSummary> {
let inner = self.inner.read();
let len = inner.summaries.len();
let start = len.saturating_sub(n);
inner.summaries[start..].to_vec()
}
pub fn total_stats(&self) -> EpochStats {
let inner = self.inner.read();
let mut stats = EpochStats {
total_epochs: inner.summaries.len() as u64 + 1,
finalized_epochs: 0,
total_blocks: 0,
total_transactions: 0,
total_fees: 0,
};
for s in &inner.summaries {
if s.finalized {
stats.finalized_epochs += 1;
}
stats.total_blocks += s.blocks as u64;
stats.total_transactions += s.transactions;
stats.total_fees += s.fees;
}
let cur = &inner.current_epoch;
if cur.is_finalized() {
stats.finalized_epochs += 1;
}
stats.total_blocks += cur.blocks_produced as u64;
stats.total_transactions += cur.total_transactions;
stats.total_fees += cur.total_fees;
stats
}
pub fn get_rewards(&self, epoch: u64) -> Option<RewardDistribution> {
self.inner.read().rewards.get(&epoch).cloned()
}
pub fn store_rewards(&self, distribution: RewardDistribution) {
let mut inner = self.inner.write();
inner.rewards.insert(distribution.epoch, distribution);
}
pub fn competition(&self) -> CheckpointCompetition {
self.inner.read().competition.clone()
}
#[doc(hidden)]
pub fn __set_competition_for_test(&self, competition: CheckpointCompetition) {
self.inner.write().competition = competition;
}
#[doc(hidden)]
pub fn __force_phase_for_test(&self, phase: EpochPhase) {
self.inner.write().current_epoch.phase = phase;
}
pub fn start_checkpoint_competition(&self) -> Result<(), EpochError> {
let mut inner = self.inner.write();
if inner.current_epoch.phase != EpochPhase::Checkpoint {
return Err(EpochError::PhaseMismatch {
expected: EpochPhase::Checkpoint,
got: inner.current_epoch.phase,
});
}
inner.competition.start()?;
Ok(())
}
pub fn submit_checkpoint(
&self,
submission: dig_block::CheckpointSubmission,
) -> Result<bool, EpochError> {
let mut inner = self.inner.write();
if inner.current_epoch.phase != EpochPhase::Checkpoint {
return Err(EpochError::PhaseMismatch {
expected: EpochPhase::Checkpoint,
got: inner.current_epoch.phase,
});
}
Ok(inner.competition.submit(submission)?)
}
pub fn finalize_competition(
&self,
epoch: u64,
l1_height: u32,
) -> Result<Option<dig_block::Checkpoint>, EpochError> {
let mut inner = self.inner.write();
if inner.current_epoch.phase != EpochPhase::Finalization {
return Err(EpochError::PhaseMismatch {
expected: EpochPhase::Finalization,
got: inner.current_epoch.phase,
});
}
if inner.competition.epoch != epoch {
return Err(EpochError::EpochMismatch {
expected: inner.competition.epoch,
got: epoch,
});
}
if inner.competition.current_winner.is_none() {
inner.competition.fail()?;
return Ok(None);
}
inner.competition.finalize(l1_height)?;
let winner_idx = inner.competition.current_winner.unwrap();
let winning_checkpoint = inner.competition.submissions[winner_idx].checkpoint.clone();
inner
.current_epoch
.set_checkpoint(winning_checkpoint.clone());
Ok(Some(winning_checkpoint))
}
pub fn get_competition(&self, epoch: u64) -> Option<CheckpointCompetition> {
let inner = self.inner.read();
if inner.competition.epoch == epoch {
Some(inner.competition.clone())
} else {
None
}
}
}