Add unit tests for the equivocation detection loop (#2571)

* Add unit tests for the equivocation detection loop

* clippy

* use std::future::pending()
This commit is contained in:
Serban Iorga
2023-09-18 11:51:54 +03:00
committed by Bastian Köcher
parent 655a5055cc
commit 4cd9e2fe79
10 changed files with 716 additions and 29 deletions
@@ -28,6 +28,7 @@ use num_traits::Saturating;
///
/// Getting the finality info associated to the source headers synced with the target chain
/// at the specified block.
#[cfg_attr(test, derive(Debug, PartialEq))]
pub struct ReadSyncedHeaders<P: EquivocationDetectionPipeline> {
pub target_block_num: P::TargetNumber,
}
@@ -61,6 +62,7 @@ impl<P: EquivocationDetectionPipeline> ReadSyncedHeaders<P> {
/// Second step in the block checking state machine.
///
/// Reading the equivocation reporting context from the target chain.
#[cfg_attr(test, derive(Debug))]
pub struct ReadContext<P: EquivocationDetectionPipeline> {
target_block_num: P::TargetNumber,
synced_headers: Vec<HeaderFinalityInfo<P>>,
@@ -104,6 +106,7 @@ impl<P: EquivocationDetectionPipeline> ReadContext<P> {
/// Third step in the block checking state machine.
///
/// Searching for equivocations in the source headers synced with the target chain.
#[cfg_attr(test, derive(Debug))]
pub struct FindEquivocations<P: EquivocationDetectionPipeline> {
target_block_num: P::TargetNumber,
synced_headers: Vec<HeaderFinalityInfo<P>>,
@@ -122,10 +125,13 @@ impl<P: EquivocationDetectionPipeline> FindEquivocations<P> {
&synced_header.finality_proof,
finality_proofs_buf.buf().as_slice(),
) {
Ok(equivocations) => result.push(ReportEquivocations {
source_block_hash: self.context.synced_header_hash,
equivocations,
}),
Ok(equivocations) =>
if !equivocations.is_empty() {
result.push(ReportEquivocations {
source_block_hash: self.context.synced_header_hash,
equivocations,
})
},
Err(e) => {
log::error!(
target: "bridge",
@@ -148,6 +154,7 @@ impl<P: EquivocationDetectionPipeline> FindEquivocations<P> {
/// Fourth step in the block checking state machine.
///
/// Reporting the detected equivocations (if any).
#[cfg_attr(test, derive(Debug))]
pub struct ReportEquivocations<P: EquivocationDetectionPipeline> {
source_block_hash: P::Hash,
equivocations: Vec<P::EquivocationProof>,
@@ -157,7 +164,7 @@ impl<P: EquivocationDetectionPipeline> ReportEquivocations<P> {
pub async fn next<SC: SourceClient<P>>(
mut self,
source_client: &mut SC,
reporter: &mut EquivocationsReporter<P, SC>,
reporter: &mut EquivocationsReporter<'_, P, SC>,
) -> Result<(), Self> {
let mut unprocessed_equivocations = vec![];
for equivocation in self.equivocations {
@@ -191,6 +198,7 @@ impl<P: EquivocationDetectionPipeline> ReportEquivocations<P> {
}
/// Block checking state machine.
#[cfg_attr(test, derive(Debug))]
pub enum BlockChecker<P: EquivocationDetectionPipeline> {
ReadSyncedHeaders(ReadSyncedHeaders<P>),
ReadContext(ReadContext<P>),
@@ -250,3 +258,214 @@ impl<P: EquivocationDetectionPipeline> BlockChecker<P> {
.boxed()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mock::*;
use std::collections::HashMap;
impl PartialEq for ReadContext<TestEquivocationDetectionPipeline> {
fn eq(&self, other: &Self) -> bool {
self.target_block_num == other.target_block_num &&
self.synced_headers == other.synced_headers
}
}
impl PartialEq for FindEquivocations<TestEquivocationDetectionPipeline> {
fn eq(&self, other: &Self) -> bool {
self.target_block_num == other.target_block_num &&
self.synced_headers == other.synced_headers &&
self.context == other.context
}
}
impl PartialEq for ReportEquivocations<TestEquivocationDetectionPipeline> {
fn eq(&self, other: &Self) -> bool {
self.source_block_hash == other.source_block_hash &&
self.equivocations == other.equivocations
}
}
impl PartialEq for BlockChecker<TestEquivocationDetectionPipeline> {
fn eq(&self, _other: &Self) -> bool {
matches!(self, _other)
}
}
#[async_std::test]
async fn block_checker_works() {
let mut source_client = TestSourceClient { ..Default::default() };
let mut target_client = TestTargetClient {
best_synced_header_hash: HashMap::from([(9, Ok(Some(5)))]),
finality_verification_context: HashMap::from([(
9,
Ok(TestFinalityVerificationContext { check_equivocations: true }),
)]),
synced_headers_finality_info: HashMap::from([(
10,
Ok(vec![
new_header_finality_info(6, None),
new_header_finality_info(7, Some(false)),
new_header_finality_info(8, None),
new_header_finality_info(9, Some(true)),
new_header_finality_info(10, None),
new_header_finality_info(11, None),
new_header_finality_info(12, None),
]),
)]),
..Default::default()
};
let mut reporter =
EquivocationsReporter::<TestEquivocationDetectionPipeline, TestSourceClient>::new();
let block_checker = BlockChecker::new(10);
assert!(block_checker
.run(
&mut source_client,
&mut target_client,
&mut FinalityProofsBuf::new(vec![
TestFinalityProof(6, vec!["6-1"]),
TestFinalityProof(7, vec![]),
TestFinalityProof(8, vec!["8-1"]),
TestFinalityProof(9, vec!["9-1"]),
TestFinalityProof(10, vec![]),
TestFinalityProof(11, vec!["11-1", "11-2"]),
TestFinalityProof(12, vec!["12-1"])
]),
&mut reporter
)
.await
.is_ok());
assert_eq!(
*source_client.reported_equivocations.lock().unwrap(),
HashMap::from([(5, vec!["6-1"]), (9, vec!["11-1", "11-2", "12-1"])])
);
}
#[async_std::test]
async fn block_checker_works_with_empty_context() {
let mut target_client = TestTargetClient {
best_synced_header_hash: HashMap::from([(9, Ok(None))]),
finality_verification_context: HashMap::from([(
9,
Ok(TestFinalityVerificationContext { check_equivocations: true }),
)]),
synced_headers_finality_info: HashMap::from([(
10,
Ok(vec![new_header_finality_info(6, None)]),
)]),
..Default::default()
};
let mut source_client = TestSourceClient { ..Default::default() };
let mut reporter =
EquivocationsReporter::<TestEquivocationDetectionPipeline, TestSourceClient>::new();
let block_checker = BlockChecker::new(10);
assert!(block_checker
.run(
&mut source_client,
&mut target_client,
&mut FinalityProofsBuf::new(vec![TestFinalityProof(6, vec!["6-1"])]),
&mut reporter
)
.await
.is_ok());
assert_eq!(*source_client.reported_equivocations.lock().unwrap(), HashMap::default());
}
#[async_std::test]
async fn read_synced_headers_handles_errors() {
let mut target_client = TestTargetClient {
synced_headers_finality_info: HashMap::from([
(10, Err(TestClientError::NonConnection)),
(11, Err(TestClientError::Connection)),
]),
..Default::default()
};
let mut source_client = TestSourceClient { ..Default::default() };
let mut reporter =
EquivocationsReporter::<TestEquivocationDetectionPipeline, TestSourceClient>::new();
// NonConnection error
let block_checker = BlockChecker::new(10);
assert_eq!(
block_checker
.run(
&mut source_client,
&mut target_client,
&mut FinalityProofsBuf::new(vec![]),
&mut reporter
)
.await,
Err(BlockChecker::ReadSyncedHeaders(ReadSyncedHeaders { target_block_num: 10 }))
);
assert_eq!(target_client.num_reconnects, 0);
// Connection error
let block_checker = BlockChecker::new(11);
assert_eq!(
block_checker
.run(
&mut source_client,
&mut target_client,
&mut FinalityProofsBuf::new(vec![]),
&mut reporter
)
.await,
Err(BlockChecker::ReadSyncedHeaders(ReadSyncedHeaders { target_block_num: 11 }))
);
assert_eq!(target_client.num_reconnects, 1);
}
#[async_std::test]
async fn read_context_handles_errors() {
let mut target_client = TestTargetClient {
synced_headers_finality_info: HashMap::from([(10, Ok(vec![])), (11, Ok(vec![]))]),
best_synced_header_hash: HashMap::from([
(9, Err(TestClientError::NonConnection)),
(10, Err(TestClientError::Connection)),
]),
..Default::default()
};
let mut source_client = TestSourceClient { ..Default::default() };
let mut reporter =
EquivocationsReporter::<TestEquivocationDetectionPipeline, TestSourceClient>::new();
// NonConnection error
let block_checker = BlockChecker::new(10);
assert_eq!(
block_checker
.run(
&mut source_client,
&mut target_client,
&mut FinalityProofsBuf::new(vec![]),
&mut reporter
)
.await,
Err(BlockChecker::ReadContext(ReadContext {
target_block_num: 10,
synced_headers: vec![]
}))
);
assert_eq!(target_client.num_reconnects, 0);
// Connection error
let block_checker = BlockChecker::new(11);
assert_eq!(
block_checker
.run(
&mut source_client,
&mut target_client,
&mut FinalityProofsBuf::new(vec![]),
&mut reporter
)
.await,
Err(BlockChecker::ReadContext(ReadContext {
target_block_num: 11,
synced_headers: vec![]
}))
);
assert_eq!(target_client.num_reconnects, 1);
}
}