kaspa_consensus/processes/
pruning.rs1use std::{collections::VecDeque, sync::Arc};
2
3use super::reachability::ReachabilityResultExtensions;
4use crate::model::{
5 services::reachability::{MTReachabilityService, ReachabilityService},
6 stores::{
7 ghostdag::{CompactGhostdagData, GhostdagStoreReader},
8 headers::HeaderStoreReader,
9 headers_selected_tip::HeadersSelectedTipStoreReader,
10 past_pruning_points::PastPruningPointsStoreReader,
11 pruning::PruningPointInfo,
12 reachability::ReachabilityStoreReader,
13 },
14};
15use kaspa_hashes::Hash;
16use kaspa_utils::option::OptionExtensions;
17use parking_lot::RwLock;
18
19#[derive(Clone)]
20pub struct PruningPointManager<
21 S: GhostdagStoreReader,
22 T: ReachabilityStoreReader,
23 U: HeaderStoreReader,
24 V: PastPruningPointsStoreReader,
25 W: HeadersSelectedTipStoreReader,
26> {
27 pruning_depth: u64,
28 finality_depth: u64,
29 genesis_hash: Hash,
30
31 reachability_service: MTReachabilityService<T>,
32 ghostdag_store: Arc<S>,
33 headers_store: Arc<U>,
34 past_pruning_points_store: Arc<V>,
35 header_selected_tip_store: Arc<RwLock<W>>,
36}
37
38impl<
39 S: GhostdagStoreReader,
40 T: ReachabilityStoreReader,
41 U: HeaderStoreReader,
42 V: PastPruningPointsStoreReader,
43 W: HeadersSelectedTipStoreReader,
44 > PruningPointManager<S, T, U, V, W>
45{
46 pub fn new(
47 pruning_depth: u64,
48 finality_depth: u64,
49 genesis_hash: Hash,
50 reachability_service: MTReachabilityService<T>,
51 ghostdag_store: Arc<S>,
52 headers_store: Arc<U>,
53 past_pruning_points_store: Arc<V>,
54 header_selected_tip_store: Arc<RwLock<W>>,
55 ) -> Self {
56 Self {
57 pruning_depth,
58 finality_depth,
59 genesis_hash,
60 reachability_service,
61 ghostdag_store,
62 headers_store,
63 past_pruning_points_store,
64 header_selected_tip_store,
65 }
66 }
67
68 pub fn next_pruning_points_and_candidate_by_ghostdag_data(
69 &self,
70 ghostdag_data: CompactGhostdagData,
71 suggested_low_hash: Option<Hash>,
72 current_candidate: Hash,
73 current_pruning_point: Hash,
74 ) -> (Vec<Hash>, Hash) {
75 let low_hash = match suggested_low_hash {
76 Some(suggested) => {
77 if !self.reachability_service.is_chain_ancestor_of(suggested, current_candidate) {
78 assert!(self.reachability_service.is_chain_ancestor_of(current_candidate, suggested));
79 suggested
80 } else {
81 current_candidate
82 }
83 }
84 None => current_candidate,
85 };
86
87 let mut new_pruning_points = Vec::with_capacity((self.pruning_depth / self.finality_depth) as usize);
89 let mut latest_pruning_point_bs = self.ghostdag_store.get_blue_score(current_pruning_point).unwrap();
90
91 if latest_pruning_point_bs + self.pruning_depth > ghostdag_data.blue_score {
92 return (vec![], current_candidate);
97 }
98
99 let mut new_candidate = current_candidate;
100
101 for selected_child in self.reachability_service.forward_chain_iterator(low_hash, ghostdag_data.selected_parent, true) {
102 let selected_child_bs = self.ghostdag_store.get_blue_score(selected_child).unwrap();
103
104 if ghostdag_data.blue_score - selected_child_bs < self.pruning_depth {
105 break;
106 }
107
108 new_candidate = selected_child;
109 let new_candidate_bs = selected_child_bs;
110
111 if self.finality_score(new_candidate_bs) > self.finality_score(latest_pruning_point_bs) {
112 new_pruning_points.push(new_candidate);
113 latest_pruning_point_bs = new_candidate_bs;
114 }
115 }
116
117 (new_pruning_points, new_candidate)
118 }
119
120 fn finality_score(&self, blue_score: u64) -> u64 {
123 blue_score / self.finality_depth
124 }
125
126 pub fn expected_header_pruning_point(&self, ghostdag_data: CompactGhostdagData, pruning_info: PruningPointInfo) -> Hash {
127 if ghostdag_data.selected_parent == self.genesis_hash {
128 return self.genesis_hash;
129 }
130
131 let (current_pruning_point, current_candidate, current_pruning_point_index) = pruning_info.decompose();
132
133 let sp_header_pp = self.headers_store.get_header(ghostdag_data.selected_parent).unwrap().pruning_point;
134 let sp_header_pp_blue_score = self.headers_store.get_blue_score(sp_header_pp).unwrap();
135
136 let has_pruning_point_in_its_selected_chain =
139 self.reachability_service.is_chain_ancestor_of(current_pruning_point, ghostdag_data.selected_parent);
140
141 let min_required_blue_score_for_next_pruning_point = (self.finality_score(sp_header_pp_blue_score) + 1) * self.finality_depth;
145 let next_or_current_pp = if has_pruning_point_in_its_selected_chain
146 && min_required_blue_score_for_next_pruning_point + self.pruning_depth <= ghostdag_data.blue_score
147 {
148 let suggested_low_hash = self
150 .reachability_service
151 .is_dag_ancestor_of_result(current_pruning_point, sp_header_pp)
152 .unwrap_option()
153 .and_then(|b| if b { Some(sp_header_pp) } else { None });
154 let (new_pruning_points, _) = self.next_pruning_points_and_candidate_by_ghostdag_data(
155 ghostdag_data,
156 suggested_low_hash,
157 current_candidate,
158 current_pruning_point,
159 );
160
161 new_pruning_points.last().copied().unwrap_or(current_pruning_point)
162 } else {
163 sp_header_pp
164 };
165
166 if self.is_pruning_point_in_pruning_depth(ghostdag_data.blue_score, next_or_current_pp) {
167 return next_or_current_pp;
168 }
169
170 for i in (0..=current_pruning_point_index).rev() {
171 let past_pp = self.past_pruning_points_store.get(i).unwrap();
172 if self.is_pruning_point_in_pruning_depth(ghostdag_data.blue_score, past_pp) {
173 return past_pp;
174 }
175 }
176
177 self.genesis_hash
178 }
179
180 fn is_pruning_point_in_pruning_depth(&self, pov_blue_score: u64, pruning_point: Hash) -> bool {
181 let pp_bs = self.headers_store.get_blue_score(pruning_point).unwrap();
182 pov_blue_score >= pp_bs + self.pruning_depth
183 }
184
185 pub fn is_valid_pruning_point(&self, pp_candidate: Hash, hst: Hash) -> bool {
186 if pp_candidate == self.genesis_hash {
187 return true;
188 }
189 if !self.reachability_service.is_chain_ancestor_of(pp_candidate, hst) {
190 return false;
191 }
192
193 let hst_bs = self.ghostdag_store.get_blue_score(hst).unwrap();
194 self.is_pruning_point_in_pruning_depth(hst_bs, pp_candidate)
195 }
196
197 pub fn are_pruning_points_in_valid_chain(&self, pruning_info: PruningPointInfo, hst: Hash) -> bool {
198 let mut expected_pps_queue = VecDeque::new();
214 for current in self.reachability_service.backward_chain_iterator(hst, pruning_info.pruning_point, false) {
215 let current_header = self.headers_store.get_header(current).unwrap();
216 if expected_pps_queue.back().is_none_or_ex(|&&h| h != current_header.pruning_point) {
217 expected_pps_queue.push_back(current_header.pruning_point);
218 }
219 }
220
221 for idx in (0..=pruning_info.index).rev() {
222 let pp = self.past_pruning_points_store.get(idx).unwrap();
223 let pp_header = self.headers_store.get_header(pp).unwrap();
224 let Some(expected_pp) = expected_pps_queue.pop_front() else {
225 return false;
227 };
228
229 if expected_pp != pp {
230 return false;
231 }
232
233 if idx == 0 {
234 if !expected_pps_queue.is_empty() || pp != self.genesis_hash {
237 return false;
238 }
239 break;
240 }
241
242 match expected_pps_queue.back() {
245 Some(last_added_pp) => {
246 if *last_added_pp != pp_header.pruning_point {
247 expected_pps_queue.push_back(pp_header.pruning_point);
248 }
249 }
250 None => {
251 return false;
254 }
255 }
256 }
257
258 true
259 }
260}
261
262#[cfg(test)]
263mod tests {
264 }