use std::collections::BTreeMap;
use dig_epoch::CORRELATION_WINDOW_EPOCHS;
use dig_protocol::Bytes32;
use serde::{Deserialize, Serialize};
use crate::bonds::BondEscrow;
use crate::error::SlashingError;
use crate::inactivity::{InactivityScoreTracker, in_finality_stall};
use crate::manager::{FinalisationResult, SlashingManager};
use crate::participation::{FlagDelta, ParticipationTracker, compute_flag_deltas};
use crate::protection::SlashingProtection;
use crate::traits::{CollateralSlasher, EffectiveBalanceView, RewardPayout, ValidatorView};
pub trait JustificationView {
fn latest_finalized_epoch(&self) -> u64;
fn current_justified_checkpoint(&self) -> crate::evidence::Checkpoint {
crate::evidence::Checkpoint {
epoch: 0,
root: dig_protocol::Bytes32::new([0u8; 32]),
}
}
fn previous_justified_checkpoint(&self) -> crate::evidence::Checkpoint {
crate::evidence::Checkpoint {
epoch: 0,
root: dig_protocol::Bytes32::new([0u8; 32]),
}
}
fn finalized_checkpoint(&self) -> crate::evidence::Checkpoint {
crate::evidence::Checkpoint {
epoch: self.latest_finalized_epoch(),
root: dig_protocol::Bytes32::new([0u8; 32]),
}
}
fn canonical_block_root_at_slot(&self, _slot: u64) -> Option<dig_protocol::Bytes32> {
None
}
fn canonical_target_root_for_epoch(&self, _epoch: u64) -> Option<dig_protocol::Bytes32> {
None
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct EpochBoundaryReport {
pub flag_deltas: Vec<FlagDelta>,
pub inactivity_penalties: Vec<(u32, u64)>,
pub finalisations: Vec<FinalisationResult>,
pub in_finality_stall: bool,
pub pruned_entries: usize,
}
#[allow(clippy::too_many_arguments)]
pub fn run_epoch_boundary(
manager: &mut SlashingManager,
participation: &mut ParticipationTracker,
inactivity: &mut InactivityScoreTracker,
validator_set: &mut dyn ValidatorView,
effective_balances: &dyn EffectiveBalanceView,
bond_escrow: &mut dyn BondEscrow,
reward_payout: &mut dyn RewardPayout,
justification: &dyn JustificationView,
current_epoch_ending: u64,
validator_count: usize,
total_active_balance: u64,
) -> EpochBoundaryReport {
let finalized_epoch = justification.latest_finalized_epoch();
let stall = in_finality_stall(current_epoch_ending, finalized_epoch);
let flag_deltas = compute_flag_deltas(
participation,
effective_balances,
total_active_balance,
stall,
);
for fd in &flag_deltas {
if fd.reward == 0 {
continue;
}
if let Some(entry) = validator_set.get(fd.validator_index) {
reward_payout.pay(entry.puzzle_hash(), fd.reward);
}
}
inactivity.update_for_epoch(participation, stall);
let inactivity_penalties = inactivity.epoch_penalties(effective_balances, stall);
for &(idx, penalty_mojos) in &inactivity_penalties {
if let Some(entry) = validator_set.get_mut(idx) {
entry.slash_absolute(penalty_mojos, current_epoch_ending);
}
}
let finalisations = manager.finalise_expired_slashes(
validator_set,
effective_balances,
bond_escrow,
total_active_balance,
);
participation.rotate_epoch(current_epoch_ending + 1, validator_count);
manager.set_epoch(current_epoch_ending + 1);
if inactivity.validator_count() != validator_count {
inactivity.resize_for(validator_count);
}
let cutoff = current_epoch_ending.saturating_sub(u64::from(CORRELATION_WINDOW_EPOCHS));
let pruned_entries = manager.prune_processed_older_than(cutoff);
EpochBoundaryReport {
flag_deltas,
inactivity_penalties,
finalisations,
in_finality_stall: stall,
pruned_entries,
}
}
#[allow(dead_code)]
type _KeepBTreeMap<K, V> = BTreeMap<K, V>;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ReorgReport {
pub rewound_pending_slashes: Vec<Bytes32>,
pub participation_epochs_dropped: u64,
pub inactivity_epochs_dropped: u64,
pub protection_rewound: bool,
}
#[allow(clippy::too_many_arguments)]
pub fn rewind_all_on_reorg(
manager: &mut SlashingManager,
participation: &mut ParticipationTracker,
inactivity: &mut InactivityScoreTracker,
protection: &mut SlashingProtection,
validator_set: &mut dyn ValidatorView,
collateral: Option<&mut dyn CollateralSlasher>,
bond_escrow: &mut dyn BondEscrow,
new_tip_epoch: u64,
new_tip_slot: u64,
validator_count: usize,
) -> Result<ReorgReport, SlashingError> {
let current_epoch = manager.current_epoch();
let depth = current_epoch.saturating_sub(new_tip_epoch);
let limit = u64::from(CORRELATION_WINDOW_EPOCHS);
if depth > limit {
return Err(SlashingError::ReorgTooDeep { depth, limit });
}
let rewound_pending_slashes =
manager.rewind_on_reorg(new_tip_epoch, validator_set, collateral, bond_escrow);
let participation_epochs_dropped =
participation.rewind_on_reorg(new_tip_epoch, validator_count);
let inactivity_epochs_dropped = inactivity.rewind_on_reorg(depth);
protection.reconcile_with_chain_tip(new_tip_slot, new_tip_epoch);
manager.set_epoch(new_tip_epoch);
Ok(ReorgReport {
rewound_pending_slashes,
participation_epochs_dropped,
inactivity_epochs_dropped,
protection_rewound: true,
})
}