1use crate::{
18 handle_client_error, reporter::EquivocationsReporter, EquivocationDetectionPipeline,
19 EquivocationReportingContext, HeaderFinalityInfo, SourceClient, TargetClient,
20};
21
22use bp_header_chain::{FinalityProof, FindEquivocations as FindEquivocationsT};
23use finality_relay::FinalityProofsBuf;
24use futures::future::{BoxFuture, FutureExt};
25use num_traits::Saturating;
26
27#[cfg_attr(test, derive(Debug, PartialEq))]
32pub struct ReadSyncedHeaders<P: EquivocationDetectionPipeline> {
33 pub target_block_num: P::TargetNumber,
34}
35
36impl<P: EquivocationDetectionPipeline> ReadSyncedHeaders<P> {
37 pub async fn next<TC: TargetClient<P>>(
38 self,
39 target_client: &mut TC,
40 ) -> Result<ReadContext<P>, Self> {
41 match target_client.synced_headers_finality_info(self.target_block_num).await {
42 Ok(synced_headers) =>
43 Ok(ReadContext { target_block_num: self.target_block_num, synced_headers }),
44 Err(e) => {
45 log::error!(
46 target: "bridge",
47 "Could not get {} headers synced to {} at block {}: {e:?}",
48 P::SOURCE_NAME,
49 P::TARGET_NAME,
50 self.target_block_num
51 );
52
53 handle_client_error(target_client, e).await;
55
56 Err(self)
57 },
58 }
59 }
60}
61
62#[cfg_attr(test, derive(Debug))]
66pub struct ReadContext<P: EquivocationDetectionPipeline> {
67 target_block_num: P::TargetNumber,
68 synced_headers: Vec<HeaderFinalityInfo<P>>,
69}
70
71impl<P: EquivocationDetectionPipeline> ReadContext<P> {
72 pub async fn next<TC: TargetClient<P>>(
73 self,
74 target_client: &mut TC,
75 ) -> Result<Option<FindEquivocations<P>>, Self> {
76 match EquivocationReportingContext::try_read_from_target::<TC>(
77 target_client,
78 self.target_block_num.saturating_sub(1.into()),
79 )
80 .await
81 {
82 Ok(Some(context)) => Ok(Some(FindEquivocations {
83 target_block_num: self.target_block_num,
84 synced_headers: self.synced_headers,
85 context,
86 })),
87 Ok(None) => Ok(None),
88 Err(e) => {
89 log::error!(
90 target: "bridge",
91 "Could not read {} `EquivocationReportingContext` from {} at block {}: {e:?}",
92 P::SOURCE_NAME,
93 P::TARGET_NAME,
94 self.target_block_num.saturating_sub(1.into()),
95 );
96
97 handle_client_error(target_client, e).await;
99
100 Err(self)
101 },
102 }
103 }
104}
105
106#[cfg_attr(test, derive(Debug))]
110pub struct FindEquivocations<P: EquivocationDetectionPipeline> {
111 target_block_num: P::TargetNumber,
112 synced_headers: Vec<HeaderFinalityInfo<P>>,
113 context: EquivocationReportingContext<P>,
114}
115
116impl<P: EquivocationDetectionPipeline> FindEquivocations<P> {
117 pub fn next(
118 mut self,
119 finality_proofs_buf: &mut FinalityProofsBuf<P>,
120 ) -> Vec<ReportEquivocations<P>> {
121 let mut result = vec![];
122 for synced_header in self.synced_headers {
123 match P::EquivocationsFinder::find_equivocations(
124 &self.context.synced_verification_context,
125 &synced_header.finality_proof,
126 finality_proofs_buf.buf().as_slice(),
127 ) {
128 Ok(equivocations) =>
129 if !equivocations.is_empty() {
130 result.push(ReportEquivocations {
131 source_block_hash: self.context.synced_header_hash,
132 equivocations,
133 })
134 },
135 Err(e) => {
136 log::error!(
137 target: "bridge",
138 "Could not search for equivocations in the finality proof \
139 for source header {:?} synced at target block {}: {e:?}",
140 synced_header.finality_proof.target_header_hash(),
141 self.target_block_num
142 );
143 },
144 };
145
146 finality_proofs_buf.prune(synced_header.finality_proof.target_header_number(), None);
147 self.context.update(synced_header);
148 }
149
150 result
151 }
152}
153
154#[cfg_attr(test, derive(Debug))]
158pub struct ReportEquivocations<P: EquivocationDetectionPipeline> {
159 source_block_hash: P::Hash,
160 equivocations: Vec<P::EquivocationProof>,
161}
162
163impl<P: EquivocationDetectionPipeline> ReportEquivocations<P> {
164 pub async fn next<SC: SourceClient<P>>(
165 mut self,
166 source_client: &mut SC,
167 reporter: &mut EquivocationsReporter<'_, P, SC>,
168 ) -> Result<(), Self> {
169 let mut unprocessed_equivocations = vec![];
170 for equivocation in self.equivocations {
171 match reporter
172 .submit_report(source_client, self.source_block_hash, equivocation.clone())
173 .await
174 {
175 Ok(_) => {},
176 Err(e) => {
177 log::error!(
178 target: "bridge",
179 "Could not submit equivocation report to {} for {equivocation:?}: {e:?}",
180 P::SOURCE_NAME,
181 );
182
183 unprocessed_equivocations.push(equivocation);
185 handle_client_error(source_client, e).await;
187 },
188 }
189 }
190
191 self.equivocations = unprocessed_equivocations;
192 if !self.equivocations.is_empty() {
193 return Err(self)
194 }
195
196 Ok(())
197 }
198}
199
200#[cfg_attr(test, derive(Debug))]
202pub enum BlockChecker<P: EquivocationDetectionPipeline> {
203 ReadSyncedHeaders(ReadSyncedHeaders<P>),
204 ReadContext(ReadContext<P>),
205 ReportEquivocations(Vec<ReportEquivocations<P>>),
206}
207
208impl<P: EquivocationDetectionPipeline> BlockChecker<P> {
209 pub fn new(target_block_num: P::TargetNumber) -> Self {
210 Self::ReadSyncedHeaders(ReadSyncedHeaders { target_block_num })
211 }
212
213 pub fn run<'a, SC: SourceClient<P>, TC: TargetClient<P>>(
214 self,
215 source_client: &'a mut SC,
216 target_client: &'a mut TC,
217 finality_proofs_buf: &'a mut FinalityProofsBuf<P>,
218 reporter: &'a mut EquivocationsReporter<P, SC>,
219 ) -> BoxFuture<'a, Result<(), Self>> {
220 async move {
221 match self {
222 Self::ReadSyncedHeaders(state) => {
223 let read_context =
224 state.next(target_client).await.map_err(Self::ReadSyncedHeaders)?;
225 Self::ReadContext(read_context)
226 .run(source_client, target_client, finality_proofs_buf, reporter)
227 .await
228 },
229 Self::ReadContext(state) => {
230 let maybe_find_equivocations =
231 state.next(target_client).await.map_err(Self::ReadContext)?;
232 let find_equivocations = match maybe_find_equivocations {
233 Some(find_equivocations) => find_equivocations,
234 None => return Ok(()),
235 };
236 Self::ReportEquivocations(find_equivocations.next(finality_proofs_buf))
237 .run(source_client, target_client, finality_proofs_buf, reporter)
238 .await
239 },
240 Self::ReportEquivocations(state) => {
241 let mut failures = vec![];
242 for report_equivocations in state {
243 if let Err(failure) =
244 report_equivocations.next(source_client, reporter).await
245 {
246 failures.push(failure);
247 }
248 }
249
250 if !failures.is_empty() {
251 return Err(Self::ReportEquivocations(failures))
252 }
253
254 Ok(())
255 },
256 }
257 }
258 .boxed()
259 }
260}
261
262#[cfg(test)]
263mod tests {
264 use super::*;
265 use crate::mock::*;
266 use std::collections::HashMap;
267
268 impl PartialEq for ReadContext<TestEquivocationDetectionPipeline> {
269 fn eq(&self, other: &Self) -> bool {
270 self.target_block_num == other.target_block_num &&
271 self.synced_headers == other.synced_headers
272 }
273 }
274
275 impl PartialEq for FindEquivocations<TestEquivocationDetectionPipeline> {
276 fn eq(&self, other: &Self) -> bool {
277 self.target_block_num == other.target_block_num &&
278 self.synced_headers == other.synced_headers &&
279 self.context == other.context
280 }
281 }
282
283 impl PartialEq for ReportEquivocations<TestEquivocationDetectionPipeline> {
284 fn eq(&self, other: &Self) -> bool {
285 self.source_block_hash == other.source_block_hash &&
286 self.equivocations == other.equivocations
287 }
288 }
289
290 impl PartialEq for BlockChecker<TestEquivocationDetectionPipeline> {
291 fn eq(&self, _other: &Self) -> bool {
292 matches!(self, _other)
293 }
294 }
295
296 #[async_std::test]
297 async fn block_checker_works() {
298 let mut source_client = TestSourceClient { ..Default::default() };
299 let mut target_client = TestTargetClient {
300 best_synced_header_hash: HashMap::from([(9, Ok(Some(5)))]),
301 finality_verification_context: HashMap::from([(
302 9,
303 Ok(TestFinalityVerificationContext { check_equivocations: true }),
304 )]),
305 synced_headers_finality_info: HashMap::from([(
306 10,
307 Ok(vec![
308 new_header_finality_info(6, None),
309 new_header_finality_info(7, Some(false)),
310 new_header_finality_info(8, None),
311 new_header_finality_info(9, Some(true)),
312 new_header_finality_info(10, None),
313 new_header_finality_info(11, None),
314 new_header_finality_info(12, None),
315 ]),
316 )]),
317 ..Default::default()
318 };
319 let mut reporter =
320 EquivocationsReporter::<TestEquivocationDetectionPipeline, TestSourceClient>::new();
321
322 let block_checker = BlockChecker::new(10);
323 assert!(block_checker
324 .run(
325 &mut source_client,
326 &mut target_client,
327 &mut FinalityProofsBuf::new(vec![
328 TestFinalityProof(6, vec!["6-1"]),
329 TestFinalityProof(7, vec![]),
330 TestFinalityProof(8, vec!["8-1"]),
331 TestFinalityProof(9, vec!["9-1"]),
332 TestFinalityProof(10, vec![]),
333 TestFinalityProof(11, vec!["11-1", "11-2"]),
334 TestFinalityProof(12, vec!["12-1"])
335 ]),
336 &mut reporter
337 )
338 .await
339 .is_ok());
340 assert_eq!(
341 *source_client.reported_equivocations.lock().unwrap(),
342 HashMap::from([(5, vec!["6-1"]), (9, vec!["11-1", "11-2", "12-1"])])
343 );
344 }
345
346 #[async_std::test]
347 async fn block_checker_works_with_empty_context() {
348 let mut target_client = TestTargetClient {
349 best_synced_header_hash: HashMap::from([(9, Ok(None))]),
350 finality_verification_context: HashMap::from([(
351 9,
352 Ok(TestFinalityVerificationContext { check_equivocations: true }),
353 )]),
354 synced_headers_finality_info: HashMap::from([(
355 10,
356 Ok(vec![new_header_finality_info(6, None)]),
357 )]),
358 ..Default::default()
359 };
360 let mut source_client = TestSourceClient { ..Default::default() };
361 let mut reporter =
362 EquivocationsReporter::<TestEquivocationDetectionPipeline, TestSourceClient>::new();
363
364 let block_checker = BlockChecker::new(10);
365 assert!(block_checker
366 .run(
367 &mut source_client,
368 &mut target_client,
369 &mut FinalityProofsBuf::new(vec![TestFinalityProof(6, vec!["6-1"])]),
370 &mut reporter
371 )
372 .await
373 .is_ok());
374 assert_eq!(*source_client.reported_equivocations.lock().unwrap(), HashMap::default());
375 }
376
377 #[async_std::test]
378 async fn read_synced_headers_handles_errors() {
379 let mut target_client = TestTargetClient {
380 synced_headers_finality_info: HashMap::from([
381 (10, Err(TestClientError::NonConnection)),
382 (11, Err(TestClientError::Connection)),
383 ]),
384 ..Default::default()
385 };
386 let mut source_client = TestSourceClient { ..Default::default() };
387 let mut reporter =
388 EquivocationsReporter::<TestEquivocationDetectionPipeline, TestSourceClient>::new();
389
390 let block_checker = BlockChecker::new(10);
392 assert_eq!(
393 block_checker
394 .run(
395 &mut source_client,
396 &mut target_client,
397 &mut FinalityProofsBuf::new(vec![]),
398 &mut reporter
399 )
400 .await,
401 Err(BlockChecker::ReadSyncedHeaders(ReadSyncedHeaders { target_block_num: 10 }))
402 );
403 assert_eq!(target_client.num_reconnects, 0);
404
405 let block_checker = BlockChecker::new(11);
407 assert_eq!(
408 block_checker
409 .run(
410 &mut source_client,
411 &mut target_client,
412 &mut FinalityProofsBuf::new(vec![]),
413 &mut reporter
414 )
415 .await,
416 Err(BlockChecker::ReadSyncedHeaders(ReadSyncedHeaders { target_block_num: 11 }))
417 );
418 assert_eq!(target_client.num_reconnects, 1);
419 }
420
421 #[async_std::test]
422 async fn read_context_handles_errors() {
423 let mut target_client = TestTargetClient {
424 synced_headers_finality_info: HashMap::from([(10, Ok(vec![])), (11, Ok(vec![]))]),
425 best_synced_header_hash: HashMap::from([
426 (9, Err(TestClientError::NonConnection)),
427 (10, Err(TestClientError::Connection)),
428 ]),
429 ..Default::default()
430 };
431 let mut source_client = TestSourceClient { ..Default::default() };
432 let mut reporter =
433 EquivocationsReporter::<TestEquivocationDetectionPipeline, TestSourceClient>::new();
434
435 let block_checker = BlockChecker::new(10);
437 assert_eq!(
438 block_checker
439 .run(
440 &mut source_client,
441 &mut target_client,
442 &mut FinalityProofsBuf::new(vec![]),
443 &mut reporter
444 )
445 .await,
446 Err(BlockChecker::ReadContext(ReadContext {
447 target_block_num: 10,
448 synced_headers: vec![]
449 }))
450 );
451 assert_eq!(target_client.num_reconnects, 0);
452
453 let block_checker = BlockChecker::new(11);
455 assert_eq!(
456 block_checker
457 .run(
458 &mut source_client,
459 &mut target_client,
460 &mut FinalityProofsBuf::new(vec![]),
461 &mut reporter
462 )
463 .await,
464 Err(BlockChecker::ReadContext(ReadContext {
465 target_block_num: 11,
466 synced_headers: vec![]
467 }))
468 );
469 assert_eq!(target_client.num_reconnects, 1);
470 }
471}