referrerpolicy=no-referrer-when-downgrade

equivocation_detector/
reporter.rs

1// Copyright 2019-2023 Parity Technologies (UK) Ltd.
2// This file is part of Parity Bridges Common.
3
4// Parity Bridges Common is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Parity Bridges Common is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Parity Bridges Common.  If not, see <http://www.gnu.org/licenses/>.
16
17//! Helper struct used for submitting finality reports and tracking their status.
18
19use crate::{EquivocationDetectionPipeline, SourceClient};
20
21use futures::FutureExt;
22use relay_utils::{TrackedTransactionFuture, TrackedTransactionStatus, TransactionTracker};
23use std::{
24	future::poll_fn,
25	task::{Context, Poll},
26};
27
28pub struct EquivocationsReporter<'a, P: EquivocationDetectionPipeline, SC: SourceClient<P>> {
29	pending_reports: Vec<TrackedTransactionFuture<'a, SC::TransactionTracker>>,
30}
31
32impl<'a, P: EquivocationDetectionPipeline, SC: SourceClient<P>> EquivocationsReporter<'a, P, SC> {
33	pub fn new() -> Self {
34		Self { pending_reports: vec![] }
35	}
36
37	/// Submit a `report_equivocation()` transaction to the source chain.
38	///
39	/// We store the transaction tracker for future monitoring.
40	pub async fn submit_report(
41		&mut self,
42		source_client: &SC,
43		at: P::Hash,
44		equivocation: P::EquivocationProof,
45	) -> Result<(), SC::Error> {
46		let pending_report = source_client.report_equivocation(at, equivocation).await?;
47		self.pending_reports.push(pending_report.wait());
48
49		Ok(())
50	}
51
52	fn do_process_pending_reports(&mut self, cx: &mut Context<'_>) -> Poll<()> {
53		self.pending_reports.retain_mut(|pending_report| {
54			match pending_report.poll_unpin(cx) {
55				Poll::Ready(tx_status) => {
56					match tx_status {
57						TrackedTransactionStatus::Lost => {
58							log::error!(target: "bridge", "Equivocation report tx was lost");
59						},
60						TrackedTransactionStatus::Finalized(id) => {
61							log::error!(target: "bridge", "Equivocation report tx was finalized in source block {id:?}");
62						},
63					}
64
65					// The future was processed. Drop it.
66					false
67				},
68				Poll::Pending => {
69					// The future is still pending. Retain it.
70					true
71				},
72			}
73		});
74
75		Poll::Ready(())
76	}
77
78	/// Iterate through all the pending `report_equivocation()` transactions
79	/// and log the ones that finished.
80	pub async fn process_pending_reports(&mut self) {
81		poll_fn(|cx| self.do_process_pending_reports(cx)).await
82	}
83}
84
85#[cfg(test)]
86mod tests {
87	use super::*;
88	use crate::mock::*;
89	use relay_utils::HeaderId;
90	use std::sync::Mutex;
91
92	#[async_std::test]
93	async fn process_pending_reports_works() {
94		let polled_reports = Mutex::new(vec![]);
95		let finished_reports = Mutex::new(vec![]);
96
97		let mut reporter =
98			EquivocationsReporter::<TestEquivocationDetectionPipeline, TestSourceClient> {
99				pending_reports: vec![
100					Box::pin(async {
101						polled_reports.lock().unwrap().push(1);
102						finished_reports.lock().unwrap().push(1);
103						TrackedTransactionStatus::Finalized(HeaderId(1, 1))
104					}),
105					Box::pin(async {
106						polled_reports.lock().unwrap().push(2);
107						finished_reports.lock().unwrap().push(2);
108						TrackedTransactionStatus::Finalized(HeaderId(2, 2))
109					}),
110					Box::pin(async {
111						polled_reports.lock().unwrap().push(3);
112						std::future::pending::<()>().await;
113						finished_reports.lock().unwrap().push(3);
114						TrackedTransactionStatus::Finalized(HeaderId(3, 3))
115					}),
116					Box::pin(async {
117						polled_reports.lock().unwrap().push(4);
118						finished_reports.lock().unwrap().push(4);
119						TrackedTransactionStatus::Finalized(HeaderId(4, 4))
120					}),
121				],
122			};
123
124		reporter.process_pending_reports().await;
125		assert_eq!(*polled_reports.lock().unwrap(), vec![1, 2, 3, 4]);
126		assert_eq!(*finished_reports.lock().unwrap(), vec![1, 2, 4]);
127		assert_eq!(reporter.pending_reports.len(), 1);
128	}
129}