use std::time::Duration;
use color_eyre::Report;
use zakura_chain::{
block::Height,
chain_tip::ChainTip,
parameters::{Network, NetworkUpgrade, POST_BLOSSOM_POW_TARGET_SPACING},
};
use crate::application::release_version;
pub const ESTIMATED_RELEASE_HEIGHT: u32 = 3_501_907;
pub const ESTIMATED_BLOCKS_PER_DAY: u32 = 24 * 60 * 60 / POST_BLOSSOM_POW_TARGET_SPACING;
pub const EOS_PANIC_AFTER: u32 = 30;
pub const EOS_WARN_AFTER: u32 = EOS_PANIC_AFTER - 3;
pub const EOS_PANIC_MESSAGE_HEADER: &str = "Zakura refuses to run";
pub const EOS_WARN_MESSAGE_HEADER: &str = "Your Zakura release is too old and it will stop running";
const CHECK_INTERVAL: Duration = Duration::from_secs(60 * 60);
const INITIAL_WAIT: Duration = Duration::from_secs(10);
const EOS_ENFORCED_METRIC: &str = "end_of_support.enforced";
const EOS_HEIGHT_METRIC: &str = "end_of_support.last_supported_height";
const EOS_REMAINING_BLOCKS_METRIC: &str = "end_of_support.remaining_blocks";
pub async fn start(
network: Network,
latest_chain_tip: impl ChainTip + std::fmt::Debug,
) -> Result<(), Report> {
info!("Starting end of support task");
if let Some(last_supported_height) = end_of_support_height(&network) {
metrics::gauge!(EOS_ENFORCED_METRIC).set(1.0);
metrics::gauge!(EOS_HEIGHT_METRIC).set(f64::from(last_supported_height.0));
} else {
metrics::gauge!(EOS_ENFORCED_METRIC).set(0.0);
}
tokio::time::sleep(INITIAL_WAIT).await;
loop {
if network == Network::Mainnet {
if let Some(tip_height) = latest_chain_tip.best_tip_height() {
if let Some(remaining_blocks) = remaining_blocks(tip_height, &network) {
metrics::gauge!(EOS_REMAINING_BLOCKS_METRIC).set(remaining_blocks as f64);
}
check(tip_height, &network);
}
} else {
info!("Release always valid in Testnet");
}
tokio::time::sleep(CHECK_INTERVAL).await;
}
}
pub fn end_of_support_height(network: &Network) -> Option<Height> {
(network == &Network::Mainnet).then(|| estimated_height_after_release(network, EOS_PANIC_AFTER))
}
fn estimated_height_after_release(network: &Network, days: u32) -> Height {
let mut height = i64::from(ESTIMATED_RELEASE_HEIGHT);
let mut remaining_seconds = i64::from(days) * 24 * 60 * 60;
let target_spacings: Vec<_> = NetworkUpgrade::target_spacings(network).collect();
for (index, (_, target_spacing)) in target_spacings.iter().enumerate() {
let target_spacing = target_spacing.num_seconds();
let remaining_blocks = remaining_seconds / target_spacing;
let blocks_until_next_spacing = target_spacings
.get(index + 1)
.map(|(next_height, _)| (i64::from(next_height.0) - height - 1).max(0));
match blocks_until_next_spacing {
Some(blocks) if blocks < remaining_blocks => {
height += blocks;
remaining_seconds -= blocks * target_spacing;
}
_ => {
height += remaining_blocks;
break;
}
}
}
Height(u32::try_from(height).expect("the support window ends below the maximum height"))
}
pub fn remaining_blocks(tip_height: Height, network: &Network) -> Option<i64> {
end_of_support_height(network)
.map(|last_supported_height| i64::from(last_supported_height.0) - i64::from(tip_height.0))
}
pub fn check(tip_height: Height, network: &Network) {
info!("Checking if Zakura release is inside support range ...");
let Some(panic_height) = end_of_support_height(network) else {
info!("Release always valid outside Mainnet");
return;
};
let warn_height = estimated_height_after_release(network, EOS_WARN_AFTER);
if tip_height > panic_height {
panic!(
"{EOS_PANIC_MESSAGE_HEADER} if the release date is older than {EOS_PANIC_AFTER} days. \
\nRelease name: {}, Estimated release height: {ESTIMATED_RELEASE_HEIGHT} \
\nHint: Download and install the latest Zakura release from: https://github.com/zakura-core/zakura/releases/latest",
release_version()
);
} else if tip_height > warn_height {
warn!(
"{EOS_WARN_MESSAGE_HEADER} at block {}. \
\nRelease name: {}, Estimated release height: {ESTIMATED_RELEASE_HEIGHT} \
\nHint: Download and install the latest Zakura release from: https://github.com/zakura-core/zakura/releases/latest", panic_height.0, release_version()
);
} else {
info!("Zakura release is supported until block {}, please report bugs at https://github.com/zakura-core/zakura/issues", panic_height.0);
}
}
#[cfg(test)]
mod tests {
use zakura_chain::parameters::testnet::{
ConfiguredActivationHeights, ConfiguredCheckpoints, RegtestParameters,
};
use super::*;
fn blocks_per_day(upgrade: NetworkUpgrade) -> u32 {
let seconds_per_day = 24 * 60 * 60;
let spacing = u32::try_from(upgrade.target_spacing().num_seconds())
.expect("target spacings are positive and fit in u32");
seconds_per_day / spacing
}
fn regtest_with_blossom(blossom: u32) -> Network {
let genesis = Network::new_regtest(Default::default()).genesis_hash();
Network::new_regtest(RegtestParameters {
activation_heights: ConfiguredActivationHeights {
blossom: Some(blossom),
..Default::default()
},
checkpoints: Some(ConfiguredCheckpoints::HeightsAndHashes(vec![
(Height(0), genesis),
(Height(blossom - 1), zakura_chain::block::Hash([1; 32])),
])),
..Default::default()
})
}
#[test]
fn mainnet_end_of_support_height_counts_75_second_blocks() {
let _init_guard = zakura_test::init();
let post_blossom_blocks_per_day = blocks_per_day(NetworkUpgrade::Blossom);
assert_eq!(
end_of_support_height(&Network::Mainnet),
Some(Height(
ESTIMATED_RELEASE_HEIGHT + EOS_PANIC_AFTER * post_blossom_blocks_per_day
)),
);
assert_eq!(
estimated_height_after_release(&Network::Mainnet, EOS_WARN_AFTER),
Height(ESTIMATED_RELEASE_HEIGHT + EOS_WARN_AFTER * post_blossom_blocks_per_day),
);
}
#[test]
fn end_of_support_height_follows_target_spacing_changes() {
let _init_guard = zakura_test::init();
let pre_blossom_blocks_per_day = blocks_per_day(NetworkUpgrade::Genesis);
let post_blossom_blocks_per_day = blocks_per_day(NetworkUpgrade::Blossom);
let network = regtest_with_blossom(
ESTIMATED_RELEASE_HEIGHT + (EOS_PANIC_AFTER + 1) * pre_blossom_blocks_per_day,
);
assert_eq!(
estimated_height_after_release(&network, EOS_PANIC_AFTER),
Height(ESTIMATED_RELEASE_HEIGHT + EOS_PANIC_AFTER * pre_blossom_blocks_per_day),
);
let blossom = ESTIMATED_RELEASE_HEIGHT + 7 * pre_blossom_blocks_per_day;
let network = regtest_with_blossom(blossom);
assert_eq!(
estimated_height_after_release(&network, EOS_PANIC_AFTER),
Height(blossom - 1 + 2 + (EOS_PANIC_AFTER - 7) * post_blossom_blocks_per_day),
);
let network = regtest_with_blossom(ESTIMATED_RELEASE_HEIGHT + 1);
assert_eq!(
estimated_height_after_release(&network, EOS_PANIC_AFTER),
Height(ESTIMATED_RELEASE_HEIGHT + EOS_PANIC_AFTER * post_blossom_blocks_per_day),
);
}
}