use std::{
fmt::{Display, Error as FmtError, Formatter},
sync::Arc,
};
use log::warn;
use sc_client_api::{blockchain::HeaderBackend, Backend as _};
use sc_service::TFullBackend;
use sp_blockchain::{Backend as _, Error as BackendError, Info};
use sp_runtime::traits::{Block as SubstrateBlock, Header as SubstrateHeader};
use crate::{
phron_primitives::{
Block, BlockNumber, Hash as PhronHash, Header as PhronHeader, PHRON_ENGINE_ID,
},
justification::backwards_compatible_decode,
sync::{
substrate::Justification, BlockStatus, ChainStatus, FinalizationStatus, Header,
Justification as _, LOG_TARGET,
},
BlockId,
};
#[derive(Debug)]
pub enum Error {
MissingHash(PhronHash),
MissingBody(PhronHash),
MissingJustification(PhronHash),
Backend(BackendError),
MismatchedId,
NoGenesisBlock,
}
impl Display for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
use Error::*;
match self {
MissingHash(hash) => {
write!(
f,
"data availability problem: no block for existing hash {hash:?}"
)
}
MissingBody(hash) => {
write!(
f,
"data availability problem: no block body for existing hash {hash:?}"
)
}
MissingJustification(hash) => {
write!(
f,
"data availability problem: no justification for finalized block with hash {hash:?}"
)
}
Backend(e) => {
write!(f, "substrate backend error {e}")
}
MismatchedId => write!(f, "the block number did not match the block hash"),
NoGenesisBlock => write!(f, "genesis block not present in DB"),
}
}
}
impl From<BackendError> for Error {
fn from(value: BackendError) -> Self {
Error::Backend(value)
}
}
#[derive(Clone)]
pub struct SubstrateChainStatus {
backend: Arc<TFullBackend<Block>>,
genesis_header: PhronHeader,
}
impl SubstrateChainStatus {
pub fn new(backend: Arc<TFullBackend<Block>>) -> Result<Self, Error> {
let hash = backend.blockchain().hash(0)?.ok_or(Error::NoGenesisBlock)?;
let genesis_header = backend
.blockchain()
.header(hash)?
.ok_or(Error::MissingHash(hash))?;
Ok(Self {
backend,
genesis_header,
})
}
fn info(&self) -> Info<Block> {
self.backend.blockchain().info()
}
fn hash_for_number(&self, number: BlockNumber) -> Result<Option<PhronHash>, BackendError> {
self.backend.blockchain().hash(number)
}
fn header_for_hash(&self, hash: PhronHash) -> Result<Option<PhronHeader>, BackendError> {
self.backend.blockchain().header(hash)
}
fn body_for_hash(
&self,
hash: PhronHash,
) -> Result<Option<Vec<<Block as SubstrateBlock>::Extrinsic>>, BackendError> {
self.backend.blockchain().body(hash)
}
fn header(&self, id: &BlockId) -> Result<Option<PhronHeader>, Error> {
let maybe_header = self.header_for_hash(id.hash)?;
match maybe_header
.as_ref()
.map(|header| header.number() == &id.number)
{
Some(false) => Err(Error::MismatchedId),
_ => Ok(maybe_header),
}
}
fn justification(&self, header: PhronHeader) -> Result<Option<Justification>, BackendError> {
if header == self.genesis_header {
return Ok(Some(Justification::genesis_justification(header)));
};
let encoded_justification = match self
.backend
.blockchain()
.justifications(header.hash())?
.and_then(|j| j.into_justification(PHRON_ENGINE_ID))
{
Some(justification) => justification,
None => return Ok(None),
};
match backwards_compatible_decode(encoded_justification) {
Ok(phron_justification) => Ok(Some(Justification::phron_justification(
header,
phron_justification,
))),
Err(e) => {
warn!(
target: LOG_TARGET,
"Could not decode stored justification for block {:?}: {}",
header.hash(),
e
);
Ok(None)
}
}
}
fn best_hash(&self) -> PhronHash {
self.info().best_hash
}
fn finalized_hash(&self) -> PhronHash {
self.info().finalized_hash
}
}
impl ChainStatus<Block, Justification> for SubstrateChainStatus {
type Error = Error;
fn finalized_at(
&self,
number: BlockNumber,
) -> Result<FinalizationStatus<Justification>, Self::Error> {
use FinalizationStatus::*;
if number > self.top_finalized()?.header().id().number {
return Ok(NotFinalized);
}
let id = match self.hash_for_number(number)? {
Some(hash) => BlockId { hash, number },
None => return Ok(NotFinalized),
};
match self.status_of(id)? {
BlockStatus::Justified(justification) => Ok(FinalizedWithJustification(justification)),
BlockStatus::Present(header) => Ok(FinalizedByDescendant(header)),
_ => Ok(NotFinalized),
}
}
fn block(&self, id: BlockId) -> Result<Option<Block>, Self::Error> {
let header = match self.header(&id)? {
Some(header) => header,
None => return Ok(None),
};
let body = match self.body_for_hash(id.hash)? {
Some(body) => body,
None => return Err(Error::MissingBody(id.hash)),
};
Ok(Some(Block::new(header, body)))
}
fn status_of(&self, id: BlockId) -> Result<BlockStatus<Justification>, Self::Error> {
let header = match self.header(&id)? {
Some(header) => header,
None => return Ok(BlockStatus::Unknown),
};
if let Some(justification) = self.justification(header.clone())? {
Ok(BlockStatus::Justified(justification))
} else {
Ok(BlockStatus::Present(header))
}
}
fn best_block(&self) -> Result<PhronHeader, Self::Error> {
let best_hash = self.best_hash();
self.header_for_hash(best_hash)?
.ok_or(Error::MissingHash(best_hash))
}
fn top_finalized(&self) -> Result<Justification, Self::Error> {
let finalized_hash = self.finalized_hash();
let header = self
.header_for_hash(finalized_hash)?
.ok_or(Error::MissingHash(finalized_hash))?;
self.justification(header)?
.ok_or(Error::MissingJustification(finalized_hash))
}
fn children(&self, id: BlockId) -> Result<Vec<PhronHeader>, Self::Error> {
self.header(&id)?;
Ok(self
.backend
.blockchain()
.children(id.hash)?
.into_iter()
.map(|hash| self.header_for_hash(hash))
.collect::<Result<Vec<Option<PhronHeader>>, BackendError>>()?
.into_iter()
.flatten()
.collect())
}
}