Skip to content
Snippets Groups Projects
Commit 0a3f8ace authored by Svyatoslav Nikolsky's avatar Svyatoslav Nikolsky Committed by Bastian Köcher
Browse files

fixed on-demand parachains relay case: if better relay header is delivered,...

fixed on-demand parachains relay case: if better relay header is delivered, then we must select para header that may be proved using this relay header (#1419)
parent 188f16be
Branches
No related merge requests found
......@@ -16,39 +16,38 @@
//! Parachain heads source.
use crate::{
finality::source::RequiredHeaderNumberRef,
parachains::{ParachainsPipelineAdapter, SubstrateParachainsPipeline},
};
use crate::parachains::{ParachainsPipelineAdapter, SubstrateParachainsPipeline};
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 codec::Decode;
use parachains_relay::parachains_loop::SourceClient;
use parachains_relay::parachains_loop::{ParaHashAtSource, SourceClient};
use relay_substrate_client::{
Chain, Client, Error as SubstrateError, HeaderIdOf, HeaderOf, RelayChain,
};
use relay_utils::relay_loop::Client as RelayClient;
use sp_runtime::traits::Header as HeaderT;
/// Shared updatable reference to the maximal parachain header id that we want to sync from the
/// source.
pub type RequiredHeaderIdRef<C> = Arc<Mutex<Option<HeaderIdOf<C>>>>;
/// Substrate client as parachain heads source.
#[derive(Clone)]
pub struct ParachainsSource<P: SubstrateParachainsPipeline> {
client: Client<P::SourceRelayChain>,
maximal_header_number: Option<RequiredHeaderNumberRef<P::SourceParachain>>,
previous_parachain_head: Arc<Mutex<Option<ParaHash>>>,
maximal_header_id: Option<RequiredHeaderIdRef<P::SourceParachain>>,
}
impl<P: SubstrateParachainsPipeline> ParachainsSource<P> {
/// Creates new parachains source client.
pub fn new(
client: Client<P::SourceRelayChain>,
maximal_header_number: Option<RequiredHeaderNumberRef<P::SourceParachain>>,
maximal_header_id: Option<RequiredHeaderIdRef<P::SourceParachain>>,
) -> Self {
let previous_parachain_head = Arc::new(Mutex::new(None));
ParachainsSource { client, maximal_header_number, previous_parachain_head }
ParachainsSource { client, maximal_header_id }
}
/// Returns reference to the underlying RPC client.
......@@ -102,7 +101,7 @@ where
&self,
at_block: HeaderIdOf<P::SourceRelayChain>,
para_id: ParaId,
) -> Result<Option<ParaHash>, Self::Error> {
) -> Result<ParaHashAtSource, Self::Error> {
// we don't need to support many parachains now
if para_id.0 != P::SOURCE_PARACHAIN_PARA_ID {
return Err(SubstrateError::Custom(format!(
......@@ -112,29 +111,33 @@ where
)))
}
let parachain_head = match self.on_chain_parachain_header(at_block, para_id).await? {
Ok(match self.on_chain_parachain_header(at_block, para_id).await? {
Some(parachain_header) => {
let mut parachain_head = Some(parachain_header.hash());
let mut parachain_head = ParaHashAtSource::Some(parachain_header.hash());
// never return head that is larger than requested. This way we'll never sync
// headers past `maximal_header_number`
if let Some(ref maximal_header_number) = self.maximal_header_number {
let maximal_header_number = *maximal_header_number.lock().await;
if *parachain_header.number() > maximal_header_number {
let previous_parachain_head = *self.previous_parachain_head.lock().await;
if let Some(previous_parachain_head) = previous_parachain_head {
parachain_head = Some(previous_parachain_head);
}
// headers past `maximal_header_id`
if let Some(ref maximal_header_id) = self.maximal_header_id {
let maximal_header_id = *maximal_header_id.lock().await;
match maximal_header_id {
Some(maximal_header_id)
if *parachain_header.number() > maximal_header_id.0 =>
{
// we don't want this header yet => let's report previously requested
// header
parachain_head = ParaHashAtSource::Some(maximal_header_id.1);
},
Some(_) => (),
None => {
// on-demand relay has not yet asked us to sync anything let's do that
parachain_head = ParaHashAtSource::Unavailable;
},
}
}
parachain_head
},
None => None,
};
*self.previous_parachain_head.lock().await = parachain_head;
Ok(parachain_head)
None => ParaHashAtSource::None,
})
}
async fn prove_parachain_heads(
......
......@@ -52,6 +52,23 @@ pub enum ParachainSyncStrategy {
All,
}
/// Parachain head hash, available at the source (relay) chain.
#[derive(Clone, Copy, Debug)]
pub enum ParaHashAtSource {
/// There's no parachain head at the source chain.
///
/// Normally it means that the parachain is not registered there.
None,
/// Parachain head with given hash is available at the source chain.
Some(ParaHash),
/// The source client refuses to report parachain head hash at this moment.
///
/// It is a "mild" error, which may appear when e.g. on-demand parachains relay is used.
/// This variant must be treated as "we don't want to update parachain head value at the
/// target chain at this moment".
Unavailable,
}
/// Source client used in parachain heads synchronization loop.
#[async_trait]
pub trait SourceClient<P: ParachainsPipeline>: RelayClient {
......@@ -63,7 +80,7 @@ pub trait SourceClient<P: ParachainsPipeline>: RelayClient {
&self,
at_block: HeaderIdOf<P::SourceChain>,
para_id: ParaId,
) -> Result<Option<ParaHash>, Self::Error>;
) -> Result<ParaHashAtSource, Self::Error>;
/// Get parachain heads proof.
async fn prove_parachain_heads(
......@@ -291,7 +308,7 @@ where
/// Given heads at source and target clients, returns set of heads that are out of sync.
fn select_parachains_to_update<P: ParachainsPipeline>(
heads_at_source: BTreeMap<ParaId, Option<ParaHash>>,
heads_at_source: BTreeMap<ParaId, ParaHashAtSource>,
heads_at_target: BTreeMap<ParaId, Option<BestParaHeadHash>>,
best_finalized_relay_block: HeaderIdOf<P::SourceChain>,
) -> Vec<ParaId>
......@@ -317,7 +334,12 @@ where
.zip(heads_at_target.into_iter())
.filter(|((para, head_at_source), (_, head_at_target))| {
let needs_update = match (head_at_source, head_at_target) {
(Some(head_at_source), Some(head_at_target))
(ParaHashAtSource::Unavailable, _) => {
// source client has politely asked us not to update current parachain head
// at the target chain
false
},
(ParaHashAtSource::Some(head_at_source), Some(head_at_target))
if head_at_target.at_relay_block_number < best_finalized_relay_block.0 &&
head_at_target.head_hash != *head_at_source =>
{
......@@ -325,22 +347,22 @@ where
// client
true
},
(Some(_), Some(_)) => {
(ParaHashAtSource::Some(_), Some(_)) => {
// this is normal case when relay has recently updated heads, when parachain is
// not progressing or when our source client is
false
},
(Some(_), None) => {
(ParaHashAtSource::Some(_), None) => {
// parachain is not yet known to the target client. This is true when parachain
// or bridge has been just onboarded/started
true
},
(None, Some(_)) => {
(ParaHashAtSource::None, Some(_)) => {
// parachain/parathread has been offboarded removed from the system. It needs to
// be propageted to the target client
true
},
(None, None) => {
(ParaHashAtSource::None, None) => {
// all's good - parachain is unknown to both clients
false
},
......@@ -378,7 +400,7 @@ async fn read_heads_at_source<P: ParachainsPipeline>(
source_client: &impl SourceClient<P>,
at_relay_block: &HeaderIdOf<P::SourceChain>,
parachains: &[ParaId],
) -> Result<BTreeMap<ParaId, Option<ParaHash>>, FailedClient> {
) -> Result<BTreeMap<ParaId, ParaHashAtSource>, FailedClient> {
let mut para_head_hashes = BTreeMap::new();
for para in parachains {
let para_head = source_client.parachain_head(*at_relay_block, *para).await;
......@@ -554,7 +576,7 @@ mod tests {
#[derive(Clone, Debug)]
struct TestClientData {
source_sync_status: Result<bool, TestError>,
source_heads: BTreeMap<u32, Result<ParaHash, TestError>>,
source_heads: BTreeMap<u32, Result<ParaHashAtSource, TestError>>,
source_proofs: BTreeMap<u32, Result<Vec<u8>, TestError>>,
target_best_block: Result<HeaderIdOf<TestChain>, TestError>,
......@@ -569,7 +591,9 @@ mod tests {
pub fn minimal() -> Self {
TestClientData {
source_sync_status: Ok(true),
source_heads: vec![(PARA_ID, Ok(PARA_0_HASH))].into_iter().collect(),
source_heads: vec![(PARA_ID, Ok(ParaHashAtSource::Some(PARA_0_HASH)))]
.into_iter()
.collect(),
source_proofs: vec![(PARA_ID, Ok(PARA_0_HASH.encode()))].into_iter().collect(),
target_best_block: Ok(HeaderId(0, Default::default())),
......@@ -615,8 +639,11 @@ mod tests {
&self,
_at_block: HeaderIdOf<TestChain>,
para_id: ParaId,
) -> Result<Option<ParaHash>, TestError> {
self.data.lock().await.source_heads.get(&para_id.0).cloned().transpose()
) -> Result<ParaHashAtSource, TestError> {
match self.data.lock().await.source_heads.get(&para_id.0).cloned() {
Some(result) => result,
None => Ok(ParaHashAtSource::None),
}
}
async fn prove_parachain_heads(
......@@ -923,7 +950,7 @@ mod tests {
fn parachain_is_not_updated_if_it_is_unknown_to_both_clients() {
assert_eq!(
select_parachains_to_update::<TestParachainsPipeline>(
vec![(ParaId(PARA_ID), None)].into_iter().collect(),
vec![(ParaId(PARA_ID), ParaHashAtSource::None)].into_iter().collect(),
vec![(ParaId(PARA_ID), None)].into_iter().collect(),
HeaderId(10, Default::default()),
),
......@@ -935,7 +962,9 @@ mod tests {
fn parachain_is_not_updated_if_it_has_been_updated_at_better_relay_block() {
assert_eq!(
select_parachains_to_update::<TestParachainsPipeline>(
vec![(ParaId(PARA_ID), Some(PARA_0_HASH))].into_iter().collect(),
vec![(ParaId(PARA_ID), ParaHashAtSource::Some(PARA_0_HASH))]
.into_iter()
.collect(),
vec![(
ParaId(PARA_ID),
Some(BestParaHeadHash { at_relay_block_number: 20, head_hash: PARA_1_HASH })
......@@ -952,7 +981,9 @@ mod tests {
fn parachain_is_not_updated_if_hash_is_the_same_at_next_relay_block() {
assert_eq!(
select_parachains_to_update::<TestParachainsPipeline>(
vec![(ParaId(PARA_ID), Some(PARA_0_HASH))].into_iter().collect(),
vec![(ParaId(PARA_ID), ParaHashAtSource::Some(PARA_0_HASH))]
.into_iter()
.collect(),
vec![(
ParaId(PARA_ID),
Some(BestParaHeadHash { at_relay_block_number: 0, head_hash: PARA_0_HASH })
......@@ -969,7 +1000,7 @@ mod tests {
fn parachain_is_updated_after_offboarding() {
assert_eq!(
select_parachains_to_update::<TestParachainsPipeline>(
vec![(ParaId(PARA_ID), None)].into_iter().collect(),
vec![(ParaId(PARA_ID), ParaHashAtSource::None)].into_iter().collect(),
vec![(
ParaId(PARA_ID),
Some(BestParaHeadHash {
......@@ -989,7 +1020,9 @@ mod tests {
fn parachain_is_updated_after_onboarding() {
assert_eq!(
select_parachains_to_update::<TestParachainsPipeline>(
vec![(ParaId(PARA_ID), Some(PARA_0_HASH))].into_iter().collect(),
vec![(ParaId(PARA_ID), ParaHashAtSource::Some(PARA_0_HASH))]
.into_iter()
.collect(),
vec![(ParaId(PARA_ID), None)].into_iter().collect(),
HeaderId(10, Default::default()),
),
......@@ -1001,7 +1034,9 @@ mod tests {
fn parachain_is_updated_if_newer_head_is_known() {
assert_eq!(
select_parachains_to_update::<TestParachainsPipeline>(
vec![(ParaId(PARA_ID), Some(PARA_1_HASH))].into_iter().collect(),
vec![(ParaId(PARA_ID), ParaHashAtSource::Some(PARA_1_HASH))]
.into_iter()
.collect(),
vec![(
ParaId(PARA_ID),
Some(BestParaHeadHash { at_relay_block_number: 0, head_hash: PARA_0_HASH })
......@@ -1014,6 +1049,23 @@ mod tests {
);
}
#[test]
fn parachain_is_not_updated_if_source_head_is_unavailable() {
assert_eq!(
select_parachains_to_update::<TestParachainsPipeline>(
vec![(ParaId(PARA_ID), ParaHashAtSource::Unavailable)].into_iter().collect(),
vec![(
ParaId(PARA_ID),
Some(BestParaHeadHash { at_relay_block_number: 0, head_hash: PARA_0_HASH })
)]
.into_iter()
.collect(),
HeaderId(10, Default::default()),
),
vec![],
);
}
#[test]
fn is_update_required_works() {
let mut sync_params = ParachainSyncParams {
......
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment