Skip to main content

soil_consensus/
longest_chain.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: GPL-3.0-or-later WITH Classpath-exception-2.0
6
7//! Longest chain implementation
8
9use soil_client::blockchain::{Backend, HeaderBackend};
10use soil_client::client_api::backend;
11use soil_client::consensus::{Error as ConsensusError, SelectChain};
12use std::{marker::PhantomData, sync::Arc};
13use subsoil::runtime::traits::{Block as BlockT, Header, NumberFor};
14
15/// Implement Longest Chain Select implementation
16/// where 'longest' is defined as the highest number of blocks
17pub struct LongestChain<B, Block> {
18	backend: Arc<B>,
19	_phantom: PhantomData<Block>,
20}
21
22impl<B, Block> Clone for LongestChain<B, Block> {
23	fn clone(&self) -> Self {
24		let backend = self.backend.clone();
25		LongestChain { backend, _phantom: Default::default() }
26	}
27}
28
29impl<B, Block> LongestChain<B, Block>
30where
31	B: backend::Backend<Block>,
32	Block: BlockT,
33{
34	/// Instantiate a new LongestChain for Backend B
35	pub fn new(backend: Arc<B>) -> Self {
36		LongestChain { backend, _phantom: Default::default() }
37	}
38
39	fn best_hash(&self) -> soil_client::blockchain::Result<<Block as BlockT>::Hash> {
40		let info = self.backend.blockchain().info();
41		let import_lock = self.backend.get_import_lock();
42		let best_hash = self
43			.backend
44			.blockchain()
45			.longest_containing(info.best_hash, import_lock)?
46			.unwrap_or(info.best_hash);
47		Ok(best_hash)
48	}
49
50	fn best_header(&self) -> soil_client::blockchain::Result<<Block as BlockT>::Header> {
51		let best_hash = self.best_hash()?;
52		Ok(self
53			.backend
54			.blockchain()
55			.header(best_hash)?
56			.expect("given block hash was fetched from block in db; qed"))
57	}
58
59	/// Returns the highest descendant of the given block that is a valid
60	/// candidate to be finalized.
61	///
62	/// In this context, being a valid target means being an ancestor of
63	/// the best chain according to the `best_header` method.
64	///
65	/// If `maybe_max_number` is `Some(max_block_number)` the search is
66	/// limited to block `number <= max_block_number`. In other words
67	/// as if there were no blocks greater than `max_block_number`.
68	fn finality_target(
69		&self,
70		base_hash: Block::Hash,
71		maybe_max_number: Option<NumberFor<Block>>,
72	) -> soil_client::blockchain::Result<Block::Hash> {
73		use soil_client::blockchain::Error::{Application, MissingHeader};
74		let blockchain = self.backend.blockchain();
75
76		let mut current_head = self.best_header()?;
77		let mut best_hash = current_head.hash();
78
79		let base_header = blockchain
80			.header(base_hash)?
81			.ok_or_else(|| MissingHeader(base_hash.to_string()))?;
82		let base_number = *base_header.number();
83
84		if let Some(max_number) = maybe_max_number {
85			if max_number < base_number {
86				let msg = format!(
87					"Requested a finality target using max number {} below the base number {}",
88					max_number, base_number
89				);
90				return Err(Application(msg.into()));
91			}
92
93			while current_head.number() > &max_number {
94				best_hash = *current_head.parent_hash();
95				current_head = blockchain
96					.header(best_hash)?
97					.ok_or_else(|| MissingHeader(format!("{best_hash:?}")))?;
98			}
99		}
100
101		while current_head.hash() != base_hash {
102			if *current_head.number() < base_number {
103				let msg = format!(
104					"Requested a finality target using a base {:?} not in the best chain {:?}",
105					base_hash, best_hash,
106				);
107				return Err(Application(msg.into()));
108			}
109			let current_hash = *current_head.parent_hash();
110			current_head = blockchain
111				.header(current_hash)?
112				.ok_or_else(|| MissingHeader(format!("{best_hash:?}")))?;
113		}
114
115		Ok(best_hash)
116	}
117
118	fn leaves(&self) -> Result<Vec<<Block as BlockT>::Hash>, soil_client::blockchain::Error> {
119		self.backend.blockchain().leaves()
120	}
121}
122
123#[async_trait::async_trait]
124impl<B, Block> SelectChain<Block> for LongestChain<B, Block>
125where
126	B: backend::Backend<Block>,
127	Block: BlockT,
128{
129	async fn leaves(&self) -> Result<Vec<<Block as BlockT>::Hash>, ConsensusError> {
130		LongestChain::leaves(self).map_err(|e| ConsensusError::ChainLookup(e.to_string()))
131	}
132
133	async fn best_chain(&self) -> Result<<Block as BlockT>::Header, ConsensusError> {
134		LongestChain::best_header(self).map_err(|e| ConsensusError::ChainLookup(e.to_string()))
135	}
136
137	async fn finality_target(
138		&self,
139		base_hash: Block::Hash,
140		maybe_max_number: Option<NumberFor<Block>>,
141	) -> Result<Block::Hash, ConsensusError> {
142		LongestChain::finality_target(self, base_hash, maybe_max_number)
143			.map_err(|e| ConsensusError::ChainLookup(e.to_string()))
144	}
145}