polkadot_node_core_approval_voting/
ops.rs1use polkadot_node_subsystem::{SubsystemError, SubsystemResult};
21
22use bitvec::order::Lsb0 as BitOrderLsb0;
23use polkadot_primitives::{
24 BlockNumber, CandidateHash, CandidateReceiptV2 as CandidateReceipt, GroupIndex, Hash,
25};
26
27use std::collections::{hash_map::Entry, BTreeMap, HashMap};
28
29use super::{
30 approval_db::{common::StoredBlockRange, v2::OurAssignment},
31 backend::{Backend, OverlayedBackend},
32 persisted_entries::{ApprovalEntry, BlockEntry, CandidateEntry},
33 LOG_TARGET,
34};
35
36#[derive(Clone)]
39pub struct NewCandidateInfo {
40 candidate: CandidateReceipt,
41 backing_group: GroupIndex,
42 our_assignment: Option<OurAssignment>,
43}
44
45impl NewCandidateInfo {
46 pub fn new(
48 candidate: CandidateReceipt,
49 backing_group: GroupIndex,
50 our_assignment: Option<OurAssignment>,
51 ) -> Self {
52 Self { candidate, backing_group, our_assignment }
53 }
54}
55
56fn visit_and_remove_block_entry(
57 block_hash: Hash,
58 overlayed_db: &mut OverlayedBackend<'_, impl Backend>,
59 visited_candidates: &mut HashMap<CandidateHash, CandidateEntry>,
60) -> SubsystemResult<Vec<Hash>> {
61 let block_entry = match overlayed_db.load_block_entry(&block_hash)? {
62 None => return Ok(Vec::new()),
63 Some(b) => b,
64 };
65
66 overlayed_db.delete_block_entry(&block_hash);
67 for (_, candidate_hash) in block_entry.candidates() {
68 let candidate = match visited_candidates.entry(*candidate_hash) {
69 Entry::Occupied(e) => e.into_mut(),
70 Entry::Vacant(e) => {
71 e.insert(match overlayed_db.load_candidate_entry(candidate_hash)? {
72 None => continue, Some(c) => c,
74 })
75 },
76 };
77
78 candidate.block_assignments.remove(&block_hash);
79 }
80
81 Ok(block_entry.children)
82}
83
84pub fn canonicalize(
87 overlay_db: &mut OverlayedBackend<'_, impl Backend>,
88 canon_number: BlockNumber,
89 canon_hash: Hash,
90) -> SubsystemResult<()> {
91 let range = match overlay_db.load_stored_blocks()? {
92 None => return Ok(()),
93 Some(range) if range.0 > canon_number => return Ok(()),
94 Some(range) => range,
95 };
96
97 let mut visited_candidates = HashMap::new();
101
102 let mut visited_heights = HashMap::new();
104
105 for i in range.0..canon_number {
107 let at_height = overlay_db.load_blocks_at_height(&i)?;
108 overlay_db.delete_blocks_at_height(i);
109
110 for b in at_height {
111 visit_and_remove_block_entry(b, overlay_db, &mut visited_candidates)?;
112 }
113 }
114
115 let pruned_branches = {
117 let at_height = overlay_db.load_blocks_at_height(&canon_number)?;
118 overlay_db.delete_blocks_at_height(canon_number);
119
120 let mut pruned_branches = Vec::new();
123
124 for b in at_height {
125 let children = visit_and_remove_block_entry(b, overlay_db, &mut visited_candidates)?;
126
127 if b != canon_hash {
128 pruned_branches.extend(children);
129 }
130 }
131
132 pruned_branches
133 };
134
135 {
137 let mut frontier: Vec<(BlockNumber, Hash)> =
138 pruned_branches.into_iter().map(|h| (canon_number + 1, h)).collect();
139 while let Some((height, next_child)) = frontier.pop() {
140 let children =
141 visit_and_remove_block_entry(next_child, overlay_db, &mut visited_candidates)?;
142
143 frontier.extend(children.into_iter().map(|h| (height + 1, h)));
145
146 let at_height = match visited_heights.entry(height) {
148 Entry::Occupied(e) => e.into_mut(),
149 Entry::Vacant(e) => e.insert(overlay_db.load_blocks_at_height(&height)?),
150 };
151 if let Some(i) = at_height.iter().position(|x| x == &next_child) {
152 at_height.remove(i);
153 }
154 }
155 }
156
157 for (candidate_hash, candidate) in visited_candidates.into_iter() {
159 if candidate.block_assignments.is_empty() {
160 overlay_db.delete_candidate_entry(&candidate_hash);
161 } else {
162 overlay_db.write_candidate_entry(candidate);
163 }
164 }
165
166 for (h, at) in visited_heights.into_iter() {
169 if at.is_empty() {
170 overlay_db.delete_blocks_at_height(h);
171 } else {
172 overlay_db.write_blocks_at_height(h, at);
173 }
174 }
175
176 let new_range = StoredBlockRange(canon_number + 1, std::cmp::max(range.1, canon_number + 2));
180
181 overlay_db.write_stored_block_range(new_range);
182
183 Ok(())
184}
185
186pub fn add_block_entry(
197 store: &mut OverlayedBackend<'_, impl Backend>,
198 entry: BlockEntry,
199 n_validators: usize,
200 candidate_info: impl Fn(&CandidateHash) -> Option<NewCandidateInfo>,
201) -> SubsystemResult<Vec<(CandidateHash, CandidateEntry)>> {
202 let session = entry.session();
203 let parent_hash = entry.parent_hash();
204 let number = entry.block_number();
205
206 {
208 let new_range = match store.load_stored_blocks()? {
209 None => Some(StoredBlockRange(number, number + 1)),
210 Some(range) if range.1 <= number => Some(StoredBlockRange(range.0, number + 1)),
211 Some(_) => None,
212 };
213
214 new_range.map(|n| store.write_stored_block_range(n));
215 };
216
217 {
219 let mut blocks_at_height = store.load_blocks_at_height(&number)?;
220 if blocks_at_height.contains(&entry.block_hash()) {
221 return Ok(Vec::new());
223 }
224
225 blocks_at_height.push(entry.block_hash());
226 store.write_blocks_at_height(number, blocks_at_height)
227 };
228
229 let mut candidate_entries = Vec::with_capacity(entry.candidates().len());
230
231 {
233 for (_, candidate_hash) in entry.candidates() {
234 let NewCandidateInfo { candidate, backing_group, our_assignment } =
235 match candidate_info(candidate_hash) {
236 None => return Ok(Vec::new()),
237 Some(info) => info,
238 };
239
240 let mut candidate_entry =
241 store.load_candidate_entry(&candidate_hash)?.unwrap_or_else(move || {
242 CandidateEntry {
243 candidate,
244 session,
245 block_assignments: BTreeMap::new(),
246 approvals: bitvec::bitvec![u8, BitOrderLsb0; 0; n_validators],
247 }
248 });
249
250 candidate_entry.block_assignments.insert(
251 entry.block_hash(),
252 ApprovalEntry::new(
253 Vec::new(),
254 backing_group,
255 our_assignment.map(|v| v.into()),
256 None,
257 bitvec::bitvec![u8, BitOrderLsb0; 0; n_validators],
258 false,
259 ),
260 );
261
262 store.write_candidate_entry(candidate_entry.clone());
263
264 candidate_entries.push((*candidate_hash, candidate_entry));
265 }
266 };
267
268 store.load_block_entry(&parent_hash)?.map(|mut e| {
270 e.children.push(entry.block_hash());
271 store.write_block_entry(e);
272 });
273
274 store.write_block_entry(entry);
276
277 Ok(candidate_entries)
278}
279
280pub fn force_approve(
283 store: &mut OverlayedBackend<'_, impl Backend>,
284 chain_head: Hash,
285 up_to: BlockNumber,
286) -> SubsystemResult<Vec<Hash>> {
287 #[derive(PartialEq, Eq)]
288 enum State {
289 WalkTo,
290 Approving,
291 }
292 let mut approved_hashes = Vec::new();
293
294 let mut cur_hash = chain_head;
295 let mut state = State::WalkTo;
296 let mut cur_block_number: BlockNumber = 0;
297
298 while let Some(mut entry) = store.load_block_entry(&cur_hash)? {
301 cur_block_number = entry.block_number();
302 if cur_block_number <= up_to {
303 if state == State::WalkTo {
304 gum::debug!(
305 target: LOG_TARGET,
306 block_hash = ?chain_head,
307 ?cur_hash,
308 ?cur_block_number,
309 "Start forced approval from block",
310 );
311 }
312 state = State::Approving;
313 }
314
315 cur_hash = entry.parent_hash();
316
317 match state {
318 State::WalkTo => {},
319 State::Approving => {
320 entry.approved_bitfield.iter_mut().for_each(|mut b| *b = true);
321 approved_hashes.push(entry.block_hash());
322 store.write_block_entry(entry);
323 },
324 }
325 }
326
327 if state == State::WalkTo {
328 gum::warn!(
329 target: LOG_TARGET,
330 ?chain_head,
331 ?cur_hash,
332 ?cur_block_number,
333 ?up_to,
334 "Missing block in the chain, cannot start force approval"
335 );
336 }
337
338 Ok(approved_hashes)
339}
340
341pub fn revert_to(
344 overlay: &mut OverlayedBackend<'_, impl Backend>,
345 hash: Hash,
346) -> SubsystemResult<()> {
347 let mut stored_range = overlay.load_stored_blocks()?.ok_or_else(|| {
348 SubsystemError::Context("no available blocks to infer revert point height".to_string())
349 })?;
350
351 let (children, children_height) = match overlay.load_block_entry(&hash)? {
352 Some(mut entry) => {
353 let children_height = entry.block_number() + 1;
354 let children = std::mem::take(&mut entry.children);
355 overlay.write_block_entry(entry);
357 (children, children_height)
358 },
359 None => {
360 let children_height = stored_range.0;
361 let children = overlay.load_blocks_at_height(&children_height)?;
362
363 let child_entry = children
364 .first()
365 .and_then(|hash| overlay.load_block_entry(hash).ok())
366 .flatten()
367 .ok_or_else(|| {
368 SubsystemError::Context("lookup failure for first block".to_string())
369 })?;
370
371 if child_entry.parent_hash() != hash {
373 return Err(SubsystemError::Context(
374 "revert below last finalized block or corrupted storage".to_string(),
375 ));
376 }
377
378 (children, children_height)
379 },
380 };
381
382 let mut stack: Vec<_> = children.into_iter().map(|h| (h, children_height)).collect();
383 let mut range_end = stored_range.1;
384
385 while let Some((hash, number)) = stack.pop() {
386 let mut blocks_at_height = overlay.load_blocks_at_height(&number)?;
387 blocks_at_height.retain(|h| h != &hash);
388
389 if blocks_at_height.is_empty() && number < range_end {
391 range_end = number;
392 }
393
394 overlay.write_blocks_at_height(number, blocks_at_height);
395
396 if let Some(entry) = overlay.load_block_entry(&hash)? {
397 overlay.delete_block_entry(&hash);
398
399 for (_, candidate_hash) in entry.candidates() {
403 if let Some(mut candidate_entry) = overlay.load_candidate_entry(candidate_hash)? {
404 candidate_entry.block_assignments.remove(&hash);
405 if candidate_entry.block_assignments.is_empty() {
406 overlay.delete_candidate_entry(candidate_hash);
407 } else {
408 overlay.write_candidate_entry(candidate_entry);
409 }
410 }
411 }
412
413 stack.extend(entry.children.into_iter().map(|h| (h, number + 1)));
414 }
415 }
416
417 if range_end != stored_range.1 {
419 if stored_range.0 < range_end {
420 stored_range.1 = range_end;
421 overlay.write_stored_block_range(stored_range);
422 } else {
423 overlay.delete_stored_block_range();
424 }
425 }
426
427 Ok(())
428}