Backport: Implement basic equivocations detection loop (#2375)

* Implement basic equivocations detection loop (#2367)

* FinalityProofsBuf adjustments

- store a Vec<FinalityProof>
- transform prune `buf_limit` to Option

* FinalityProof: add target_header_hash()

* Target client: implement best_synced_header_hash()

* Implement first version of the equivocations detection loop

* Address code review comments

* Leftover

* polkadot-staging adjustments
This commit is contained in:
Serban Iorga
2023-08-23 19:31:17 +03:00
committed by Bastian Köcher
parent cc3bbc690b
commit 588508acd4
18 changed files with 572 additions and 79 deletions
+1 -1
View File
@@ -32,7 +32,7 @@ pub trait FinalityPipeline: 'static + Clone + Debug + Send + Sync {
/// Synced headers are identified by this number.
type Number: relay_utils::BlockNumberBase;
/// Finality proof type.
type FinalityProof: FinalityProof<Self::Number>;
type FinalityProof: FinalityProof<Self::Hash, Self::Number>;
}
/// Source client used in finality related loops.
+4 -2
View File
@@ -319,8 +319,10 @@ impl<P: FinalitySyncPipeline, SC: SourceClient<P>, TC: TargetClient<P>> Finality
.as_ref()
.map(|justified_header| justified_header.number())
.unwrap_or(info.best_number_at_target);
self.finality_proofs_buf
.prune(oldest_finality_proof_to_keep, self.sync_params.recent_finality_proofs_limit);
self.finality_proofs_buf.prune(
oldest_finality_proof_to_keep,
Some(self.sync_params.recent_finality_proofs_limit),
);
Ok(maybe_justified_header)
}
+32 -35
View File
@@ -20,11 +20,8 @@ use bp_header_chain::FinalityProof;
use futures::{FutureExt, Stream, StreamExt};
use std::pin::Pin;
/// Finality proofs container. Ordered by target header number.
pub type FinalityProofs<P> =
Vec<(<P as FinalityPipeline>::Number, <P as FinalityPipeline>::FinalityProof)>;
/// Source finality proofs stream that may be restarted.
#[derive(Default)]
pub struct FinalityProofsStream<P: FinalityPipeline, SC: SourceClientBase<P>> {
/// The underlying stream.
stream: Option<Pin<Box<SC::FinalityProofsStream>>>,
@@ -75,16 +72,16 @@ impl<P: FinalityPipeline, SC: SourceClientBase<P>> FinalityProofsStream<P, SC> {
/// Source finality proofs buffer.
pub struct FinalityProofsBuf<P: FinalityPipeline> {
/// Proofs buffer.
buf: FinalityProofs<P>,
/// Proofs buffer. Ordered by target header number.
buf: Vec<P::FinalityProof>,
}
impl<P: FinalityPipeline> FinalityProofsBuf<P> {
pub fn new(buf: FinalityProofs<P>) -> Self {
pub fn new(buf: Vec<P::FinalityProof>) -> Self {
Self { buf }
}
pub fn buf(&self) -> &FinalityProofs<P> {
pub fn buf(&self) -> &Vec<P::FinalityProof> {
&self.buf
}
@@ -98,7 +95,7 @@ impl<P: FinalityPipeline> FinalityProofsBuf<P> {
last_header_number = Some(target_header_number);
proofs_count += 1;
self.buf.push((target_header_number, finality_proof));
self.buf.push(finality_proof);
}
if proofs_count != 0 {
@@ -113,15 +110,19 @@ impl<P: FinalityPipeline> FinalityProofsBuf<P> {
}
}
pub fn prune(&mut self, until_hdr_num: P::Number, buf_limit: usize) {
let kept_hdr_idx = self
/// Prune all finality proofs that target header numbers older than `first_to_keep`.
pub fn prune(&mut self, first_to_keep: P::Number, maybe_buf_limit: Option<usize>) {
let first_to_keep_idx = self
.buf
.binary_search_by_key(&until_hdr_num, |(hdr_num, _)| *hdr_num)
.binary_search_by_key(&first_to_keep, |hdr| hdr.target_header_number())
.map(|idx| idx + 1)
.unwrap_or_else(|idx| idx);
let buf_limit_idx = self.buf.len().saturating_sub(buf_limit);
let buf_limit_idx = match maybe_buf_limit {
Some(buf_limit) => self.buf.len().saturating_sub(buf_limit),
None => 0,
};
self.buf = self.buf.split_off(std::cmp::max(kept_hdr_idx, buf_limit_idx));
self.buf = self.buf.split_off(std::cmp::max(first_to_keep_idx, buf_limit_idx));
}
}
@@ -140,13 +141,13 @@ mod tests {
fn finality_proofs_buf_fill_works() {
// when stream is currently empty, nothing is changed
let mut finality_proofs_buf =
FinalityProofsBuf::<TestFinalitySyncPipeline> { buf: vec![(1, TestFinalityProof(1))] };
FinalityProofsBuf::<TestFinalitySyncPipeline> { buf: vec![TestFinalityProof(1)] };
let mut stream =
FinalityProofsStream::<TestFinalitySyncPipeline, TestSourceClient>::from_stream(
Box::pin(futures::stream::pending()),
);
finality_proofs_buf.fill(&mut stream);
assert_eq!(finality_proofs_buf.buf, vec![(1, TestFinalityProof(1))]);
assert_eq!(finality_proofs_buf.buf, vec![TestFinalityProof(1)]);
assert!(stream.stream.is_some());
// when stream has entry with target, it is added to the recent proofs container
@@ -158,10 +159,7 @@ mod tests {
),
);
finality_proofs_buf.fill(&mut stream);
assert_eq!(
finality_proofs_buf.buf,
vec![(1, TestFinalityProof(1)), (4, TestFinalityProof(4))]
);
assert_eq!(finality_proofs_buf.buf, vec![TestFinalityProof(1), TestFinalityProof(4)]);
assert!(stream.stream.is_some());
// when stream has ended, we'll need to restart it
@@ -170,21 +168,20 @@ mod tests {
Box::pin(futures::stream::empty()),
);
finality_proofs_buf.fill(&mut stream);
assert_eq!(
finality_proofs_buf.buf,
vec![(1, TestFinalityProof(1)), (4, TestFinalityProof(4))]
);
assert_eq!(finality_proofs_buf.buf, vec![TestFinalityProof(1), TestFinalityProof(4)]);
assert!(stream.stream.is_none());
}
#[test]
fn finality_proofs_buf_prune_works() {
let original_finality_proofs_buf: FinalityProofs<TestFinalitySyncPipeline> = vec![
(10, TestFinalityProof(10)),
(13, TestFinalityProof(13)),
(15, TestFinalityProof(15)),
(17, TestFinalityProof(17)),
(19, TestFinalityProof(19)),
let original_finality_proofs_buf: Vec<
<TestFinalitySyncPipeline as FinalityPipeline>::FinalityProof,
> = vec![
TestFinalityProof(10),
TestFinalityProof(13),
TestFinalityProof(15),
TestFinalityProof(17),
TestFinalityProof(19),
]
.into_iter()
.collect();
@@ -193,35 +190,35 @@ mod tests {
let mut finality_proofs_buf = FinalityProofsBuf::<TestFinalitySyncPipeline> {
buf: original_finality_proofs_buf.clone(),
};
finality_proofs_buf.prune(10, 1024);
finality_proofs_buf.prune(10, None);
assert_eq!(&original_finality_proofs_buf[1..], finality_proofs_buf.buf,);
// when there are no proof for justified header in the vec
let mut finality_proofs_buf = FinalityProofsBuf::<TestFinalitySyncPipeline> {
buf: original_finality_proofs_buf.clone(),
};
finality_proofs_buf.prune(11, 1024);
finality_proofs_buf.prune(11, None);
assert_eq!(&original_finality_proofs_buf[1..], finality_proofs_buf.buf,);
// when there are too many entries after initial prune && they also need to be pruned
let mut finality_proofs_buf = FinalityProofsBuf::<TestFinalitySyncPipeline> {
buf: original_finality_proofs_buf.clone(),
};
finality_proofs_buf.prune(10, 2);
finality_proofs_buf.prune(10, Some(2));
assert_eq!(&original_finality_proofs_buf[3..], finality_proofs_buf.buf,);
// when last entry is pruned
let mut finality_proofs_buf = FinalityProofsBuf::<TestFinalitySyncPipeline> {
buf: original_finality_proofs_buf.clone(),
};
finality_proofs_buf.prune(19, 2);
finality_proofs_buf.prune(19, Some(2));
assert_eq!(&original_finality_proofs_buf[5..], finality_proofs_buf.buf,);
// when post-last entry is pruned
let mut finality_proofs_buf = FinalityProofsBuf::<TestFinalitySyncPipeline> {
buf: original_finality_proofs_buf.clone(),
};
finality_proofs_buf.prune(20, 2);
finality_proofs_buf.prune(20, Some(2));
assert_eq!(&original_finality_proofs_buf[5..], finality_proofs_buf.buf,);
}
}
+11 -10
View File
@@ -19,6 +19,7 @@ use crate::{
SourceClient, SourceHeader, TargetClient,
};
use bp_header_chain::FinalityProof;
use std::cmp::Ordering;
/// Unjustified headers container. Ordered by header number.
@@ -120,18 +121,18 @@ impl<P: FinalitySyncPipeline> JustifiedHeaderSelector<P> {
while let (Some(finality_proof), Some(unjustified_header)) =
(maybe_finality_proof, maybe_unjustified_header)
{
match finality_proof.0.cmp(&unjustified_header.number()) {
match finality_proof.target_header_number().cmp(&unjustified_header.number()) {
Ordering::Equal => {
log::trace!(
target: "bridge",
"Managed to improve selected {} finality proof {:?} to {:?}.",
P::SOURCE_NAME,
maybe_justified_header.as_ref().map(|justified_header| justified_header.number()),
finality_proof.0
finality_proof.target_header_number()
);
return Some(JustifiedHeader {
header: unjustified_header.clone(),
proof: finality_proof.1.clone(),
proof: finality_proof.clone(),
})
},
Ordering::Less => maybe_unjustified_header = unjustified_headers_iter.next(),
@@ -160,7 +161,7 @@ mod tests {
fn select_better_recent_finality_proof_works() {
// if there are no unjustified headers, nothing is changed
let finality_proofs_buf =
FinalityProofsBuf::<TestFinalitySyncPipeline>::new(vec![(5, TestFinalityProof(5))]);
FinalityProofsBuf::<TestFinalitySyncPipeline>::new(vec![TestFinalityProof(5)]);
let justified_header =
JustifiedHeader { header: TestSourceHeader(false, 2, 2), proof: TestFinalityProof(2) };
let selector = JustifiedHeaderSelector::Regular(vec![], justified_header.clone());
@@ -179,8 +180,8 @@ mod tests {
// if there's no intersection between recent finality proofs and unjustified headers,
// nothing is changed
let finality_proofs_buf = FinalityProofsBuf::<TestFinalitySyncPipeline>::new(vec![
(1, TestFinalityProof(1)),
(4, TestFinalityProof(4)),
TestFinalityProof(1),
TestFinalityProof(4),
]);
let justified_header =
JustifiedHeader { header: TestSourceHeader(false, 2, 2), proof: TestFinalityProof(2) };
@@ -193,8 +194,8 @@ mod tests {
// if there's intersection between recent finality proofs and unjustified headers, but there
// are no proofs in this intersection, nothing is changed
let finality_proofs_buf = FinalityProofsBuf::<TestFinalitySyncPipeline>::new(vec![
(7, TestFinalityProof(7)),
(11, TestFinalityProof(11)),
TestFinalityProof(7),
TestFinalityProof(11),
]);
let justified_header =
JustifiedHeader { header: TestSourceHeader(false, 2, 2), proof: TestFinalityProof(2) };
@@ -213,8 +214,8 @@ mod tests {
// - this better (last from intersection) proof is selected;
// - 'obsolete' unjustified headers are pruned.
let finality_proofs_buf = FinalityProofsBuf::<TestFinalitySyncPipeline>::new(vec![
(7, TestFinalityProof(7)),
(9, TestFinalityProof(9)),
TestFinalityProof(7),
TestFinalityProof(9),
]);
let justified_header =
JustifiedHeader { header: TestSourceHeader(false, 2, 2), proof: TestFinalityProof(2) };
+1
View File
@@ -22,6 +22,7 @@
pub use crate::{
base::{FinalityPipeline, SourceClientBase},
finality_loop::{metrics_prefix, run, FinalitySyncParams, SourceClient, TargetClient},
finality_proofs::{FinalityProofsBuf, FinalityProofsStream},
sync_loop_metrics::SyncLoopMetrics,
};
+5 -1
View File
@@ -106,7 +106,11 @@ impl SourceHeader<TestHash, TestNumber, GrandpaConsensusLogReader<TestNumber>>
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TestFinalityProof(pub TestNumber);
impl FinalityProof<TestNumber> for TestFinalityProof {
impl FinalityProof<TestHash, TestNumber> for TestFinalityProof {
fn target_header_hash(&self) -> TestHash {
Default::default()
}
fn target_header_number(&self) -> TestNumber {
self.0
}