use sc_client_api::backend;
use sp_blockchain::{Backend, HeaderBackend};
use sp_consensus::{Error as ConsensusError, SelectChain};
use sp_runtime::traits::{Block as BlockT, Header, NumberFor};
use std::{marker::PhantomData, sync::Arc};
pub struct LongestChain<B, Block> {
backend: Arc<B>,
_phantom: PhantomData<Block>,
}
impl<B, Block> Clone for LongestChain<B, Block> {
fn clone(&self) -> Self {
let backend = self.backend.clone();
LongestChain { backend, _phantom: Default::default() }
}
}
impl<B, Block> LongestChain<B, Block>
where
B: backend::Backend<Block>,
Block: BlockT,
{
pub fn new(backend: Arc<B>) -> Self {
LongestChain { backend, _phantom: Default::default() }
}
fn best_hash(&self) -> sp_blockchain::Result<<Block as BlockT>::Hash> {
let info = self.backend.blockchain().info();
let import_lock = self.backend.get_import_lock();
let best_hash = self
.backend
.blockchain()
.longest_containing(info.best_hash, import_lock)?
.unwrap_or(info.best_hash);
Ok(best_hash)
}
fn best_header(&self) -> sp_blockchain::Result<<Block as BlockT>::Header> {
let best_hash = self.best_hash()?;
Ok(self
.backend
.blockchain()
.header(best_hash)?
.expect("given block hash was fetched from block in db; qed"))
}
fn finality_target(
&self,
base_hash: Block::Hash,
maybe_max_number: Option<NumberFor<Block>>,
) -> sp_blockchain::Result<Block::Hash> {
use sp_blockchain::Error::{Application, MissingHeader};
let blockchain = self.backend.blockchain();
let mut current_head = self.best_header()?;
let mut best_hash = current_head.hash();
let base_header = blockchain
.header(base_hash)?
.ok_or_else(|| MissingHeader(base_hash.to_string()))?;
let base_number = *base_header.number();
if let Some(max_number) = maybe_max_number {
if max_number < base_number {
let msg = format!(
"Requested a finality target using max number {} below the base number {}",
max_number, base_number
);
return Err(Application(msg.into()))
}
while current_head.number() > &max_number {
best_hash = *current_head.parent_hash();
current_head = blockchain
.header(best_hash)?
.ok_or_else(|| MissingHeader(format!("{best_hash:?}")))?;
}
}
while current_head.hash() != base_hash {
if *current_head.number() < base_number {
let msg = format!(
"Requested a finality target using a base {:?} not in the best chain {:?}",
base_hash, best_hash,
);
return Err(Application(msg.into()))
}
let current_hash = *current_head.parent_hash();
current_head = blockchain
.header(current_hash)?
.ok_or_else(|| MissingHeader(format!("{best_hash:?}")))?;
}
Ok(best_hash)
}
fn leaves(&self) -> Result<Vec<<Block as BlockT>::Hash>, sp_blockchain::Error> {
self.backend.blockchain().leaves()
}
}
#[async_trait::async_trait]
impl<B, Block> SelectChain<Block> for LongestChain<B, Block>
where
B: backend::Backend<Block>,
Block: BlockT,
{
async fn leaves(&self) -> Result<Vec<<Block as BlockT>::Hash>, ConsensusError> {
LongestChain::leaves(self).map_err(|e| ConsensusError::ChainLookup(e.to_string()))
}
async fn best_chain(&self) -> Result<<Block as BlockT>::Header, ConsensusError> {
LongestChain::best_header(self).map_err(|e| ConsensusError::ChainLookup(e.to_string()))
}
async fn finality_target(
&self,
base_hash: Block::Hash,
maybe_max_number: Option<NumberFor<Block>>,
) -> Result<Block::Hash, ConsensusError> {
LongestChain::finality_target(self, base_hash, maybe_max_number)
.map_err(|e| ConsensusError::ChainLookup(e.to_string()))
}
}