Skip to main content

soil_client/blockchain/
backend.rs

1// This file is part of Soil.
2
3// Copyright (C) Soil contributors.
4// Copyright (C) Parity Technologies (UK) Ltd.
5// SPDX-License-Identifier: Apache-2.0 OR GPL-3.0-or-later WITH Classpath-exception-2.0
6
7//! Substrate blockchain trait
8
9use codec::{Decode, Encode};
10use parking_lot::RwLock;
11use std::collections::{btree_set::BTreeSet, HashMap, VecDeque};
12use subsoil::runtime::{
13	generic::BlockId,
14	traits::{Block as BlockT, Header as HeaderT, NumberFor, Zero},
15	Justifications,
16};
17use tracing::{debug, warn};
18
19use super::{
20	error::{Error, Result},
21	header_metadata::HeaderMetadata,
22	tree_route, CachedHeaderMetadata,
23};
24
25/// Blockchain database header backend. Does not perform any validation.
26pub trait HeaderBackend<Block: BlockT>: Send + Sync {
27	/// Get block header. Returns `None` if block is not found.
28	fn header(&self, hash: Block::Hash) -> Result<Option<Block::Header>>;
29	/// Get blockchain info.
30	fn info(&self) -> Info<Block>;
31	/// Get block status.
32	fn status(&self, hash: Block::Hash) -> Result<BlockStatus>;
33	/// Get block number by hash. Returns `None` if the header is not in the chain.
34	fn number(
35		&self,
36		hash: Block::Hash,
37	) -> Result<Option<<<Block as BlockT>::Header as HeaderT>::Number>>;
38	/// Get block hash by number. Returns `None` if the header is not in the chain.
39	fn hash(&self, number: NumberFor<Block>) -> Result<Option<Block::Hash>>;
40
41	/// Convert an arbitrary block ID into a block hash.
42	fn block_hash_from_id(&self, id: &BlockId<Block>) -> Result<Option<Block::Hash>> {
43		match *id {
44			BlockId::Hash(h) => Ok(Some(h)),
45			BlockId::Number(n) => self.hash(n),
46		}
47	}
48
49	/// Convert an arbitrary block ID into a block hash.
50	fn block_number_from_id(&self, id: &BlockId<Block>) -> Result<Option<NumberFor<Block>>> {
51		match *id {
52			BlockId::Hash(h) => self.number(h),
53			BlockId::Number(n) => Ok(Some(n)),
54		}
55	}
56
57	/// Get block header. Returns `UnknownBlock` error if block is not found.
58	fn expect_header(&self, hash: Block::Hash) -> Result<Block::Header> {
59		self.header(hash)?
60			.ok_or_else(|| Error::UnknownBlock(format!("Expect header: {}", hash)))
61	}
62
63	/// Convert an arbitrary block ID into a block number. Returns `UnknownBlock` error if block is
64	/// not found.
65	fn expect_block_number_from_id(&self, id: &BlockId<Block>) -> Result<NumberFor<Block>> {
66		self.block_number_from_id(id).and_then(|n| {
67			n.ok_or_else(|| Error::UnknownBlock(format!("Expect block number from id: {}", id)))
68		})
69	}
70
71	/// Convert an arbitrary block ID into a block hash. Returns `UnknownBlock` error if block is
72	/// not found.
73	fn expect_block_hash_from_id(&self, id: &BlockId<Block>) -> Result<Block::Hash> {
74		self.block_hash_from_id(id).and_then(|h| {
75			h.ok_or_else(|| Error::UnknownBlock(format!("Expect block hash from id: {}", id)))
76		})
77	}
78}
79
80/// Handles stale forks.
81pub trait ForkBackend<Block: BlockT>:
82	HeaderMetadata<Block> + HeaderBackend<Block> + Send + Sync
83{
84	/// Returns block hashes for provided fork heads. It skips the fork if when blocks are missing
85	/// (e.g. warp-sync) and internal `tree_route` function fails.
86	///
87	/// Example:
88	///  G --- A1 --- A2 --- A3 --- A4           ( < fork1 )
89	///                       \-----C4 --- C5    ( < fork2 )
90	/// We finalize A3 and call expand_fork(C5). Result = (C5,C4).
91	fn expand_forks(
92		&self,
93		fork_heads: &[Block::Hash],
94	) -> std::result::Result<BTreeSet<Block::Hash>, Error> {
95		let mut expanded_forks = BTreeSet::new();
96		for fork_head in fork_heads {
97			match tree_route(self, *fork_head, self.info().finalized_hash) {
98				Ok(tree_route) => {
99					for block in tree_route.retracted() {
100						expanded_forks.insert(block.hash);
101					}
102					continue;
103				},
104				Err(_) => {
105					// There are cases when blocks are missing (e.g. warp-sync).
106				},
107			}
108		}
109
110		Ok(expanded_forks)
111	}
112}
113
114impl<Block, T> ForkBackend<Block> for T
115where
116	Block: BlockT,
117	T: HeaderMetadata<Block> + HeaderBackend<Block> + Send + Sync,
118{
119}
120
121struct MinimalBlockMetadata<Block: BlockT> {
122	number: NumberFor<Block>,
123	hash: Block::Hash,
124	parent: Block::Hash,
125}
126
127impl<Block> Clone for MinimalBlockMetadata<Block>
128where
129	Block: BlockT,
130{
131	fn clone(&self) -> Self {
132		Self { number: self.number, hash: self.hash, parent: self.parent }
133	}
134}
135
136impl<Block> Copy for MinimalBlockMetadata<Block> where Block: BlockT {}
137
138impl<Block> From<&CachedHeaderMetadata<Block>> for MinimalBlockMetadata<Block>
139where
140	Block: BlockT,
141{
142	fn from(value: &CachedHeaderMetadata<Block>) -> Self {
143		Self { number: value.number, hash: value.hash, parent: value.parent }
144	}
145}
146
147/// Blockchain database backend. Does not perform any validation.
148pub trait Backend<Block: BlockT>:
149	HeaderBackend<Block> + HeaderMetadata<Block, Error = Error>
150{
151	/// Get block body. Returns `None` if block is not found.
152	fn body(&self, hash: Block::Hash) -> Result<Option<Vec<<Block as BlockT>::Extrinsic>>>;
153	/// Get block justifications. Returns `None` if no justification exists.
154	fn justifications(&self, hash: Block::Hash) -> Result<Option<Justifications>>;
155	/// Get last finalized block hash.
156	fn last_finalized(&self) -> Result<Block::Hash>;
157
158	/// Returns hashes of all blocks that are leaves of the block tree.
159	/// in other words, that have no children, are chain heads.
160	/// Results must be ordered best (longest, highest) chain first.
161	fn leaves(&self) -> Result<Vec<Block::Hash>>;
162
163	/// Return hashes of all blocks that are children of the block with `parent_hash`.
164	fn children(&self, parent_hash: Block::Hash) -> Result<Vec<Block::Hash>>;
165
166	/// Get the most recent block hash of the longest chain that contains
167	/// a block with the given `base_hash`.
168	///
169	/// The search space is always limited to blocks which are in the finalized
170	/// chain or descendants of it.
171	///
172	/// Returns `Ok(None)` if `base_hash` is not found in search space.
173	// TODO: document time complexity of this, see [#1444](https://github.com/paritytech/substrate/issues/1444)
174	fn longest_containing(
175		&self,
176		base_hash: Block::Hash,
177		import_lock: &RwLock<()>,
178	) -> Result<Option<Block::Hash>> {
179		let Some(base_header) = self.header(base_hash)? else { return Ok(None) };
180
181		let leaves = {
182			// ensure no blocks are imported during this code block.
183			// an import could trigger a reorg which could change the canonical chain.
184			// we depend on the canonical chain staying the same during this code block.
185			let _import_guard = import_lock.read();
186			let info = self.info();
187			if info.finalized_number > *base_header.number() {
188				// `base_header` is on a dead fork.
189				return Ok(None);
190			}
191			self.leaves()?
192		};
193
194		// for each chain. longest chain first. shortest last
195		for leaf_hash in leaves {
196			let mut current_hash = leaf_hash;
197			// go backwards through the chain (via parent links)
198			loop {
199				if current_hash == base_hash {
200					return Ok(Some(leaf_hash));
201				}
202
203				let current_header = self
204					.header(current_hash)?
205					.ok_or_else(|| Error::MissingHeader(current_hash.to_string()))?;
206
207				// stop search in this chain once we go below the target's block number
208				if current_header.number() < base_header.number() {
209					break;
210				}
211
212				current_hash = *current_header.parent_hash();
213			}
214		}
215
216		// header may be on a dead fork -- the only leaves that are considered are
217		// those which can still be finalized.
218		//
219		// FIXME #1558 only issue this warning when not on a dead fork
220		warn!(
221			target: super::LOG_TARGET,
222			"Block {:?} exists in chain but not found when following all leaves backwards",
223			base_hash,
224		);
225
226		Ok(None)
227	}
228
229	/// Get single indexed transaction by content hash. Note that this will only fetch transactions
230	/// that are indexed by the runtime with `storage_index_transaction`.
231	fn indexed_transaction(&self, hash: Block::Hash) -> Result<Option<Vec<u8>>>;
232
233	/// Check if indexed transaction exists.
234	fn has_indexed_transaction(&self, hash: Block::Hash) -> Result<bool> {
235		Ok(self.indexed_transaction(hash)?.is_some())
236	}
237
238	fn block_indexed_body(&self, hash: Block::Hash) -> Result<Option<Vec<Vec<u8>>>>;
239
240	/// Returns all leaves that will be displaced after the block finalization.
241	fn displaced_leaves_after_finalizing(
242		&self,
243		finalized_block_hash: Block::Hash,
244		finalized_block_number: NumberFor<Block>,
245		finalized_block_parent_hash: Block::Hash,
246	) -> std::result::Result<DisplacedLeavesAfterFinalization<Block>, Error> {
247		// There are no forks at genesis.
248		if finalized_block_number.is_zero() {
249			return Ok(DisplacedLeavesAfterFinalization::default());
250		}
251
252		let leaves = self.leaves()?;
253
254		let now = std::time::Instant::now();
255		debug!(
256			target: super::LOG_TARGET,
257			?leaves,
258			?finalized_block_hash,
259			?finalized_block_number,
260			"Checking for displaced leaves after finalization."
261		);
262
263		// Store hashes of finalized blocks for quick checking later, the last block is the
264		// finalized one
265		let mut finalized_chain = VecDeque::new();
266		finalized_chain.push_front(MinimalBlockMetadata {
267			number: finalized_block_number,
268			hash: finalized_block_hash,
269			parent: finalized_block_parent_hash,
270		});
271
272		// Local cache is a performance optimization in case of finalized block deep below the
273		// tip of the chain with a lot of leaves above finalized block
274		let mut local_cache = HashMap::<Block::Hash, MinimalBlockMetadata<Block>>::new();
275
276		let mut result = DisplacedLeavesAfterFinalization {
277			displaced_leaves: Vec::with_capacity(leaves.len()),
278			displaced_blocks: Vec::with_capacity(leaves.len()),
279		};
280
281		let mut displaced_blocks_candidates = Vec::new();
282
283		let genesis_hash = self.info().genesis_hash;
284
285		for leaf_hash in leaves {
286			let mut current_header_metadata =
287				MinimalBlockMetadata::from(&self.header_metadata(leaf_hash).map_err(|err| {
288					debug!(
289						target: super::LOG_TARGET,
290						?leaf_hash,
291						?err,
292						elapsed = ?now.elapsed(),
293						"Failed to fetch leaf header.",
294					);
295					err
296				})?);
297			let leaf_number = current_header_metadata.number;
298
299			// The genesis block is part of the canonical chain.
300			if leaf_hash == genesis_hash {
301				result.displaced_leaves.push((leaf_number, leaf_hash));
302				debug!(
303					target: super::LOG_TARGET,
304					?leaf_hash,
305					elapsed = ?now.elapsed(),
306					"Added genesis leaf to displaced leaves."
307				);
308				continue;
309			}
310
311			debug!(
312				target: super::LOG_TARGET,
313				?leaf_number,
314				?leaf_hash,
315				elapsed = ?now.elapsed(),
316				"Handle displaced leaf.",
317			);
318
319			// Collect all block hashes until the height of the finalized block
320			displaced_blocks_candidates.clear();
321			while current_header_metadata.number > finalized_block_number {
322				displaced_blocks_candidates.push(current_header_metadata.hash);
323
324				let parent_hash = current_header_metadata.parent;
325				match local_cache.get(&parent_hash) {
326					Some(metadata_header) => {
327						current_header_metadata = *metadata_header;
328					},
329					None => {
330						current_header_metadata = MinimalBlockMetadata::from(
331							&self.header_metadata(parent_hash).map_err(|err| {
332								debug!(
333									target: super::LOG_TARGET,
334									?err,
335									?parent_hash,
336									?leaf_hash,
337									elapsed = ?now.elapsed(),
338									"Failed to fetch parent header during leaf tracking.",
339								);
340
341								err
342							})?,
343						);
344						// Cache locally in case more branches above finalized block reference
345						// the same block hash
346						local_cache.insert(parent_hash, current_header_metadata);
347					},
348				}
349			}
350
351			// If points back to the finalized header then nothing left to do, this leaf will be
352			// checked again later
353			if current_header_metadata.hash == finalized_block_hash {
354				debug!(
355					target: super::LOG_TARGET,
356					?leaf_hash,
357					elapsed = ?now.elapsed(),
358					"Leaf points to the finalized header, skipping for now.",
359				);
360
361				continue;
362			}
363
364			// We reuse `displaced_blocks_candidates` to store the current metadata.
365			// This block is not displaced if there is a gap in the ancestry. We
366			// check for this gap later.
367			displaced_blocks_candidates.push(current_header_metadata.hash);
368
369			debug!(
370				target: super::LOG_TARGET,
371				current_hash = ?current_header_metadata.hash,
372				current_num = ?current_header_metadata.number,
373				?finalized_block_number,
374				elapsed = ?now.elapsed(),
375				"Looking for path from finalized block number to current leaf number"
376			);
377
378			// Collect the rest of the displaced blocks of leaf branch
379			for distance_from_finalized in 1_u32.. {
380				// Find block at `distance_from_finalized` from finalized block
381				let (finalized_chain_block_number, finalized_chain_block_hash) =
382					match finalized_chain.iter().rev().nth(distance_from_finalized as usize) {
383						Some(header) => (header.number, header.hash),
384						None => {
385							let to_fetch = finalized_chain.front().expect("Not empty; qed");
386							let metadata = match self.header_metadata(to_fetch.parent) {
387								Ok(metadata) => metadata,
388								Err(Error::UnknownBlock(_)) => {
389									debug!(
390										target: super::LOG_TARGET,
391										distance_from_finalized,
392										hash = ?to_fetch.parent,
393										number = ?to_fetch.number,
394										elapsed = ?now.elapsed(),
395										"Tried to fetch unknown block, block ancestry has gaps."
396									);
397									break;
398								},
399								Err(err) => {
400									debug!(
401										target: super::LOG_TARGET,
402										hash = ?to_fetch.parent,
403										number = ?to_fetch.number,
404										?err,
405										elapsed = ?now.elapsed(),
406										"Failed to fetch header for parent hash.",
407									);
408									return Err(err);
409								},
410							};
411							let metadata = MinimalBlockMetadata::from(&metadata);
412							let result = (metadata.number, metadata.hash);
413							finalized_chain.push_front(metadata);
414							result
415						},
416					};
417
418				if current_header_metadata.hash == finalized_chain_block_hash {
419					// Found the block on the finalized chain, nothing left to do
420					result.displaced_leaves.push((leaf_number, leaf_hash));
421
422					debug!(
423						target: super::LOG_TARGET,
424						?leaf_hash,
425						elapsed = ?now.elapsed(),
426						"Leaf is ancestor of finalized block."
427					);
428					break;
429				}
430
431				if current_header_metadata.number <= finalized_chain_block_number {
432					// Skip more blocks until we get all blocks on finalized chain until the height
433					// of the parent block
434					continue;
435				}
436
437				let parent_hash = current_header_metadata.parent;
438				if finalized_chain_block_hash == parent_hash {
439					// Reached finalized chain, nothing left to do
440					result.displaced_blocks.extend(displaced_blocks_candidates.drain(..));
441					result.displaced_leaves.push((leaf_number, leaf_hash));
442
443					debug!(
444						target: super::LOG_TARGET,
445						?leaf_hash,
446						elapsed = ?now.elapsed(),
447						"Found displaced leaf."
448					);
449					break;
450				}
451
452				// Store displaced block and look deeper for block on finalized chain
453				debug!(
454					target: super::LOG_TARGET,
455					?parent_hash,
456					elapsed = ?now.elapsed(),
457					"Found displaced block. Looking further.",
458				);
459				displaced_blocks_candidates.push(parent_hash);
460				current_header_metadata = MinimalBlockMetadata::from(
461					&self.header_metadata(parent_hash).map_err(|err| {
462						debug!(
463							target: super::LOG_TARGET,
464							?err,
465							?parent_hash,
466							elapsed = ?now.elapsed(),
467							"Failed to fetch header for parent during displaced block collection",
468						);
469						err
470					})?,
471				);
472			}
473		}
474
475		// There could be duplicates shared by multiple branches, clean them up
476		result.displaced_blocks.sort_unstable();
477		result.displaced_blocks.dedup();
478
479		debug!(
480			target: super::LOG_TARGET,
481			%finalized_block_hash,
482			?finalized_block_number,
483			?result,
484			elapsed = ?now.elapsed(),
485			"Finished checking for displaced leaves after finalization.",
486		);
487
488		return Ok(result);
489	}
490}
491
492/// Result of  [`Backend::displaced_leaves_after_finalizing`].
493#[derive(Clone, Debug)]
494pub struct DisplacedLeavesAfterFinalization<Block: BlockT> {
495	/// A list of hashes and block numbers of displaced leaves.
496	pub displaced_leaves: Vec<(NumberFor<Block>, Block::Hash)>,
497
498	/// A list of hashes displaced blocks from all displaced leaves.
499	pub displaced_blocks: Vec<Block::Hash>,
500}
501
502impl<Block: BlockT> Default for DisplacedLeavesAfterFinalization<Block> {
503	fn default() -> Self {
504		Self { displaced_leaves: Vec::new(), displaced_blocks: Vec::new() }
505	}
506}
507
508impl<Block: BlockT> DisplacedLeavesAfterFinalization<Block> {
509	/// Returns a collection of hashes for the displaced leaves.
510	pub fn hashes(&self) -> impl Iterator<Item = Block::Hash> + '_ {
511		self.displaced_leaves.iter().map(|(_, hash)| *hash)
512	}
513}
514
515/// Represents the type of block gaps that may result from either warp sync or fast sync.
516#[derive(Debug, Clone, Copy, Eq, PartialEq, Encode, Decode)]
517pub enum BlockGapType {
518	/// Both the header and body are missing, as a result of warp sync.
519	MissingHeaderAndBody,
520	/// The block body is missing, as a result of fast sync.
521	MissingBody,
522}
523
524/// Represents the block gap resulted by warp sync or fast sync.
525///
526/// A block gap is a range of blocks where either the bodies, or both headers and bodies are
527/// missing.
528#[derive(Debug, Clone, Copy, Eq, PartialEq, Encode, Decode)]
529pub struct BlockGap<N> {
530	/// The starting block number of the gap (inclusive).
531	pub start: N,
532	/// The ending block number of the gap (inclusive).
533	pub end: N,
534	/// The type of gap.
535	pub gap_type: BlockGapType,
536}
537
538/// Blockchain info
539#[derive(Debug, Eq, PartialEq, Clone)]
540pub struct Info<Block: BlockT> {
541	/// Best block hash.
542	pub best_hash: Block::Hash,
543	/// Best block number.
544	pub best_number: <<Block as BlockT>::Header as HeaderT>::Number,
545	/// Genesis block hash.
546	pub genesis_hash: Block::Hash,
547	/// The head of the finalized chain.
548	pub finalized_hash: Block::Hash,
549	/// Last finalized block number.
550	pub finalized_number: <<Block as BlockT>::Header as HeaderT>::Number,
551	/// Last finalized state.
552	pub finalized_state: Option<(Block::Hash, <<Block as BlockT>::Header as HeaderT>::Number)>,
553	/// Number of concurrent leave forks.
554	pub number_leaves: usize,
555	/// Missing blocks after warp sync or fast sync.
556	pub block_gap: Option<BlockGap<NumberFor<Block>>>,
557}
558
559/// Block status.
560#[derive(Debug, Clone, Copy, PartialEq, Eq)]
561pub enum BlockStatus {
562	/// Already in the blockchain.
563	InChain,
564	/// Not in the queue or the blockchain.
565	Unknown,
566}