1use polkadot_node_primitives::BlockWeight;
27use polkadot_node_subsystem::ChainApiError;
28use polkadot_primitives::{BlockNumber, Hash};
29
30use std::collections::HashMap;
31
32use super::{Approval, BlockEntry, Error, LeafEntry, Timestamp, ViabilityCriteria, LOG_TARGET};
33use crate::backend::{Backend, OverlayedBackend};
34
35struct ViabilityUpdate(Option<Hash>);
37
38impl ViabilityUpdate {
39 fn apply(self, mut entry: BlockEntry) -> (BlockEntry, Vec<(Hash, ViabilityUpdate)>) {
43 let maybe_earliest_unviable = self.0;
68 let next_earliest_unviable = {
69 if maybe_earliest_unviable.is_none() && !entry.viability.is_explicitly_viable() {
70 Some(entry.block_hash)
71 } else {
72 maybe_earliest_unviable
73 }
74 };
75 entry.viability.earliest_unviable_ancestor = maybe_earliest_unviable;
76
77 let recurse = entry
78 .children
79 .iter()
80 .cloned()
81 .map(move |c| (c, ViabilityUpdate(next_earliest_unviable)))
82 .collect();
83
84 (entry, recurse)
85 }
86}
87
88fn propagate_viability_update(
98 backend: &mut OverlayedBackend<impl Backend>,
99 base: BlockEntry,
100) -> Result<(), Error> {
101 enum BlockEntryRef {
102 Explicit(BlockEntry),
103 Hash(Hash),
104 }
105
106 if !base.viability.is_parent_viable() {
107 backend.write_block_entry(base);
114 return Ok(())
115 }
116
117 let mut viable_leaves = backend.load_leaves()?;
118
119 let mut viability_pivots = HashMap::new();
126
127 let viability_update = ViabilityUpdate(None);
131
132 let mut tree_frontier = vec![(BlockEntryRef::Explicit(base), viability_update)];
139 while let Some((entry_ref, update)) = tree_frontier.pop() {
140 let entry = match entry_ref {
141 BlockEntryRef::Explicit(entry) => entry,
142 BlockEntryRef::Hash(hash) => match backend.load_block_entry(&hash)? {
143 None => {
144 gum::warn!(
145 target: LOG_TARGET,
146 block_hash = ?hash,
147 "Missing expected block entry"
148 );
149
150 continue
151 },
152 Some(entry) => entry,
153 },
154 };
155
156 let (new_entry, children) = update.apply(entry);
157
158 if new_entry.viability.is_viable() {
159 viable_leaves.remove(&new_entry.parent_hash);
162
163 if new_entry.children.is_empty() {
166 viable_leaves.insert(new_entry.leaf_entry());
167 }
168 } else {
169 viable_leaves.remove(&new_entry.block_hash);
171
172 if new_entry.viability.is_parent_viable() {
176 *viability_pivots.entry(new_entry.parent_hash).or_insert(0) += 1;
177 }
178 }
179
180 backend.write_block_entry(new_entry);
181
182 tree_frontier
183 .extend(children.into_iter().map(|(h, update)| (BlockEntryRef::Hash(h), update)));
184 }
185
186 for (pivot, pivot_count) in viability_pivots {
212 match backend.load_block_entry(&pivot)? {
213 None => {
214 continue
226 },
227 Some(entry) =>
228 if entry.children.len() == pivot_count {
229 viable_leaves.insert(entry.leaf_entry());
230 },
231 }
232 }
233
234 backend.write_leaves(viable_leaves);
235
236 Ok(())
237}
238
239pub(crate) fn import_block(
241 backend: &mut OverlayedBackend<impl Backend>,
242 block_hash: Hash,
243 block_number: BlockNumber,
244 parent_hash: Hash,
245 reversion_logs: Vec<BlockNumber>,
246 weight: BlockWeight,
247 stagnant_at: Timestamp,
248) -> Result<(), Error> {
249 let block_entry =
250 add_block(backend, block_hash, block_number, parent_hash, weight, stagnant_at)?;
251 apply_reversions(backend, block_entry, reversion_logs)?;
252
253 Ok(())
254}
255
256fn load_ancestor(
263 backend: &mut OverlayedBackend<impl Backend>,
264 block_entry: &BlockEntry,
265 ancestor_number: BlockNumber,
266) -> Result<Option<BlockEntry>, Error> {
267 let block_hash = block_entry.block_hash;
268 let block_number = block_entry.block_number;
269 if block_number == ancestor_number {
270 return Ok(Some(block_entry.clone()))
271 } else if block_number < ancestor_number {
272 return Ok(None)
273 }
274
275 let mut current_hash = block_hash;
276 let mut current_entry = None;
277
278 let segment_length = (block_number - ancestor_number) + 1;
279 for _ in 0..segment_length {
280 match backend.load_block_entry(¤t_hash)? {
281 None => return Ok(None),
282 Some(entry) => {
283 let parent_hash = entry.parent_hash;
284 current_entry = Some(entry);
285 current_hash = parent_hash;
286 },
287 }
288 }
289
290 Ok(current_entry)
292}
293
294fn add_block(
301 backend: &mut OverlayedBackend<impl Backend>,
302 block_hash: Hash,
303 block_number: BlockNumber,
304 parent_hash: Hash,
305 weight: BlockWeight,
306 stagnant_at: Timestamp,
307) -> Result<BlockEntry, Error> {
308 let mut leaves = backend.load_leaves()?;
309 let parent_entry = backend.load_block_entry(&parent_hash)?;
310
311 let inherited_viability =
312 parent_entry.as_ref().and_then(|parent| parent.non_viable_ancestor_for_child());
313
314 let block_entry = BlockEntry {
316 block_hash,
317 block_number,
318 parent_hash,
319 children: Vec::new(),
320 viability: ViabilityCriteria {
321 earliest_unviable_ancestor: inherited_viability,
322 explicitly_reverted: false,
323 approval: Approval::Unapproved,
324 },
325 weight,
326 };
327 backend.write_block_entry(block_entry.clone());
328
329 if inherited_viability.is_none() {
331 leaves.remove(&parent_hash);
332 leaves.insert(LeafEntry { block_hash, block_number, weight });
333 backend.write_leaves(leaves);
334 }
335
336 if let Some(mut parent_entry) = parent_entry {
338 parent_entry.children.push(block_hash);
339 backend.write_block_entry(parent_entry);
340 }
341
342 let mut blocks_by_number = backend.load_blocks_by_number(block_number)?;
344 blocks_by_number.push(block_hash);
345 backend.write_blocks_by_number(block_number, blocks_by_number);
346
347 let mut stagnant_at_list = backend.load_stagnant_at(stagnant_at)?;
349 stagnant_at_list.push(block_hash);
350 backend.write_stagnant_at(stagnant_at, stagnant_at_list);
351
352 Ok(block_entry)
353}
354
355fn apply_reversions(
358 backend: &mut OverlayedBackend<impl Backend>,
359 block_entry: BlockEntry,
360 reversions: Vec<BlockNumber>,
361) -> Result<(), Error> {
362 for revert_number in reversions {
365 let maybe_block_entry = load_ancestor(backend, &block_entry, revert_number)?;
366 if let Some(entry) = &maybe_block_entry {
367 gum::trace!(
368 target: LOG_TARGET,
369 ?revert_number,
370 revert_hash = ?entry.block_hash,
371 "Block marked as reverted via scraped on-chain reversions"
372 );
373 }
374 revert_single_block_entry_if_present(
375 backend,
376 maybe_block_entry,
377 None,
378 revert_number,
379 Some(block_entry.block_hash),
380 Some(block_entry.block_number),
381 )?;
382 }
383
384 Ok(())
385}
386
387pub(crate) fn apply_single_reversion(
391 backend: &mut OverlayedBackend<impl Backend>,
392 revert_hash: Hash,
393 revert_number: BlockNumber,
394) -> Result<(), Error> {
395 gum::trace!(
396 target: LOG_TARGET,
397 ?revert_number,
398 ?revert_hash,
399 "Block marked as reverted via ChainSelectionMessage::RevertBlocks"
400 );
401 let maybe_block_entry = backend.load_block_entry(&revert_hash)?;
402 revert_single_block_entry_if_present(
403 backend,
404 maybe_block_entry,
405 Some(revert_hash),
406 revert_number,
407 None,
408 None,
409 )?;
410 Ok(())
411}
412
413fn revert_single_block_entry_if_present(
414 backend: &mut OverlayedBackend<impl Backend>,
415 maybe_block_entry: Option<BlockEntry>,
416 maybe_revert_hash: Option<Hash>,
417 revert_number: BlockNumber,
418 maybe_reporting_hash: Option<Hash>,
419 maybe_reporting_number: Option<BlockNumber>,
420) -> Result<(), Error> {
421 match maybe_block_entry {
422 None => {
423 gum::warn!(
424 target: LOG_TARGET,
425 ?maybe_revert_hash,
426 revert_target = revert_number,
427 ?maybe_reporting_hash,
428 ?maybe_reporting_number,
429 "The hammer has dropped. \
430 The protocol has indicated that a finalized block be reverted. \
431 Please inform an adult.",
432 );
433 },
434 Some(mut block_entry) => {
435 gum::info!(
436 target: LOG_TARGET,
437 ?maybe_revert_hash,
438 revert_target = revert_number,
439 ?maybe_reporting_hash,
440 ?maybe_reporting_number,
441 "Unfinalized block reverted due to a bad parachain block.",
442 );
443
444 block_entry.viability.explicitly_reverted = true;
445 propagate_viability_update(backend, block_entry)?;
447 },
448 }
449 Ok(())
450}
451
452pub(super) fn finalize_block<'a, B: Backend + 'a>(
462 backend: &'a B,
463 finalized_hash: Hash,
464 finalized_number: BlockNumber,
465) -> Result<OverlayedBackend<'a, B>, Error> {
466 let earliest_stored_number = backend.load_first_block_number()?;
467 let mut backend = OverlayedBackend::new(backend);
468
469 let earliest_stored_number = match earliest_stored_number {
470 None => {
471 return Ok(backend)
474 },
475 Some(e) => e,
476 };
477
478 let mut viable_leaves = backend.load_leaves()?;
479
480 for number in earliest_stored_number..finalized_number {
482 let blocks_at = backend.load_blocks_by_number(number)?;
483 backend.delete_blocks_by_number(number);
484
485 for block in blocks_at {
486 viable_leaves.remove(&block);
487 backend.delete_block_entry(&block);
488 }
489 }
490
491 {
494 let blocks_at_finalized_height = backend.load_blocks_by_number(finalized_number)?;
495 backend.delete_blocks_by_number(finalized_number);
496
497 let mut frontier: Vec<_> = blocks_at_finalized_height
498 .into_iter()
499 .filter(|h| h != &finalized_hash)
500 .map(|h| (h, finalized_number))
501 .collect();
502
503 while let Some((dead_hash, dead_number)) = frontier.pop() {
504 let entry = backend.load_block_entry(&dead_hash)?;
505 backend.delete_block_entry(&dead_hash);
506 viable_leaves.remove(&dead_hash);
507
508 let mut blocks_at_height = backend.load_blocks_by_number(dead_number)?;
511 blocks_at_height.retain(|h| h != &dead_hash);
512 backend.write_blocks_by_number(dead_number, blocks_at_height);
513
514 let next_height = dead_number + 1;
516 frontier.extend(entry.into_iter().flat_map(|e| e.children).map(|h| (h, next_height)));
517 }
518 }
519
520 let children_of_finalized = {
522 let finalized_entry = backend.load_block_entry(&finalized_hash)?;
523 backend.delete_block_entry(&finalized_hash);
524 viable_leaves.remove(&finalized_hash);
525
526 finalized_entry.into_iter().flat_map(|e| e.children)
527 };
528
529 backend.write_leaves(viable_leaves);
530
531 for child in children_of_finalized {
533 if let Some(mut child) = backend.load_block_entry(&child)? {
534 child.viability.earliest_unviable_ancestor = None;
536
537 propagate_viability_update(&mut backend, child)?;
538 } else {
539 gum::debug!(
540 target: LOG_TARGET,
541 ?finalized_hash,
542 finalized_number,
543 child_hash = ?child,
544 "Missing child of finalized block",
545 );
546
547 }
549 }
550
551 Ok(backend)
552}
553
554pub(super) fn approve_block(
557 backend: &mut OverlayedBackend<impl Backend>,
558 approved_hash: Hash,
559) -> Result<(), Error> {
560 if let Some(mut entry) = backend.load_block_entry(&approved_hash)? {
561 let was_viable = entry.viability.is_viable();
562 entry.viability.approval = Approval::Approved;
563 let is_viable = entry.viability.is_viable();
564
565 if !was_viable && is_viable {
569 propagate_viability_update(backend, entry)?;
570 } else {
571 backend.write_block_entry(entry);
572 }
573 } else {
574 gum::debug!(
575 target: LOG_TARGET,
576 block_hash = ?approved_hash,
577 "Missing entry for freshly-approved block. Ignoring"
578 );
579 }
580
581 Ok(())
582}
583
584pub(super) fn detect_stagnant<'a, B: 'a + Backend>(
590 backend: &'a B,
591 up_to: Timestamp,
592 max_elements: usize,
593) -> Result<OverlayedBackend<'a, B>, Error> {
594 let stagnant_up_to = backend.load_stagnant_at_up_to(up_to, max_elements)?;
595 let mut backend = OverlayedBackend::new(backend);
596
597 let (min_ts, max_ts) = match stagnant_up_to.len() {
598 0 => (0 as Timestamp, 0 as Timestamp),
599 1 => (stagnant_up_to[0].0, stagnant_up_to[0].0),
600 n => (stagnant_up_to[0].0, stagnant_up_to[n - 1].0),
601 };
602
603 gum::debug!(
606 target: LOG_TARGET,
607 ?up_to,
608 ?min_ts,
609 ?max_ts,
610 "Prepared {} stagnant entries for checking/pruning",
611 stagnant_up_to.len()
612 );
613
614 for (timestamp, maybe_stagnant) in stagnant_up_to {
615 backend.delete_stagnant_at(timestamp);
616
617 for block_hash in maybe_stagnant {
618 if let Some(mut entry) = backend.load_block_entry(&block_hash)? {
619 let was_viable = entry.viability.is_viable();
620 if let Approval::Unapproved = entry.viability.approval {
621 entry.viability.approval = Approval::Stagnant;
622 }
623 let is_viable = entry.viability.is_viable();
624 gum::trace!(
625 target: LOG_TARGET,
626 ?block_hash,
627 ?timestamp,
628 ?was_viable,
629 ?is_viable,
630 "Found existing stagnant entry"
631 );
632
633 if was_viable && !is_viable {
634 propagate_viability_update(&mut backend, entry)?;
635 } else {
636 backend.write_block_entry(entry);
637 }
638 } else {
639 gum::trace!(
640 target: LOG_TARGET,
641 ?block_hash,
642 ?timestamp,
643 "Found non-existing stagnant entry"
644 );
645 }
646 }
647 }
648
649 Ok(backend)
650}
651
652pub(super) fn prune_only_stagnant<'a, B: 'a + Backend>(
656 backend: &'a B,
657 up_to: Timestamp,
658 max_elements: usize,
659) -> Result<OverlayedBackend<'a, B>, Error> {
660 let stagnant_up_to = backend.load_stagnant_at_up_to(up_to, max_elements)?;
661 let mut backend = OverlayedBackend::new(backend);
662
663 let (min_ts, max_ts) = match stagnant_up_to.len() {
664 0 => (0 as Timestamp, 0 as Timestamp),
665 1 => (stagnant_up_to[0].0, stagnant_up_to[0].0),
666 n => (stagnant_up_to[0].0, stagnant_up_to[n - 1].0),
667 };
668
669 gum::debug!(
670 target: LOG_TARGET,
671 ?up_to,
672 ?min_ts,
673 ?max_ts,
674 "Prepared {} stagnant entries for pruning",
675 stagnant_up_to.len()
676 );
677
678 for (timestamp, _) in stagnant_up_to {
679 backend.delete_stagnant_at(timestamp);
680 }
681
682 Ok(backend)
683}
684
685pub(super) fn revert_to<'a, B: Backend + 'a>(
690 backend: &'a B,
691 hash: Hash,
692) -> Result<OverlayedBackend<'a, B>, Error> {
693 let first_number = backend.load_first_block_number()?.unwrap_or_default();
694
695 let mut backend = OverlayedBackend::new(backend);
696
697 let mut entry = match backend.load_block_entry(&hash)? {
698 Some(entry) => entry,
699 None => {
700 let blocks = backend.load_blocks_by_number(first_number)?;
710
711 let block = blocks
712 .first()
713 .and_then(|hash| backend.load_block_entry(hash).ok())
714 .flatten()
715 .ok_or_else(|| {
716 ChainApiError::from(format!(
717 "Lookup failure for block at height {}",
718 first_number
719 ))
720 })?;
721
722 if block.parent_hash != hash {
724 return Err(ChainApiError::from("Can't revert below last finalized block").into())
725 }
726
727 let block_number = first_number.saturating_sub(1);
731 let viability = ViabilityCriteria {
732 explicitly_reverted: false,
733 approval: Approval::Approved,
734 earliest_unviable_ancestor: None,
735 };
736 let entry = BlockEntry {
737 block_hash: hash,
738 block_number,
739 parent_hash: Hash::default(),
740 children: blocks,
741 viability,
742 weight: block.weight,
743 };
744 backend.write_blocks_by_number(block_number, vec![hash]);
746 entry
747 },
748 };
749
750 let mut stack: Vec<_> = std::mem::take(&mut entry.children)
751 .into_iter()
752 .map(|h| (h, entry.block_number + 1))
753 .collect();
754
755 backend.write_block_entry(entry.clone());
757
758 let mut viable_leaves = backend.load_leaves()?;
759
760 viable_leaves.insert(LeafEntry {
761 block_hash: hash,
762 block_number: entry.block_number,
763 weight: entry.weight,
764 });
765
766 while let Some((hash, number)) = stack.pop() {
767 let entry = backend.load_block_entry(&hash)?;
768 backend.delete_block_entry(&hash);
769
770 viable_leaves.remove(&hash);
771
772 let mut blocks_at_height = backend.load_blocks_by_number(number)?;
773 blocks_at_height.retain(|h| h != &hash);
774 backend.write_blocks_by_number(number, blocks_at_height);
775
776 stack.extend(entry.into_iter().flat_map(|e| e.children).map(|h| (h, number + 1)));
777 }
778
779 backend.write_leaves(viable_leaves);
780
781 Ok(backend)
782}