Merge commit '392447f5c8f986ded2559a78457f4cd87942f393' into update-bridges-subtree-r/w

This commit is contained in:
antonio-dropulic
2021-12-01 09:46:14 +01:00
321 changed files with 28385 additions and 10466 deletions
@@ -43,18 +43,19 @@ pub struct FinalitySyncParams {
/// `min(source_block_time, target_block_time)`.
///
/// This parameter may be used to limit transactions rate. Increase the value && you'll get
/// infrequent updates => sparse headers => potential slow down of bridge applications, but pallet storage
/// won't be super large. Decrease the value to near `source_block_time` and you'll get
/// transaction for (almost) every block of the source chain => all source headers will be known
/// to the target chain => bridge applications will run faster, but pallet storage may explode
/// (but if pruning is there, then it's fine).
/// infrequent updates => sparse headers => potential slow down of bridge applications, but
/// pallet storage won't be super large. Decrease the value to near `source_block_time` and
/// you'll get transaction for (almost) every block of the source chain => all source headers
/// will be known to the target chain => bridge applications will run faster, but pallet
/// storage may explode (but if pruning is there, then it's fine).
pub tick: Duration,
/// Number of finality proofs to keep in internal buffer between loop wakeups.
/// Number of finality proofs to keep in internal buffer between loop iterations.
///
/// While in "major syncing" state, we still read finality proofs from the stream. They're stored
/// in the internal buffer between loop wakeups. When we're close to the tip of the chain, we may
/// meet finality delays if headers are not finalized frequently. So instead of waiting for next
/// finality proof to appear in the stream, we may use existing proof from that buffer.
/// While in "major syncing" state, we still read finality proofs from the stream. They're
/// stored in the internal buffer between loop iterations. When we're close to the tip of the
/// chain, we may meet finality delays if headers are not finalized frequently. So instead of
/// waiting for next finality proof to appear in the stream, we may use existing proof from
/// that buffer.
pub recent_finality_proofs_limit: usize,
/// Timeout before we treat our transactions as lost and restart the whole sync process.
pub stall_timeout: Duration,
@@ -89,10 +90,15 @@ pub trait TargetClient<P: FinalitySyncPipeline>: RelayClient {
async fn best_finalized_source_block_number(&self) -> Result<P::Number, Self::Error>;
/// Submit header finality proof.
async fn submit_finality_proof(&self, header: P::Header, proof: P::FinalityProof) -> Result<(), Self::Error>;
async fn submit_finality_proof(
&self,
header: P::Header,
proof: P::FinalityProof,
) -> Result<(), Self::Error>;
}
/// Return prefix that will be used by default to expose Prometheus metrics of the finality proofs sync loop.
/// Return prefix that will be used by default to expose Prometheus metrics of the finality proofs
/// sync loop.
pub fn metrics_prefix<P: FinalitySyncPipeline>() -> String {
format!("{}_to_{}_Sync", P::SOURCE_NAME, P::TARGET_NAME)
}
@@ -104,12 +110,12 @@ pub async fn run<P: FinalitySyncPipeline>(
sync_params: FinalitySyncParams,
metrics_params: MetricsParams,
exit_signal: impl Future<Output = ()> + 'static + Send,
) -> Result<(), String> {
) -> Result<(), relay_utils::Error> {
let exit_signal = exit_signal.shared();
relay_utils::relay_loop(source_client, target_client)
.with_metrics(Some(metrics_prefix::<P>()), metrics_params)
.loop_metric(|registry, prefix| SyncLoopMetrics::new(registry, prefix))?
.standalone_metric(|registry, prefix| GlobalMetrics::new(registry, prefix))?
.loop_metric(SyncLoopMetrics::new)?
.standalone_metric(GlobalMetrics::new)?
.expose()
.await?
.run(metrics_prefix::<P>(), move |source_client, target_client, metrics| {
@@ -127,15 +133,11 @@ pub async fn run<P: FinalitySyncPipeline>(
/// Unjustified headers container. Ordered by header number.
pub(crate) type UnjustifiedHeaders<H> = Vec<H>;
/// Finality proofs container. Ordered by target header number.
pub(crate) type FinalityProofs<P> = Vec<(
<P as FinalitySyncPipeline>::Number,
<P as FinalitySyncPipeline>::FinalityProof,
)>;
pub(crate) type FinalityProofs<P> =
Vec<(<P as FinalitySyncPipeline>::Number, <P as FinalitySyncPipeline>::FinalityProof)>;
/// Reference to finality proofs container.
pub(crate) type FinalityProofsRef<'a, P> = &'a [(
<P as FinalitySyncPipeline>::Number,
<P as FinalitySyncPipeline>::FinalityProof,
)];
pub(crate) type FinalityProofsRef<'a, P> =
&'a [(<P as FinalitySyncPipeline>::Number, <P as FinalitySyncPipeline>::FinalityProof)];
/// Error that may happen inside finality synchronization loop.
#[derive(Debug)]
@@ -186,10 +188,7 @@ pub(crate) struct RestartableFinalityProofsStream<S> {
#[cfg(test)]
impl<S> From<S> for RestartableFinalityProofsStream<S> {
fn from(stream: S) -> Self {
RestartableFinalityProofsStream {
needs_restart: false,
stream: Box::pin(stream),
}
RestartableFinalityProofsStream { needs_restart: false, stream: Box::pin(stream) }
}
}
@@ -260,14 +259,12 @@ async fn run_until_connection_lost<P: FinalitySyncPipeline>(
last_transaction = updated_last_transaction;
retry_backoff.reset();
sync_params.tick
}
},
Err(error) => {
log::error!(target: "bridge", "Finality sync loop iteration has failed with error: {:?}", error);
error.fail_if_connection_error()?;
retry_backoff
.next_backoff()
.unwrap_or(relay_utils::relay_loop::RECONNECT_DELAY)
}
retry_backoff.next_backoff().unwrap_or(relay_utils::relay_loop::RECONNECT_DELAY)
},
};
if finality_proofs_stream.needs_restart {
log::warn!(target: "bridge", "{} finality proofs stream is being restarted", P::SOURCE_NAME);
@@ -297,10 +294,8 @@ where
TC: TargetClient<P>,
{
// read best source headers ids from source and target nodes
let best_number_at_source = source_client
.best_finalized_block_number()
.await
.map_err(Error::Source)?;
let best_number_at_source =
source_client.best_finalized_block_number().await.map_err(Error::Source)?;
let best_number_at_target = target_client
.best_finalized_source_block_number()
.await
@@ -309,7 +304,8 @@ where
metrics_sync.update_best_block_at_source(best_number_at_source);
metrics_sync.update_best_block_at_target(best_number_at_target);
}
*state.progress = print_sync_progress::<P>(*state.progress, best_number_at_source, best_number_at_target);
*state.progress =
print_sync_progress::<P>(*state.progress, best_number_at_source, best_number_at_target);
// if we have already submitted header, then we just need to wait for it
// if we're waiting too much, then we believe our transaction has been lost and restart sync
@@ -324,9 +320,9 @@ where
P::TARGET_NAME,
);
return Err(Error::Stalled);
return Err(Error::Stalled)
} else {
return Ok(Some(last_transaction));
return Ok(Some(last_transaction))
}
}
@@ -343,10 +339,8 @@ where
.await?
{
Some((header, justification)) => {
let new_transaction = Transaction {
time: Instant::now(),
submitted_header_number: header.number(),
};
let new_transaction =
Transaction { time: Instant::now(), submitted_header_number: header.number() };
log::debug!(
target: "bridge",
@@ -361,7 +355,7 @@ where
.await
.map_err(Error::Target)?;
Ok(Some(new_transaction))
}
},
None => Ok(None),
}
}
@@ -398,15 +392,15 @@ where
)
.await?;
let (mut unjustified_headers, mut selected_finality_proof) = match selected_finality_proof {
SelectedFinalityProof::Mandatory(header, finality_proof) => return Ok(Some((header, finality_proof))),
SelectedFinalityProof::Mandatory(header, finality_proof) =>
return Ok(Some((header, finality_proof))),
_ if sync_params.only_mandatory_headers => {
// we are not reading finality proofs from the stream, so eventually it'll break
// but we don't care about transient proofs at all, so it is acceptable
return Ok(None);
}
SelectedFinalityProof::Regular(unjustified_headers, header, finality_proof) => {
(unjustified_headers, Some((header, finality_proof)))
}
return Ok(None)
},
SelectedFinalityProof::Regular(unjustified_headers, header, finality_proof) =>
(unjustified_headers, Some((header, finality_proof))),
SelectedFinalityProof::None(unjustified_headers) => (unjustified_headers, None),
};
@@ -451,7 +445,11 @@ pub(crate) enum SelectedFinalityProof<Header, FinalityProof> {
/// Otherwise, `SelectedFinalityProof::None` is returned.
///
/// Unless we have found mandatory header, all missing headers are collected and returned.
pub(crate) async fn read_missing_headers<P: FinalitySyncPipeline, SC: SourceClient<P>, TC: TargetClient<P>>(
pub(crate) async fn read_missing_headers<
P: FinalitySyncPipeline,
SC: SourceClient<P>,
TC: TargetClient<P>,
>(
source_client: &SC,
_target_client: &TC,
best_number_at_source: P::Number,
@@ -470,22 +468,30 @@ pub(crate) async fn read_missing_headers<P: FinalitySyncPipeline, SC: SourceClie
match (is_mandatory, finality_proof) {
(true, Some(finality_proof)) => {
log::trace!(target: "bridge", "Header {:?} is mandatory", header_number);
return Ok(SelectedFinalityProof::Mandatory(header, finality_proof));
}
return Ok(SelectedFinalityProof::Mandatory(header, finality_proof))
},
(true, None) => return Err(Error::MissingMandatoryFinalityProof(header.number())),
(false, Some(finality_proof)) => {
log::trace!(target: "bridge", "Header {:?} has persistent finality proof", header_number);
unjustified_headers.clear();
selected_finality_proof = Some((header, finality_proof));
}
},
(false, None) => {
unjustified_headers.push(header);
}
},
}
header_number = header_number + One::one();
}
log::trace!(
target: "bridge",
"Read {} {} headers. Selected finality proof for header: {:?}",
best_number_at_source.saturating_sub(best_number_at_target),
P::SOURCE_NAME,
selected_finality_proof.as_ref().map(|(header, _)| header),
);
Ok(match selected_finality_proof {
Some((header, proof)) => SelectedFinalityProof::Regular(unjustified_headers, header, proof),
None => SelectedFinalityProof::None(unjustified_headers),
@@ -493,22 +499,46 @@ pub(crate) async fn read_missing_headers<P: FinalitySyncPipeline, SC: SourceClie
}
/// Read finality proofs from the stream.
pub(crate) fn read_finality_proofs_from_stream<P: FinalitySyncPipeline, FPS: Stream<Item = P::FinalityProof>>(
pub(crate) fn read_finality_proofs_from_stream<
P: FinalitySyncPipeline,
FPS: Stream<Item = P::FinalityProof>,
>(
finality_proofs_stream: &mut RestartableFinalityProofsStream<FPS>,
recent_finality_proofs: &mut FinalityProofs<P>,
) {
let mut proofs_count = 0;
let mut first_header_number = None;
let mut last_header_number = None;
loop {
let next_proof = finality_proofs_stream.stream.next();
let finality_proof = match next_proof.now_or_never() {
Some(Some(finality_proof)) => finality_proof,
Some(None) => {
finality_proofs_stream.needs_restart = true;
break;
}
break
},
None => break,
};
recent_finality_proofs.push((finality_proof.target_header_number(), finality_proof));
let target_header_number = finality_proof.target_header_number();
if first_header_number.is_none() {
first_header_number = Some(target_header_number);
}
last_header_number = Some(target_header_number);
proofs_count += 1;
recent_finality_proofs.push((target_header_number, finality_proof));
}
if proofs_count != 0 {
log::trace!(
target: "bridge",
"Read {} finality proofs from {} finality stream for headers in range [{:?}; {:?}]",
proofs_count,
P::SOURCE_NAME,
first_header_number,
last_header_number,
);
}
}
@@ -520,7 +550,13 @@ pub(crate) fn select_better_recent_finality_proof<P: FinalitySyncPipeline>(
selected_finality_proof: Option<(P::Header, P::FinalityProof)>,
) -> Option<(P::Header, P::FinalityProof)> {
if unjustified_headers.is_empty() || recent_finality_proofs.is_empty() {
return selected_finality_proof;
log::trace!(
target: "bridge",
"Can not improve selected {} finality proof {:?}. No unjustified headers and recent proofs",
P::SOURCE_NAME,
selected_finality_proof.as_ref().map(|(h, _)| h.number()),
);
return selected_finality_proof
}
const NOT_EMPTY_PROOF: &str = "we have checked that the vec is not empty; qed";
@@ -542,9 +578,24 @@ pub(crate) fn select_better_recent_finality_proof<P: FinalitySyncPipeline>(
let selected_finality_proof_index = recent_finality_proofs
.binary_search_by_key(intersection.end(), |(number, _)| *number)
.unwrap_or_else(|index| index.saturating_sub(1));
let (selected_header_number, finality_proof) = &recent_finality_proofs[selected_finality_proof_index];
if !intersection.contains(selected_header_number) {
return selected_finality_proof;
let (selected_header_number, finality_proof) =
&recent_finality_proofs[selected_finality_proof_index];
let has_selected_finality_proof = intersection.contains(selected_header_number);
log::trace!(
target: "bridge",
"Trying to improve selected {} finality proof {:?}. Headers range: [{:?}; {:?}]. Proofs range: [{:?}; {:?}].\
Trying to improve to: {:?}. Result: {}",
P::SOURCE_NAME,
selected_finality_proof.as_ref().map(|(h, _)| h.number()),
unjustified_range_begin,
unjustified_range_end,
buffered_range_begin,
buffered_range_end,
selected_header_number,
if has_selected_finality_proof { "improved" } else { "not improved" },
);
if !has_selected_finality_proof {
return selected_finality_proof
}
// now remove all obsolete headers and extract selected header
@@ -560,20 +611,15 @@ pub(crate) fn prune_recent_finality_proofs<P: FinalitySyncPipeline>(
recent_finality_proofs: &mut FinalityProofs<P>,
recent_finality_proofs_limit: usize,
) {
let position =
recent_finality_proofs.binary_search_by_key(&justified_header_number, |(header_number, _)| *header_number);
let position = recent_finality_proofs
.binary_search_by_key(&justified_header_number, |(header_number, _)| *header_number);
// remove all obsolete elements
*recent_finality_proofs = recent_finality_proofs.split_off(
position
.map(|position| position + 1)
.unwrap_or_else(|position| position),
);
*recent_finality_proofs = recent_finality_proofs
.split_off(position.map(|position| position + 1).unwrap_or_else(|position| position));
// now - limit vec by size
let split_index = recent_finality_proofs
.len()
.saturating_sub(recent_finality_proofs_limit);
let split_index = recent_finality_proofs.len().saturating_sub(recent_finality_proofs_limit);
*recent_finality_proofs = recent_finality_proofs.split_off(split_index);
}
@@ -585,15 +631,15 @@ fn print_sync_progress<P: FinalitySyncPipeline>(
let (prev_time, prev_best_number_at_target) = progress_context;
let now = Instant::now();
let need_update = now - prev_time > Duration::from_secs(10)
|| prev_best_number_at_target
let need_update = now - prev_time > Duration::from_secs(10) ||
prev_best_number_at_target
.map(|prev_best_number_at_target| {
best_number_at_target.saturating_sub(prev_best_number_at_target) > 10.into()
})
.unwrap_or(true);
if !need_update {
return (prev_time, prev_best_number_at_target);
return (prev_time, prev_best_number_at_target)
}
log::info!(
@@ -18,17 +18,21 @@
#![cfg(test)]
use crate::finality_loop::{
prune_recent_finality_proofs, read_finality_proofs_from_stream, run, select_better_recent_finality_proof,
select_header_to_submit, FinalityProofs, FinalitySyncParams, RestartableFinalityProofsStream, SourceClient,
TargetClient,
use crate::{
finality_loop::{
prune_recent_finality_proofs, read_finality_proofs_from_stream, run,
select_better_recent_finality_proof, select_header_to_submit, FinalityProofs,
FinalitySyncParams, RestartableFinalityProofsStream, SourceClient, TargetClient,
},
FinalityProof, FinalitySyncPipeline, SourceHeader,
};
use crate::{FinalityProof, FinalitySyncPipeline, SourceHeader};
use async_trait::async_trait;
use futures::{FutureExt, Stream, StreamExt};
use parking_lot::Mutex;
use relay_utils::{metrics::MetricsParams, relay_loop::Client as RelayClient, MaybeConnectionError};
use relay_utils::{
metrics::MetricsParams, relay_loop::Client as RelayClient, MaybeConnectionError,
};
use std::{collections::HashMap, pin::Pin, sync::Arc, time::Duration};
type IsMandatory = bool;
@@ -121,10 +125,7 @@ impl SourceClient<TestFinalitySyncPipeline> for TestSourceClient {
) -> Result<(TestSourceHeader, Option<TestFinalityProof>), TestError> {
let mut data = self.data.lock();
(self.on_method_call)(&mut *data);
data.source_headers
.get(&number)
.cloned()
.ok_or(TestError::NonConnection)
data.source_headers.get(&number).cloned().ok_or(TestError::NonConnection)
}
async fn finality_proofs(&self) -> Result<Self::FinalityProofsStream, TestError> {
@@ -157,7 +158,11 @@ impl TargetClient<TestFinalitySyncPipeline> for TestTargetClient {
Ok(data.target_best_block_number)
}
async fn submit_finality_proof(&self, header: TestSourceHeader, proof: TestFinalityProof) -> Result<(), TestError> {
async fn submit_finality_proof(
&self,
header: TestSourceHeader,
proof: TestFinalityProof,
) -> Result<(), TestError> {
let mut data = self.data.lock();
(self.on_method_call)(&mut *data);
data.target_best_block_number = header.number();
@@ -171,11 +176,12 @@ fn prepare_test_clients(
state_function: impl Fn(&mut ClientsData) -> bool + Send + Sync + 'static,
source_headers: HashMap<TestNumber, (TestSourceHeader, Option<TestFinalityProof>)>,
) -> (TestSourceClient, TestTargetClient) {
let internal_state_function: Arc<dyn Fn(&mut ClientsData) + Send + Sync> = Arc::new(move |data| {
if state_function(data) {
exit_sender.unbounded_send(()).unwrap();
}
});
let internal_state_function: Arc<dyn Fn(&mut ClientsData) + Send + Sync> =
Arc::new(move |data| {
if state_function(data) {
exit_sender.unbounded_send(()).unwrap();
}
});
let clients_data = Arc::new(Mutex::new(ClientsData {
source_best_block_number: 10,
source_headers,
@@ -189,14 +195,13 @@ fn prepare_test_clients(
on_method_call: internal_state_function.clone(),
data: clients_data.clone(),
},
TestTargetClient {
on_method_call: internal_state_function,
data: clients_data,
},
TestTargetClient { on_method_call: internal_state_function, data: clients_data },
)
}
fn run_sync_loop(state_function: impl Fn(&mut ClientsData) -> bool + Send + Sync + 'static) -> ClientsData {
fn run_sync_loop(
state_function: impl Fn(&mut ClientsData) -> bool + Send + Sync + 'static,
) -> ClientsData {
let (exit_sender, exit_receiver) = futures::channel::mpsc::unbounded();
let (source_client, target_client) = prepare_test_clients(
exit_sender,
@@ -234,12 +239,13 @@ fn run_sync_loop(state_function: impl Fn(&mut ClientsData) -> bool + Send + Sync
#[test]
fn finality_sync_loop_works() {
let client_data = run_sync_loop(|data| {
// header#7 has persistent finality proof, but it isn't mandatory => it isn't submitted, because
// header#8 has persistent finality proof && it is mandatory => it is submitted
// header#9 has persistent finality proof, but it isn't mandatory => it is submitted, because
// there are no more persistent finality proofs
// header#7 has persistent finality proof, but it isn't mandatory => it isn't submitted,
// because header#8 has persistent finality proof && it is mandatory => it is submitted
// header#9 has persistent finality proof, but it isn't mandatory => it is submitted,
// because there are no more persistent finality proofs
//
// once this ^^^ is done, we generate more blocks && read proof for blocks 12 and 14 from the stream
// once this ^^^ is done, we generate more blocks && read proof for blocks 12 and 14 from
// the stream
if data.target_best_block_number == 9 {
data.source_best_block_number = 14;
data.source_headers.insert(11, (TestSourceHeader(false, 11), None));
@@ -287,10 +293,7 @@ fn run_only_mandatory_headers_mode_test(
vec![
(6, (TestSourceHeader(false, 6), Some(TestFinalityProof(6)))),
(7, (TestSourceHeader(false, 7), Some(TestFinalityProof(7)))),
(
8,
(TestSourceHeader(has_mandatory_headers, 8), Some(TestFinalityProof(8))),
),
(8, (TestSourceHeader(has_mandatory_headers, 8), Some(TestFinalityProof(8)))),
(9, (TestSourceHeader(false, 9), Some(TestFinalityProof(9)))),
(10, (TestSourceHeader(false, 10), Some(TestFinalityProof(10)))),
]
@@ -357,7 +360,8 @@ fn select_better_recent_finality_proof_works() {
Some((TestSourceHeader(false, 2), TestFinalityProof(2))),
);
// if there's no intersection between recent finality proofs and unjustified headers, nothing is changed
// if there's no intersection between recent finality proofs and unjustified headers, nothing is
// changed
let mut unjustified_headers = vec![TestSourceHeader(false, 9), TestSourceHeader(false, 10)];
assert_eq!(
select_better_recent_finality_proof::<TestFinalitySyncPipeline>(
@@ -368,13 +372,10 @@ fn select_better_recent_finality_proof_works() {
Some((TestSourceHeader(false, 2), TestFinalityProof(2))),
);
// if there's intersection between recent finality proofs and unjustified headers, but there are no
// proofs in this intersection, nothing is changed
let mut unjustified_headers = vec![
TestSourceHeader(false, 8),
TestSourceHeader(false, 9),
TestSourceHeader(false, 10),
];
// if there's intersection between recent finality proofs and unjustified headers, but there are
// no proofs in this intersection, nothing is changed
let mut unjustified_headers =
vec![TestSourceHeader(false, 8), TestSourceHeader(false, 9), TestSourceHeader(false, 10)];
assert_eq!(
select_better_recent_finality_proof::<TestFinalitySyncPipeline>(
&[(7, TestFinalityProof(7)), (11, TestFinalityProof(11))],
@@ -385,22 +386,15 @@ fn select_better_recent_finality_proof_works() {
);
assert_eq!(
unjustified_headers,
vec![
TestSourceHeader(false, 8),
TestSourceHeader(false, 9),
TestSourceHeader(false, 10)
]
vec![TestSourceHeader(false, 8), TestSourceHeader(false, 9), TestSourceHeader(false, 10)]
);
// if there's intersection between recent finality proofs and unjustified headers and there's
// a proof in this intersection:
// - this better (last from intersection) proof is selected;
// - 'obsolete' unjustified headers are pruned.
let mut unjustified_headers = vec![
TestSourceHeader(false, 8),
TestSourceHeader(false, 9),
TestSourceHeader(false, 10),
];
let mut unjustified_headers =
vec![TestSourceHeader(false, 8), TestSourceHeader(false, 9), TestSourceHeader(false, 10)];
assert_eq!(
select_better_recent_finality_proof::<TestFinalitySyncPipeline>(
&[(7, TestFinalityProof(7)), (9, TestFinalityProof(9))],
@@ -416,7 +410,10 @@ fn read_finality_proofs_from_stream_works() {
// when stream is currently empty, nothing is changed
let mut recent_finality_proofs = vec![(1, TestFinalityProof(1))];
let mut stream = futures::stream::pending().into();
read_finality_proofs_from_stream::<TestFinalitySyncPipeline, _>(&mut stream, &mut recent_finality_proofs);
read_finality_proofs_from_stream::<TestFinalitySyncPipeline, _>(
&mut stream,
&mut recent_finality_proofs,
);
assert_eq!(recent_finality_proofs, vec![(1, TestFinalityProof(1))]);
assert!(!stream.needs_restart);
@@ -424,20 +421,20 @@ fn read_finality_proofs_from_stream_works() {
let mut stream = futures::stream::iter(vec![TestFinalityProof(4)])
.chain(futures::stream::pending())
.into();
read_finality_proofs_from_stream::<TestFinalitySyncPipeline, _>(&mut stream, &mut recent_finality_proofs);
assert_eq!(
recent_finality_proofs,
vec![(1, TestFinalityProof(1)), (4, TestFinalityProof(4))]
read_finality_proofs_from_stream::<TestFinalitySyncPipeline, _>(
&mut stream,
&mut recent_finality_proofs,
);
assert_eq!(recent_finality_proofs, vec![(1, TestFinalityProof(1)), (4, TestFinalityProof(4))]);
assert!(!stream.needs_restart);
// when stream has ended, we'll need to restart it
let mut stream = futures::stream::empty().into();
read_finality_proofs_from_stream::<TestFinalitySyncPipeline, _>(&mut stream, &mut recent_finality_proofs);
assert_eq!(
recent_finality_proofs,
vec![(1, TestFinalityProof(1)), (4, TestFinalityProof(4))]
read_finality_proofs_from_stream::<TestFinalitySyncPipeline, _>(
&mut stream,
&mut recent_finality_proofs,
);
assert_eq!(recent_finality_proofs, vec![(1, TestFinalityProof(1)), (4, TestFinalityProof(4))]);
assert!(stream.needs_restart);
}
+3 -1
View File
@@ -19,7 +19,9 @@
//! are still submitted to the target node, but are treated as auxiliary data as we are not trying
//! to submit all source headers to the target node.
pub use crate::finality_loop::{metrics_prefix, run, FinalitySyncParams, SourceClient, TargetClient};
pub use crate::finality_loop::{
metrics_prefix, run, FinalitySyncParams, SourceClient, TargetClient,
};
use bp_header_chain::FinalityProof;
use std::fmt::Debug;