referrerpolicy=no-referrer-when-downgrade

polkadot_node_core_chain_selection/
tree.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//! Implements the tree-view over the data backend which we use to determine
18//! viable leaves.
19//!
20//! The metadata is structured as a tree, with the root implicitly being the
21//! finalized block, which is not stored as part of the tree.
22//!
23//! Each direct descendant of the finalized block acts as its own sub-tree,
24//! and as the finalized block advances, orphaned sub-trees are entirely pruned.
25
26use 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
35// A viability update to be applied to a block.
36struct ViabilityUpdate(Option<Hash>);
37
38impl ViabilityUpdate {
39	// Apply the viability update to a single block, yielding the updated
40	// block entry along with a vector of children and the updates to apply
41	// to them.
42	fn apply(self, mut entry: BlockEntry) -> (BlockEntry, Vec<(Hash, ViabilityUpdate)>) {
43		// 1. When an ancestor has changed from unviable to viable,
44		// we erase the `earliest_unviable_ancestor` of all descendants
45		// until encountering a explicitly unviable descendant D.
46		//
47		// We then update the `earliest_unviable_ancestor` for all
48		// descendants of D to be equal to D.
49		//
50		// 2. When an ancestor A has changed from viable to unviable,
51		// we update the `earliest_unviable_ancestor` for all blocks
52		// to A.
53		//
54		// The following algorithm covers both cases.
55		//
56		// Furthermore, if there has been any change in viability,
57		// it is necessary to visit every single descendant of the root
58		// block.
59		//
60		// If a block B was unviable and is now viable, then every descendant
61		// has an `earliest_unviable_ancestor` which must be updated either
62		// to nothing or to the new earliest unviable ancestor.
63		//
64		// If a block B was viable and is now unviable, then every descendant
65		// has an `earliest_unviable_ancestor` which needs to be set to B.
66
67		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
88// Propagate viability update to descendants of the given block. This writes
89// the `base` entry as well as all descendants. If the parent of the block
90// entry is not viable, this will not affect any descendants.
91//
92// If the block entry provided is self-unviable, then it's assumed that an
93// unviability update needs to be propagated to descendants.
94//
95// If the block entry provided is self-viable, then it's assumed that a
96// viability update needs to be propagated to descendants.
97fn 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		// If the parent of the block is still unviable,
108		// then the `earliest_viable_ancestor` will not change
109		// regardless of the change in the block here.
110		//
111		// Furthermore, in such cases, the set of viable leaves
112		// does not change at all.
113		backend.write_block_entry(base);
114		return Ok(())
115	}
116
117	let mut viable_leaves = backend.load_leaves()?;
118
119	// A mapping of Block Hash -> number
120	// Where the hash is the hash of a viable block which has
121	// at least 1 unviable child.
122	//
123	// The number is the number of known unviable children which is known
124	// as the pivot count.
125	let mut viability_pivots = HashMap::new();
126
127	// If the base block is itself explicitly unviable,
128	// this will change to a `Some(base_hash)` after the first
129	// invocation.
130	let viability_update = ViabilityUpdate(None);
131
132	// Recursively apply update to tree.
133	//
134	// As we go, we remove any blocks from the leaves which are no longer viable
135	// leaves. We also add blocks to the leaves-set which are obviously viable leaves.
136	// And we build up a frontier of blocks which may either be viable leaves or
137	// the ancestors of one.
138	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			// A block which is viable has a parent which is obviously not
160			// in the viable leaves set.
161			viable_leaves.remove(&new_entry.parent_hash);
162
163			// Furthermore, if the block is viable and has no children,
164			// it is viable by definition.
165			if new_entry.children.is_empty() {
166				viable_leaves.insert(new_entry.leaf_entry());
167			}
168		} else {
169			// A block which is not viable is certainly not a viable leaf.
170			viable_leaves.remove(&new_entry.block_hash);
171
172			// When the parent is viable but the entry itself is not, that means
173			// that the parent is a viability pivot. As we visit the children
174			// of a viability pivot, we build up an exhaustive pivot count.
175			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	// Revisit the viability pivots now that we've traversed the entire subtree.
187	// After this point, the viable leaves set is fully updated. A proof follows.
188	//
189	// If the base has become unviable, then we've iterated into all descendants,
190	// made them unviable and removed them from the set. We know that the parent is
191	// viable as this function is a no-op otherwise, so we need to see if the parent
192	// has other children or not.
193	//
194	// If the base has become viable, then we've iterated into all descendants,
195	// and found all blocks which are viable and have no children. We've already added
196	// those blocks to the leaf set, but what we haven't detected
197	// is blocks which are viable and have children, but all of the children are
198	// unviable.
199	//
200	// The solution of viability pivots addresses both of these:
201	//
202	// When the base has become unviable, the parent's viability is unchanged and therefore
203	// any leaves descending from parent but not base are still in the viable leaves set.
204	// If the parent has only one child which is the base, the parent is now a viable leaf.
205	// We've already visited the base in recursive search so the set of pivots should
206	// contain only a single entry `(parent, 1)`. qed.
207	//
208	// When the base has become viable, we've already iterated into every descendant
209	// of the base and thus have collected a set of pivots whose corresponding pivot
210	// counts have already been exhaustively computed from their children. qed.
211	for (pivot, pivot_count) in viability_pivots {
212		match backend.load_block_entry(&pivot)? {
213			None => {
214				// This means the block is finalized. We might reach this
215				// code path when the base is a child of the finalized block
216				// and has become unviable.
217				//
218				// Each such child is the root of its own tree
219				// which, as an invariant, does not depend on the viability
220				// of the finalized block. So no siblings need to be inspected
221				// and we can ignore it safely.
222				//
223				// Furthermore, if the set of viable leaves is empty, the
224				// finalized block is implicitly the viable leaf.
225				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
239/// Imports a new block and applies any reversions to ancestors or the block itself.
240pub(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
256// Load the given ancestor's block entry, in descending order from the `block_hash`.
257// The ancestor_number must be not higher than the `block_entry`'s.
258//
259// The returned entry will be `None` if the range is invalid or any block in the path had
260// no entry present. If any block entry was missing, it can safely be assumed to
261// be finalized.
262fn 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(&current_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	// Current entry should always be `Some` here.
291	Ok(current_entry)
292}
293
294// Add a new block to the tree, which is assumed to be unreverted and unapproved,
295// but not stagnant. It inherits viability from its parent, if any.
296//
297// This updates the parent entry, if any, and updates the viable leaves set accordingly.
298// This also schedules a stagnation-check update and adds the block to the blocks-by-number
299// mapping.
300fn 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	// 1. Add the block to the DB assuming it's not reverted.
315	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	// 2. Update leaves if inherited viability is fine.
330	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	// 3. Update and write the parent
337	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	// 4. Add to blocks-by-number.
343	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	// 5. Add stagnation timeout.
348	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
355/// Assuming that a block is already imported, accepts the number of the block
356/// as well as a list of reversions triggered by the block in ascending order.
357fn apply_reversions(
358	backend: &mut OverlayedBackend<impl Backend>,
359	block_entry: BlockEntry,
360	reversions: Vec<BlockNumber>,
361) -> Result<(), Error> {
362	// Note: since revert numbers are  in ascending order, the expensive propagation
363	// of unviability is only heavy on the first log.
364	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
387/// Marks a single block as explicitly reverted, then propagates viability updates
388/// to all its children. This is triggered when the disputes subsystem signals that
389/// a dispute has concluded against a candidate.
390pub(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			// Marks children of reverted block as non-viable
446			propagate_viability_update(backend, block_entry)?;
447		},
448	}
449	Ok(())
450}
451
452/// Finalize a block with the given number and hash.
453///
454/// This will prune all sub-trees not descending from the given block,
455/// all block entries at or before the given height,
456/// and will update the viability of all sub-trees descending from the given
457/// block if the finalized block was not viable.
458///
459/// This is assumed to start with a fresh backend, and will produce
460/// an overlay over the backend with all the changes applied.
461pub(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			// This implies that there are no unfinalized blocks and hence nothing
472			// to update.
473			return Ok(backend)
474		},
475		Some(e) => e,
476	};
477
478	let mut viable_leaves = backend.load_leaves()?;
479
480	// Walk all numbers up to the finalized number and remove those entries.
481	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	// Remove all blocks at the finalized height, with the exception of the finalized block,
492	// and their descendants, recursively.
493	{
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			// This does a few extra `clone`s but is unlikely to be
509			// a bottleneck. Code complexity is very low as a result.
510			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			// Add all children to the frontier.
515			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	// Visit and remove the finalized block, fetching its children.
521	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	// Update the viability of each child.
532	for child in children_of_finalized {
533		if let Some(mut child) = backend.load_block_entry(&child)? {
534			// Finalized blocks are always viable.
535			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			// No need to do anything, but this is an inconsistent state.
548		}
549	}
550
551	Ok(backend)
552}
553
554/// Mark a block as approved and update the viability of itself and its
555/// descendants accordingly.
556pub(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		// Approval can change the viability in only one direction.
566		// If the viability has changed, then we propagate that to children
567		// and recalculate the viable leaf set.
568		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
584/// Check whether any blocks up to the given timestamp are stagnant and update
585/// accordingly.
586///
587/// This accepts a fresh backend and returns an overlay on top of it representing
588/// all changes made.
589pub(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	// As this is in ascending order, only the earliest stagnant
604	// blocks will involve heavy viability propagations.
605	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
652/// Prune stagnant entries at some timestamp without other checks
653/// This function is intended just to clean leftover entries when the real
654/// stagnant checks are disabled
655pub(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
685/// Revert the tree to the block relative to `hash`.
686///
687/// This accepts a fresh backend and returns an overlay on top of it representing
688/// all changes made.
689pub(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			// May be a revert to the last finalized block. If this is the case,
701			// then revert to this block should be handled specially since no
702			// information about finalized blocks is persisted within the tree.
703			//
704			// We use part of the information contained in the finalized block
705			// children (that are expected to be in the tree) to construct a
706			// dummy block entry for the last finalized block. This will be
707			// wiped as soon as the next block is finalized.
708
709			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			// The parent is expected to be the last finalized block.
723			if block.parent_hash != hash {
724				return Err(ChainApiError::from("Can't revert below last finalized block").into())
725			}
726
727			// The weight is set to the one of the first child. Even though this is
728			// not accurate, it does the job. The reason is that the revert point is
729			// the last finalized block, i.e. this is the best and only choice.
730			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			// This becomes the first entry according to the block number.
745			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	// Write revert point block entry without the children.
756	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}