use std::collections::{BTreeMap, HashMap};
use dig_protocol::Bytes32;
use serde::{Deserialize, Serialize};
use crate::error::SlashingError;
use crate::evidence::envelope::SlashingEvidence;
use crate::evidence::verify::VerifiedEvidence;
use crate::manager::PerValidatorSlash;
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum PendingSlashStatus {
Accepted,
ChallengeOpen {
first_appeal_filed_epoch: u64,
appeal_count: u8,
},
Reverted {
winning_appeal_hash: Bytes32,
reverted_at_epoch: u64,
},
Finalised {
finalised_at_epoch: u64,
},
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum AppealOutcome {
Won,
Lost {
reason_hash: Bytes32,
},
Pending,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct AppealAttempt {
pub appeal_hash: Bytes32,
pub appellant_index: u32,
pub filed_epoch: u64,
pub outcome: AppealOutcome,
pub bond_mojos: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct PendingSlash {
pub evidence_hash: Bytes32,
pub evidence: SlashingEvidence,
pub verified: VerifiedEvidence,
pub status: PendingSlashStatus,
pub submitted_at_epoch: u64,
pub window_expires_at_epoch: u64,
pub base_slash_per_validator: Vec<PerValidatorSlash>,
pub reporter_bond_mojos: u64,
pub appeal_history: Vec<AppealAttempt>,
}
#[derive(Debug, Clone, Default)]
pub struct PendingSlashBook {
pending: HashMap<Bytes32, PendingSlash>,
by_window_expiry: BTreeMap<u64, Vec<Bytes32>>,
capacity: usize,
}
impl PendingSlashBook {
#[must_use]
pub fn new(capacity: usize) -> Self {
Self {
pending: HashMap::with_capacity(capacity.min(1_024)),
by_window_expiry: BTreeMap::new(),
capacity,
}
}
pub fn insert(&mut self, record: PendingSlash) -> Result<(), SlashingError> {
if self.pending.len() >= self.capacity {
return Err(SlashingError::PendingBookFull);
}
let hash = record.evidence_hash;
let expiry = record.window_expires_at_epoch;
self.pending.insert(hash, record);
self.by_window_expiry.entry(expiry).or_default().push(hash);
Ok(())
}
#[must_use]
pub fn get(&self, hash: &Bytes32) -> Option<&PendingSlash> {
self.pending.get(hash)
}
pub fn get_mut(&mut self, hash: &Bytes32) -> Option<&mut PendingSlash> {
self.pending.get_mut(hash)
}
pub fn remove(&mut self, hash: &Bytes32) -> Option<PendingSlash> {
let record = self.pending.remove(hash)?;
if let Some(vec) = self
.by_window_expiry
.get_mut(&record.window_expires_at_epoch)
{
vec.retain(|h| h != hash);
if vec.is_empty() {
self.by_window_expiry
.remove(&record.window_expires_at_epoch);
}
}
Some(record)
}
#[must_use]
pub fn submitted_after(&self, new_tip_epoch: u64) -> Vec<Bytes32> {
self.pending
.values()
.filter(|p| p.submitted_at_epoch > new_tip_epoch)
.map(|p| p.evidence_hash)
.collect()
}
#[must_use]
pub fn len(&self) -> usize {
self.pending.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.pending.is_empty()
}
#[must_use]
pub fn capacity(&self) -> usize {
self.capacity
}
#[must_use]
pub fn expired_by(&self, current_epoch: u64) -> Vec<Bytes32> {
self.by_window_expiry
.range(..current_epoch)
.flat_map(|(_, hashes)| hashes.iter())
.filter(|hash| {
matches!(
self.pending.get(hash).map(|p| &p.status),
Some(PendingSlashStatus::Accepted)
| Some(PendingSlashStatus::ChallengeOpen { .. }),
)
})
.copied()
.collect()
}
}