use super::error::{ParalockError, ParalockErrorCode};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NetworkUpgrade {
pub name: String,
pub branch_id: u32,
pub activation_height: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct NoUpgradeBefore {
tip: u32,
refund_height: u32,
}
impl NoUpgradeBefore {
pub const fn tip(&self) -> u32 {
self.tip
}
pub const fn refund_height(&self) -> u32 {
self.refund_height
}
pub const fn covers(&self, refund_height: u32) -> bool {
self.refund_height >= refund_height
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UpgradeCheck {
Checked(NoUpgradeBefore),
BackendCannotReport,
}
pub fn assert_no_upgrade_before(
upgrades: &[NetworkUpgrade],
tip: u32,
refund_height: u32,
) -> Result<NoUpgradeBefore, ParalockError> {
let next_block = u64::from(tip) + 1;
let last = u64::from(refund_height) + 1;
let first = upgrades
.iter()
.filter(|u| u64::from(u.activation_height) > next_block && u64::from(u.activation_height) <= last)
.min_by_key(|u| u.activation_height);
if let Some(upgrade) = first {
return Err(ParalockError::new(
ParalockErrorCode::UnsafeTimeouts,
format!(
"network upgrade {} activates at block {}, before the refund height",
upgrade.name, upgrade.activation_height
),
));
}
Ok(NoUpgradeBefore { tip, refund_height })
}
pub(crate) fn require_upgrade_check(check: &UpgradeCheck, refund_height: u32) -> Result<(), ParalockError> {
match check {
UpgradeCheck::Checked(proof) if proof.covers(refund_height) => Ok(()),
UpgradeCheck::Checked(proof) => Err(ParalockError::new(
ParalockErrorCode::UnsafeTimeouts,
format!("the network-upgrade check covers refund height {}, not {refund_height}", proof.refund_height),
)),
UpgradeCheck::BackendCannotReport => Ok(()),
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
use super::*;
fn nu(height: u32) -> NetworkUpgrade {
NetworkUpgrade { name: "NU7".into(), branch_id: 0x7719_0ad9, activation_height: height }
}
#[test]
fn refuses_an_upgrade_before_the_refund_can_be_mined() {
let tip = 3_000_000;
let refund = tip + 144;
assert!(assert_no_upgrade_before(&[nu(2_000_000), nu(tip + 1)], tip, refund).is_ok());
for height in [tip + 2, refund, refund + 1] {
let err = assert_no_upgrade_before(&[nu(height)], tip, refund).unwrap_err();
assert_eq!(err.code, ParalockErrorCode::UnsafeTimeouts);
assert_eq!(
err.message,
format!("network upgrade NU7 activates at block {height}, before the refund height")
);
}
let proof = assert_no_upgrade_before(&[nu(refund + 2)], tip, refund).unwrap();
assert_eq!((proof.tip(), proof.refund_height()), (tip, refund));
assert!(require_upgrade_check(&UpgradeCheck::Checked(proof), refund).is_ok());
assert!(require_upgrade_check(&UpgradeCheck::Checked(proof), refund - 1).is_ok());
assert!(require_upgrade_check(&UpgradeCheck::Checked(proof), refund + 1).is_err());
assert!(require_upgrade_check(&UpgradeCheck::BackendCannotReport, refund).is_ok());
assert!(assert_no_upgrade_before(&[nu(u32::MAX)], u32::MAX - 1, u32::MAX).is_ok());
}
}