#[doc(hidden)]
pub const STR_002_MODULE_PRESENT: () = ();
use chia_protocol::Bytes32;
use dig_block::CheckpointSubmission;
use serde::{Deserialize, Serialize};
use crate::error::{CheckpointCompetitionError, EpochError};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum CompetitionStatus {
Pending,
Collecting,
WinnerSelected {
winner_hash: Bytes32,
winner_score: u64,
},
Finalized {
winner_hash: Bytes32,
l1_height: u32,
},
Failed,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CheckpointCompetition {
pub epoch: u64,
pub submissions: Vec<CheckpointSubmission>,
pub status: CompetitionStatus,
pub current_winner: Option<usize>,
}
impl CheckpointCompetition {
pub fn new(epoch: u64) -> Self {
Self {
epoch,
submissions: Vec::new(),
status: CompetitionStatus::Pending,
current_winner: None,
}
}
pub fn is_finalized(&self) -> bool {
matches!(self.status, CompetitionStatus::Finalized { .. })
}
pub fn start(&mut self) -> Result<(), CheckpointCompetitionError> {
if self.status != CompetitionStatus::Pending {
return Err(CheckpointCompetitionError::AlreadyFinalized);
}
self.status = CompetitionStatus::Collecting;
Ok(())
}
pub fn submit(
&mut self,
submission: CheckpointSubmission,
) -> Result<bool, CheckpointCompetitionError> {
match self.status {
CompetitionStatus::Pending => {
return Err(CheckpointCompetitionError::NotStarted);
}
CompetitionStatus::Finalized { .. } | CompetitionStatus::Failed => {
return Err(CheckpointCompetitionError::AlreadyFinalized);
}
CompetitionStatus::Collecting | CompetitionStatus::WinnerSelected { .. } => {}
}
if submission.checkpoint.epoch != self.epoch {
return Err(CheckpointCompetitionError::EpochMismatch {
expected: self.epoch,
got: submission.checkpoint.epoch,
});
}
let new_score = submission.score;
let current_score = match &self.status {
CompetitionStatus::WinnerSelected { winner_score, .. } => *winner_score,
_ => 0,
};
self.submissions.push(submission);
let idx = self.submissions.len() - 1;
let is_new_leader = match &self.status {
CompetitionStatus::WinnerSelected { .. } => new_score > current_score,
_ => new_score > 0,
};
if is_new_leader {
let winner_hash = self.submissions[idx].checkpoint.hash();
self.status = CompetitionStatus::WinnerSelected {
winner_hash,
winner_score: new_score,
};
self.current_winner = Some(idx);
Ok(true)
} else {
Err(CheckpointCompetitionError::ScoreNotHigher {
current: current_score,
submitted: new_score,
})
}
}
pub fn finalize(&mut self, l1_height: u32) -> Result<Bytes32, CheckpointCompetitionError> {
let winner_hash = match self.status {
CompetitionStatus::WinnerSelected { winner_hash, .. } => winner_hash,
CompetitionStatus::Finalized { .. } => {
return Err(CheckpointCompetitionError::AlreadyFinalized);
}
_ => return Err(CheckpointCompetitionError::NotStarted),
};
self.status = CompetitionStatus::Finalized {
winner_hash,
l1_height,
};
Ok(winner_hash)
}
pub fn fail(&mut self) -> Result<(), CheckpointCompetitionError> {
match self.status {
CompetitionStatus::Collecting | CompetitionStatus::WinnerSelected { .. } => {
self.status = CompetitionStatus::Failed;
Ok(())
}
CompetitionStatus::Finalized { .. } | CompetitionStatus::Failed => {
Err(CheckpointCompetitionError::AlreadyFinalized)
}
CompetitionStatus::Pending => Err(CheckpointCompetitionError::NotStarted),
}
}
pub fn winner(&self) -> Option<&CheckpointSubmission> {
self.current_winner.and_then(|i| self.submissions.get(i))
}
pub fn to_bytes(&self) -> Vec<u8> {
bincode::serialize(self).expect("CheckpointCompetition serialization should never fail")
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, EpochError> {
bincode::deserialize(bytes).map_err(|e| EpochError::InvalidData(e.to_string()))
}
}