referrerpolicy=no-referrer-when-downgrade

polkadot_node_core_approval_voting/
ops.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Polkadot.
3
4// Polkadot is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Polkadot is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.
16
17//! Middleware interface that leverages low-level database operations
18//! to provide a clean API for processing block and candidate imports.
19
20use 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/// Information about a new candidate necessary to instantiate the requisite
37/// candidate and approval entries.
38#[derive(Clone)]
39pub struct NewCandidateInfo {
40	candidate: CandidateReceipt,
41	backing_group: GroupIndex,
42	our_assignment: Option<OurAssignment>,
43}
44
45impl NewCandidateInfo {
46	/// Convenience constructor
47	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, // Should not happen except for corrupt DB
73					Some(c) => c,
74				})
75			},
76		};
77
78		candidate.block_assignments.remove(&block_hash);
79	}
80
81	Ok(block_entry.children)
82}
83
84/// Canonicalize some particular block, pruning everything before it and
85/// pruning any competing branches at the same height.
86pub 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	// Storing all candidates in memory is potentially heavy, but should be fine
98	// as long as finality doesn't stall for a long while. We could optimize this
99	// by keeping only the metadata about which blocks reference each candidate.
100	let mut visited_candidates = HashMap::new();
101
102	// All the block heights we visited but didn't necessarily delete everything from.
103	let mut visited_heights = HashMap::new();
104
105	// First visit everything before the height.
106	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	// Then visit everything at the height.
116	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		// Note that while there may be branches descending from blocks at earlier heights,
121		// we have already covered them by removing everything at earlier heights.
122		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	// Follow all children of non-canonicalized blocks.
136	{
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			// extend the frontier of branches to include the given height.
144			frontier.extend(children.into_iter().map(|h| (height + 1, h)));
145
146			// visit the at-height key for this deleted block's height.
147			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	// Update all `CandidateEntry`s, deleting all those which now have empty `block_assignments`.
158	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	// Update all blocks-at-height keys, deleting all those which now have empty
167	// `block_assignments`.
168	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	// due to the fork pruning, this range actually might go too far above where our actual highest
177	// block is, if a relatively short fork is canonicalized.
178	// TODO https://github.com/paritytech/polkadot/issues/3389
179	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
186/// Record a new block entry.
187///
188/// This will update the blocks-at-height mapping, the stored block range, if necessary,
189/// and add block and candidate entries. It will also add approval entries to existing
190/// candidate entries and add this as a child of any block entry corresponding to the
191/// parent hash.
192///
193/// Has no effect if there is already an entry for the block or `candidate_info` returns
194/// `None` for any of the candidates referenced by the block entry. In these cases,
195/// no information about new candidates will be referred to by this function.
196pub 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	// Update the stored block range.
207	{
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	// Update the blocks at height meta key.
218	{
219		let mut blocks_at_height = store.load_blocks_at_height(&number)?;
220		if blocks_at_height.contains(&entry.block_hash()) {
221			// seems we already have a block entry for this block. nothing to do here.
222			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	// read and write all updated entries.
232	{
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	// Update the child index for the parent.
269	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	// Put the new block entry in.
275	store.write_block_entry(entry);
276
277	Ok(candidate_entries)
278}
279
280/// Forcibly approve all candidates included at up to the given relay-chain height in the indicated
281/// chain.
282pub 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	// iterate back to the `up_to` block, and then iterate backwards until all blocks
299	// are updated.
300	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
341/// Revert to the block corresponding to the specified `hash`.
342/// The operation is not allowed for blocks older than the last finalized one.
343pub 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			// Write revert point block entry without the children.
356			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			// The parent is expected to be the revert point
372			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		// Check if we need to update the range top
390		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			// Cleanup the candidate entries by removing any reference to the
400			// removed block. If for a candidate entry the block block_assignments
401			// drops to zero then we remove the entry.
402			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	// Check if our modifications to the dag has reduced the range top
418	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}