zebra_state/service/finalized_state/disk_format/upgrade/
prune_trees.rs1use crossbeam_channel::{Receiver, TryRecvError};
4
5use semver::Version;
6use zebra_chain::block::Height;
7
8use crate::service::finalized_state::{DiskWriteBatch, ZebraDb};
9
10use super::{CancelFormatChange, DiskFormatUpgrade};
11
12pub struct PruneTrees;
14
15impl DiskFormatUpgrade for PruneTrees {
16 fn version(&self) -> Version {
17 Version::new(25, 1, 1)
18 }
19
20 fn description(&self) -> &'static str {
21 "deduplicate trees upgrade"
22 }
23
24 #[allow(clippy::unwrap_in_result)]
25 fn run(
26 &self,
27 initial_tip_height: Height,
28 db: &ZebraDb,
29 cancel_receiver: &Receiver<CancelFormatChange>,
30 ) -> Result<(), CancelFormatChange> {
31 let mut last_tree = db
35 .sapling_tree_by_height(&Height(0))
36 .expect("Checked above that the genesis block is in the database.");
37
38 for (height, tree) in db.sapling_tree_by_height_range(Height(1)..=initial_tip_height) {
41 if !matches!(cancel_receiver.try_recv(), Err(TryRecvError::Empty)) {
43 return Err(CancelFormatChange);
44 }
45
46 if tree == last_tree {
48 let mut batch = DiskWriteBatch::new();
49 batch.delete_sapling_tree(db, &height);
50 db.write_batch(batch)
51 .expect("Deleting Sapling note commitment trees should always succeed.");
52 }
53
54 last_tree = tree;
56 }
57
58 let mut last_tree = db
62 .orchard_tree_by_height(&Height(0))
63 .expect("Checked above that the genesis block is in the database.");
64
65 for (height, tree) in db.orchard_tree_by_height_range(Height(1)..=initial_tip_height) {
68 if !matches!(cancel_receiver.try_recv(), Err(TryRecvError::Empty)) {
70 return Err(CancelFormatChange);
71 }
72
73 if tree == last_tree {
75 let mut batch = DiskWriteBatch::new();
76 batch.delete_orchard_tree(db, &height);
77 db.write_batch(batch)
78 .expect("Deleting Orchard note commitment trees should always succeed.");
79 }
80
81 last_tree = tree;
83 }
84
85 Ok(())
86 }
87
88 #[allow(clippy::unwrap_in_result)]
90 fn validate(
91 &self,
92 db: &ZebraDb,
93 cancel_receiver: &Receiver<CancelFormatChange>,
94 ) -> Result<Result<(), String>, CancelFormatChange> {
95 let mut result = Ok(());
98
99 let mut prev_height = None;
100 let mut prev_tree = None;
101 for (height, tree) in db.sapling_tree_by_height_range(..) {
102 if !matches!(cancel_receiver.try_recv(), Err(TryRecvError::Empty)) {
104 return Err(CancelFormatChange);
105 }
106
107 if prev_tree == Some(tree.clone()) {
108 result = Err(format!(
109 "found duplicate sapling trees after running de-duplicate tree upgrade:\
110 height: {height:?}, previous height: {:?}, tree root: {:?}",
111 prev_height.unwrap(),
112 tree.root()
113 ));
114 error!(?result);
115 }
116
117 prev_height = Some(height);
118 prev_tree = Some(tree);
119 }
120
121 let mut prev_height = None;
122 let mut prev_tree = None;
123 for (height, tree) in db.orchard_tree_by_height_range(..) {
124 if !matches!(cancel_receiver.try_recv(), Err(TryRecvError::Empty)) {
126 return Err(CancelFormatChange);
127 }
128
129 if prev_tree == Some(tree.clone()) {
130 result = Err(format!(
131 "found duplicate orchard trees after running de-duplicate tree upgrade:\
132 height: {height:?}, previous height: {:?}, tree root: {:?}",
133 prev_height.unwrap(),
134 tree.root()
135 ));
136 error!(?result);
137 }
138
139 prev_height = Some(height);
140 prev_tree = Some(tree);
141 }
142
143 Ok(result)
144 }
145}