use crate::{
parachains::{ParachainsPipelineAdapter, SubstrateParachainsPipeline},
proofs::to_raw_storage_proof,
};
use async_std::sync::{Arc, Mutex};
use async_trait::async_trait;
use bp_parachains::parachain_head_storage_key_at_source;
use bp_polkadot_core::parachains::{ParaHash, ParaHead, ParaHeadsProof, ParaId};
use bp_runtime::HeaderIdProvider;
use codec::Decode;
use parachains_relay::parachains_loop::{AvailableHeader, SourceClient};
use relay_substrate_client::{
is_ancient_block, Chain, Client, Error as SubstrateError, HeaderIdOf, HeaderOf, ParachainBase,
RelayChain,
};
use relay_utils::relay_loop::Client as RelayClient;
pub type RequiredHeaderIdRef<C> = Arc<Mutex<AvailableHeader<HeaderIdOf<C>>>>;
#[derive(Clone)]
pub struct ParachainsSource<P: SubstrateParachainsPipeline, SourceRelayClnt> {
client: SourceRelayClnt,
max_head_id: RequiredHeaderIdRef<P::SourceParachain>,
}
impl<P: SubstrateParachainsPipeline, SourceRelayClnt: Client<P::SourceRelayChain>>
ParachainsSource<P, SourceRelayClnt>
{
pub fn new(
client: SourceRelayClnt,
max_head_id: RequiredHeaderIdRef<P::SourceParachain>,
) -> Self {
ParachainsSource { client, max_head_id }
}
pub fn client(&self) -> &SourceRelayClnt {
&self.client
}
pub async fn on_chain_para_head_id(
&self,
at_block: HeaderIdOf<P::SourceRelayChain>,
) -> Result<Option<HeaderIdOf<P::SourceParachain>>, SubstrateError> {
let para_id = ParaId(P::SourceParachain::PARACHAIN_ID);
let storage_key =
parachain_head_storage_key_at_source(P::SourceRelayChain::PARAS_PALLET_NAME, para_id);
let para_head: Option<ParaHead> =
self.client.storage_value(at_block.hash(), storage_key).await?;
let para_head = match para_head {
Some(para_head) => para_head,
None => return Ok(None),
};
let para_head: HeaderOf<P::SourceParachain> = Decode::decode(&mut ¶_head.0[..])?;
Ok(Some(para_head.id()))
}
}
#[async_trait]
impl<P: SubstrateParachainsPipeline, SourceRelayClnt: Client<P::SourceRelayChain>> RelayClient
for ParachainsSource<P, SourceRelayClnt>
{
type Error = SubstrateError;
async fn reconnect(&mut self) -> Result<(), SubstrateError> {
self.client.reconnect().await
}
}
#[async_trait]
impl<P: SubstrateParachainsPipeline, SourceRelayClnt: Client<P::SourceRelayChain>>
SourceClient<ParachainsPipelineAdapter<P>> for ParachainsSource<P, SourceRelayClnt>
where
P::SourceParachain: Chain<Hash = ParaHash>,
{
async fn ensure_synced(&self) -> Result<bool, Self::Error> {
match self.client.ensure_synced().await {
Ok(_) => Ok(true),
Err(SubstrateError::ClientNotSynced(_)) => Ok(false),
Err(e) => Err(e),
}
}
async fn parachain_head(
&self,
at_block: HeaderIdOf<P::SourceRelayChain>,
) -> Result<AvailableHeader<HeaderIdOf<P::SourceParachain>>, Self::Error> {
let best_block_number = self.client.best_finalized_header_number().await?;
if is_ancient_block(at_block.number(), best_block_number) {
log::trace!(
target: "bridge",
"{} block {:?} is ancient. Cannot prove the {} header there",
P::SourceRelayChain::NAME,
at_block,
P::SourceParachain::NAME,
);
return Ok(AvailableHeader::Unavailable)
}
let mut para_head_id = AvailableHeader::Missing;
if let Some(on_chain_para_head_id) = self.on_chain_para_head_id(at_block).await? {
para_head_id = match *self.max_head_id.lock().await {
AvailableHeader::Unavailable => AvailableHeader::Unavailable,
AvailableHeader::Missing => {
AvailableHeader::Available(on_chain_para_head_id)
},
AvailableHeader::Available(max_head_id) if on_chain_para_head_id >= max_head_id => {
AvailableHeader::Available(std::cmp::min(on_chain_para_head_id, max_head_id))
},
AvailableHeader::Available(_) => {
AvailableHeader::Unavailable
},
}
}
Ok(para_head_id)
}
async fn prove_parachain_head(
&self,
at_block: HeaderIdOf<P::SourceRelayChain>,
) -> Result<(ParaHeadsProof, ParaHash), Self::Error> {
let parachain = ParaId(P::SourceParachain::PARACHAIN_ID);
let storage_key =
parachain_head_storage_key_at_source(P::SourceRelayChain::PARAS_PALLET_NAME, parachain);
let storage_proof =
self.client.prove_storage(at_block.hash(), vec![storage_key.clone()]).await?;
let parachain_head = self
.client
.storage_value::<ParaHead>(at_block.hash(), storage_key)
.await?
.ok_or_else(|| {
SubstrateError::Custom(format!(
"Failed to read expected parachain {parachain:?} head at {at_block:?}"
))
})?;
let parachain_head_hash = parachain_head.hash();
Ok((
ParaHeadsProof {
storage_proof: to_raw_storage_proof::<P::SourceRelayChain>(storage_proof),
},
parachain_head_hash,
))
}
}