use crossbeam_channel::{Receiver, TryRecvError};
use semver::Version;
use zebra_chain::block::Height;
use crate::service::finalized_state::{DiskWriteBatch, ZebraDb};
use super::{CancelFormatChange, DiskFormatUpgrade};
pub struct PruneTrees;
impl DiskFormatUpgrade for PruneTrees {
fn version(&self) -> Version {
Version::new(25, 1, 1)
}
fn description(&self) -> &'static str {
"deduplicate trees upgrade"
}
#[allow(clippy::unwrap_in_result)]
fn run(
&self,
initial_tip_height: Height,
db: &ZebraDb,
cancel_receiver: &Receiver<CancelFormatChange>,
) -> Result<(), CancelFormatChange> {
let mut last_tree = db
.sapling_tree_by_height(&Height(0))
.expect("Checked above that the genesis block is in the database.");
for (height, tree) in db.sapling_tree_by_height_range(Height(1)..=initial_tip_height) {
if !matches!(cancel_receiver.try_recv(), Err(TryRecvError::Empty)) {
return Err(CancelFormatChange);
}
if tree == last_tree {
let mut batch = DiskWriteBatch::new();
batch.delete_sapling_tree(db, &height);
db.write_batch(batch)
.expect("Deleting Sapling note commitment trees should always succeed.");
}
last_tree = tree;
}
let mut last_tree = db
.orchard_tree_by_height(&Height(0))
.expect("Checked above that the genesis block is in the database.");
for (height, tree) in db.orchard_tree_by_height_range(Height(1)..=initial_tip_height) {
if !matches!(cancel_receiver.try_recv(), Err(TryRecvError::Empty)) {
return Err(CancelFormatChange);
}
if tree == last_tree {
let mut batch = DiskWriteBatch::new();
batch.delete_orchard_tree(db, &height);
db.write_batch(batch)
.expect("Deleting Orchard note commitment trees should always succeed.");
}
last_tree = tree;
}
Ok(())
}
#[allow(clippy::unwrap_in_result)]
fn validate(
&self,
db: &ZebraDb,
cancel_receiver: &Receiver<CancelFormatChange>,
) -> Result<Result<(), String>, CancelFormatChange> {
let mut result = Ok(());
let mut prev_height = None;
let mut prev_tree = None;
for (height, tree) in db.sapling_tree_by_height_range(..) {
if !matches!(cancel_receiver.try_recv(), Err(TryRecvError::Empty)) {
return Err(CancelFormatChange);
}
if prev_tree == Some(tree.clone()) {
result = Err(format!(
"found duplicate sapling trees after running de-duplicate tree upgrade:\
height: {height:?}, previous height: {:?}, tree root: {:?}",
prev_height.unwrap(),
tree.root()
));
error!(?result);
}
prev_height = Some(height);
prev_tree = Some(tree);
}
let mut prev_height = None;
let mut prev_tree = None;
for (height, tree) in db.orchard_tree_by_height_range(..) {
if !matches!(cancel_receiver.try_recv(), Err(TryRecvError::Empty)) {
return Err(CancelFormatChange);
}
if prev_tree == Some(tree.clone()) {
result = Err(format!(
"found duplicate orchard trees after running de-duplicate tree upgrade:\
height: {height:?}, previous height: {:?}, tree root: {:?}",
prev_height.unwrap(),
tree.root()
));
error!(?result);
}
prev_height = Some(height);
prev_tree = Some(tree);
}
Ok(result)
}
}