fix: Complete snowbridge pezpallet rebrand and critical bug fixes
- snowbridge-pezpallet-* → pezsnowbridge-pezpallet-* (201 refs) - pallet/ directories → pezpallet/ (4 locations) - Fixed pezpallet.rs self-include recursion bug - Fixed sc-chain-spec hardcoded crate name in derive macro - Reverted .pezpallet_by_name() to .pallet_by_name() (subxt API) - Added BizinikiwiConfig type alias for zombienet tests - Deleted obsolete session state files Verified: pezsnowbridge-pezpallet-*, pezpallet-staking, pezpallet-staking-async, pezframe-benchmarking-cli all pass cargo check
This commit is contained in:
@@ -0,0 +1,476 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::{
|
||||
handle_client_error, reporter::EquivocationsReporter, EquivocationDetectionPipeline,
|
||||
EquivocationReportingContext, HeaderFinalityInfo, SourceClient, TargetClient,
|
||||
};
|
||||
|
||||
use bp_header_pez_chain::{FinalityProof, FindEquivocations as FindEquivocationsT};
|
||||
use pez_finality_relay::FinalityProofsBuf;
|
||||
use futures::future::{BoxFuture, FutureExt};
|
||||
use num_traits::Saturating;
|
||||
|
||||
/// First step in the block checking state machine.
|
||||
///
|
||||
/// 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,
|
||||
}
|
||||
|
||||
impl<P: EquivocationDetectionPipeline> ReadSyncedHeaders<P> {
|
||||
pub async fn next<TC: TargetClient<P>>(
|
||||
self,
|
||||
target_client: &mut TC,
|
||||
) -> Result<ReadContext<P>, Self> {
|
||||
match target_client.synced_headers_finality_info(self.target_block_num).await {
|
||||
Ok(synced_headers) =>
|
||||
Ok(ReadContext { target_block_num: self.target_block_num, synced_headers }),
|
||||
Err(e) => {
|
||||
tracing::error!(
|
||||
target: "bridge",
|
||||
error=?e,
|
||||
source=%P::SOURCE_NAME,
|
||||
target=%P::TARGET_NAME,
|
||||
block=%self.target_block_num,
|
||||
"Could not get headers synced at block"
|
||||
);
|
||||
|
||||
// Reconnect target client in case of a connection error.
|
||||
handle_client_error(target_client, e).await;
|
||||
|
||||
Err(self)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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>>,
|
||||
}
|
||||
|
||||
impl<P: EquivocationDetectionPipeline> ReadContext<P> {
|
||||
pub async fn next<TC: TargetClient<P>>(
|
||||
self,
|
||||
target_client: &mut TC,
|
||||
) -> Result<Option<FindEquivocations<P>>, Self> {
|
||||
match EquivocationReportingContext::try_read_from_target::<TC>(
|
||||
target_client,
|
||||
self.target_block_num.saturating_sub(1.into()),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(Some(context)) => Ok(Some(FindEquivocations {
|
||||
target_block_num: self.target_block_num,
|
||||
synced_headers: self.synced_headers,
|
||||
context,
|
||||
})),
|
||||
Ok(None) => Ok(None),
|
||||
Err(e) => {
|
||||
tracing::error!(
|
||||
target: "bridge",
|
||||
error=?e,
|
||||
source=%P::SOURCE_NAME,
|
||||
target=%P::TARGET_NAME,
|
||||
block=%self.target_block_num.saturating_sub(1.into()),
|
||||
"Could not read `EquivocationReportingContext` at block",
|
||||
);
|
||||
|
||||
// Reconnect target client in case of a connection error.
|
||||
handle_client_error(target_client, e).await;
|
||||
|
||||
Err(self)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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>>,
|
||||
context: EquivocationReportingContext<P>,
|
||||
}
|
||||
|
||||
impl<P: EquivocationDetectionPipeline> FindEquivocations<P> {
|
||||
pub fn next(
|
||||
mut self,
|
||||
finality_proofs_buf: &mut FinalityProofsBuf<P>,
|
||||
) -> Vec<ReportEquivocations<P>> {
|
||||
let mut result = vec![];
|
||||
for synced_header in self.synced_headers {
|
||||
match P::EquivocationsFinder::find_equivocations(
|
||||
&self.context.synced_verification_context,
|
||||
&synced_header.finality_proof,
|
||||
finality_proofs_buf.buf().as_slice(),
|
||||
) {
|
||||
Ok(equivocations) =>
|
||||
if !equivocations.is_empty() {
|
||||
result.push(ReportEquivocations {
|
||||
source_block_hash: self.context.synced_header_hash,
|
||||
equivocations,
|
||||
})
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::error!(
|
||||
target: "bridge",
|
||||
error=?e,
|
||||
source_header=?synced_header.finality_proof.target_header_hash(),
|
||||
block=%self.target_block_num,
|
||||
"Could not search for equivocations in the finality proof \
|
||||
for source header synced at target block"
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
finality_proofs_buf.prune(synced_header.finality_proof.target_header_number(), None);
|
||||
self.context.update(synced_header);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
/// 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>,
|
||||
}
|
||||
|
||||
impl<P: EquivocationDetectionPipeline> ReportEquivocations<P> {
|
||||
pub async fn next<SC: SourceClient<P>>(
|
||||
mut self,
|
||||
source_client: &mut SC,
|
||||
reporter: &mut EquivocationsReporter<'_, P, SC>,
|
||||
) -> Result<(), Self> {
|
||||
let mut unprocessed_equivocations = vec![];
|
||||
for equivocation in self.equivocations {
|
||||
match reporter
|
||||
.submit_report(source_client, self.source_block_hash, equivocation.clone())
|
||||
.await
|
||||
{
|
||||
Ok(_) => {},
|
||||
Err(e) => {
|
||||
tracing::error!(
|
||||
target: "bridge",
|
||||
error=?e,
|
||||
source=%P::SOURCE_NAME,
|
||||
?equivocation,
|
||||
"Could not submit equivocation report"
|
||||
);
|
||||
|
||||
// Mark the equivocation as unprocessed
|
||||
unprocessed_equivocations.push(equivocation);
|
||||
// Reconnect source client in case of a connection error.
|
||||
handle_client_error(source_client, e).await;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
self.equivocations = unprocessed_equivocations;
|
||||
if !self.equivocations.is_empty() {
|
||||
return Err(self);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Block checking state machine.
|
||||
#[cfg_attr(test, derive(Debug))]
|
||||
pub enum BlockChecker<P: EquivocationDetectionPipeline> {
|
||||
ReadSyncedHeaders(ReadSyncedHeaders<P>),
|
||||
ReadContext(ReadContext<P>),
|
||||
ReportEquivocations(Vec<ReportEquivocations<P>>),
|
||||
}
|
||||
|
||||
impl<P: EquivocationDetectionPipeline> BlockChecker<P> {
|
||||
pub fn new(target_block_num: P::TargetNumber) -> Self {
|
||||
Self::ReadSyncedHeaders(ReadSyncedHeaders { target_block_num })
|
||||
}
|
||||
|
||||
pub fn run<'a, SC: SourceClient<P>, TC: TargetClient<P>>(
|
||||
self,
|
||||
source_client: &'a mut SC,
|
||||
target_client: &'a mut TC,
|
||||
finality_proofs_buf: &'a mut FinalityProofsBuf<P>,
|
||||
reporter: &'a mut EquivocationsReporter<P, SC>,
|
||||
) -> BoxFuture<'a, Result<(), Self>> {
|
||||
async move {
|
||||
match self {
|
||||
Self::ReadSyncedHeaders(state) => {
|
||||
let read_context =
|
||||
state.next(target_client).await.map_err(Self::ReadSyncedHeaders)?;
|
||||
Self::ReadContext(read_context)
|
||||
.run(source_client, target_client, finality_proofs_buf, reporter)
|
||||
.await
|
||||
},
|
||||
Self::ReadContext(state) => {
|
||||
let maybe_find_equivocations =
|
||||
state.next(target_client).await.map_err(Self::ReadContext)?;
|
||||
let find_equivocations = match maybe_find_equivocations {
|
||||
Some(find_equivocations) => find_equivocations,
|
||||
None => return Ok(()),
|
||||
};
|
||||
Self::ReportEquivocations(find_equivocations.next(finality_proofs_buf))
|
||||
.run(source_client, target_client, finality_proofs_buf, reporter)
|
||||
.await
|
||||
},
|
||||
Self::ReportEquivocations(state) => {
|
||||
let mut failures = vec![];
|
||||
for report_equivocations in state {
|
||||
if let Err(failure) =
|
||||
report_equivocations.next(source_client, reporter).await
|
||||
{
|
||||
failures.push(failure);
|
||||
}
|
||||
}
|
||||
|
||||
if !failures.is_empty() {
|
||||
return Err(Self::ReportEquivocations(failures));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
}
|
||||
.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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,310 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::{
|
||||
handle_client_error, reporter::EquivocationsReporter, EquivocationDetectionPipeline,
|
||||
SourceClient, TargetClient,
|
||||
};
|
||||
|
||||
use crate::block_checker::BlockChecker;
|
||||
use pez_finality_relay::{FinalityProofsBuf, FinalityProofsStream};
|
||||
use futures::{select_biased, FutureExt};
|
||||
use num_traits::Saturating;
|
||||
use relay_utils::{metrics::MetricsParams, FailedClient};
|
||||
use std::{future::Future, time::Duration};
|
||||
|
||||
/// Equivocations detection loop state.
|
||||
struct EquivocationDetectionLoop<
|
||||
P: EquivocationDetectionPipeline,
|
||||
SC: SourceClient<P>,
|
||||
TC: TargetClient<P>,
|
||||
> {
|
||||
source_client: SC,
|
||||
target_client: TC,
|
||||
|
||||
from_block_num: Option<P::TargetNumber>,
|
||||
until_block_num: Option<P::TargetNumber>,
|
||||
|
||||
reporter: EquivocationsReporter<'static, P, SC>,
|
||||
|
||||
finality_proofs_stream: FinalityProofsStream<P, SC>,
|
||||
finality_proofs_buf: FinalityProofsBuf<P>,
|
||||
}
|
||||
|
||||
impl<P: EquivocationDetectionPipeline, SC: SourceClient<P>, TC: TargetClient<P>>
|
||||
EquivocationDetectionLoop<P, SC, TC>
|
||||
{
|
||||
async fn ensure_finality_proofs_stream(&mut self) {
|
||||
match self.finality_proofs_stream.ensure_stream(&self.source_client).await {
|
||||
Ok(_) => {},
|
||||
Err(e) => {
|
||||
tracing::error!(
|
||||
target: "bridge",
|
||||
error=?e,
|
||||
source=%P::SOURCE_NAME,
|
||||
"Could not connect to `FinalityProofsStream`"
|
||||
);
|
||||
|
||||
// Reconnect to the source client if needed
|
||||
handle_client_error(&mut self.source_client, e).await;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async fn best_finalized_target_block_number(&mut self) -> Option<P::TargetNumber> {
|
||||
match self.target_client.best_finalized_header_number().await {
|
||||
Ok(block_num) => Some(block_num),
|
||||
Err(e) => {
|
||||
tracing::error!(
|
||||
target: "bridge",
|
||||
error=?e,
|
||||
target=%P::TARGET_NAME,
|
||||
"Could not read best finalized header number"
|
||||
);
|
||||
|
||||
// Reconnect target client and move on
|
||||
handle_client_error(&mut self.target_client, e).await;
|
||||
|
||||
None
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async fn do_run(&mut self, tick: Duration, exit_signal: impl Future<Output = ()>) {
|
||||
let exit_signal = exit_signal.fuse();
|
||||
futures::pin_mut!(exit_signal);
|
||||
|
||||
loop {
|
||||
// Make sure that we are connected to the source finality proofs stream.
|
||||
self.ensure_finality_proofs_stream().await;
|
||||
// Check the status of the pending equivocation reports
|
||||
self.reporter.process_pending_reports().await;
|
||||
|
||||
// Update blocks range.
|
||||
if let Some(block_number) = self.best_finalized_target_block_number().await {
|
||||
self.from_block_num.get_or_insert(block_number);
|
||||
self.until_block_num = Some(block_number);
|
||||
}
|
||||
let (from, until) = match (self.from_block_num, self.until_block_num) {
|
||||
(Some(from), Some(until)) => (from, until),
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
// Check the available blocks
|
||||
let mut current_block_number = from;
|
||||
while current_block_number <= until {
|
||||
self.finality_proofs_buf.fill(&mut self.finality_proofs_stream);
|
||||
let block_checker = BlockChecker::new(current_block_number);
|
||||
let _ = block_checker
|
||||
.run(
|
||||
&mut self.source_client,
|
||||
&mut self.target_client,
|
||||
&mut self.finality_proofs_buf,
|
||||
&mut self.reporter,
|
||||
)
|
||||
.await;
|
||||
current_block_number = current_block_number.saturating_add(1.into());
|
||||
}
|
||||
self.from_block_num = Some(current_block_number);
|
||||
|
||||
select_biased! {
|
||||
_ = exit_signal => return,
|
||||
_ = async_std::task::sleep(tick).fuse() => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run(
|
||||
source_client: SC,
|
||||
target_client: TC,
|
||||
tick: Duration,
|
||||
exit_signal: impl Future<Output = ()>,
|
||||
) -> Result<(), FailedClient> {
|
||||
let mut equivocation_detection_loop = Self {
|
||||
source_client,
|
||||
target_client,
|
||||
from_block_num: None,
|
||||
until_block_num: None,
|
||||
reporter: EquivocationsReporter::<P, SC>::new(),
|
||||
finality_proofs_stream: FinalityProofsStream::new(),
|
||||
finality_proofs_buf: FinalityProofsBuf::new(vec![]),
|
||||
};
|
||||
|
||||
equivocation_detection_loop.do_run(tick, exit_signal).await;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawn the equivocations detection loop.
|
||||
pub async fn run<P: EquivocationDetectionPipeline>(
|
||||
source_client: impl SourceClient<P>,
|
||||
target_client: impl TargetClient<P>,
|
||||
tick: Duration,
|
||||
metrics_params: MetricsParams,
|
||||
exit_signal: impl Future<Output = ()> + 'static + Send,
|
||||
) -> Result<(), relay_utils::Error> {
|
||||
let exit_signal = exit_signal.shared();
|
||||
relay_utils::relay_loop(source_client, target_client)
|
||||
.with_metrics(metrics_params)
|
||||
.expose()
|
||||
.await?
|
||||
.run(
|
||||
format!("{}_to_{}_EquivocationDetection", P::SOURCE_NAME, P::TARGET_NAME),
|
||||
move |source_client, target_client, _metrics| {
|
||||
EquivocationDetectionLoop::run(
|
||||
source_client,
|
||||
target_client,
|
||||
tick,
|
||||
exit_signal.clone(),
|
||||
)
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mock::*;
|
||||
use futures::{channel::mpsc::UnboundedSender, StreamExt};
|
||||
use std::{
|
||||
collections::{HashMap, VecDeque},
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
fn best_finalized_header_number(
|
||||
best_finalized_headers: &Mutex<VecDeque<Result<TestTargetNumber, TestClientError>>>,
|
||||
exit_sender: &UnboundedSender<()>,
|
||||
) -> Result<TestTargetNumber, TestClientError> {
|
||||
let mut best_finalized_headers = best_finalized_headers.lock().unwrap();
|
||||
let result = best_finalized_headers.pop_front().unwrap();
|
||||
if best_finalized_headers.is_empty() {
|
||||
exit_sender.unbounded_send(()).unwrap();
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn multiple_blocks_are_checked_correctly() {
|
||||
let best_finalized_headers = Arc::new(Mutex::new(VecDeque::from([Ok(10), Ok(12), Ok(13)])));
|
||||
let (exit_sender, exit_receiver) = futures::channel::mpsc::unbounded();
|
||||
|
||||
let source_client = TestSourceClient {
|
||||
finality_proofs: Arc::new(Mutex::new(vec![
|
||||
TestFinalityProof(2, vec!["2-1"]),
|
||||
TestFinalityProof(3, vec!["3-1", "3-2"]),
|
||||
TestFinalityProof(4, vec!["4-1"]),
|
||||
TestFinalityProof(5, vec!["5-1"]),
|
||||
TestFinalityProof(6, vec!["6-1", "6-2"]),
|
||||
TestFinalityProof(7, vec!["7-1", "7-2"]),
|
||||
])),
|
||||
..Default::default()
|
||||
};
|
||||
let reported_equivocations = source_client.reported_equivocations.clone();
|
||||
let target_client = TestTargetClient {
|
||||
best_finalized_header_number: Arc::new(move || {
|
||||
best_finalized_header_number(&best_finalized_headers, &exit_sender)
|
||||
}),
|
||||
best_synced_header_hash: HashMap::from([
|
||||
(9, Ok(Some(1))),
|
||||
(10, Ok(Some(3))),
|
||||
(11, Ok(Some(5))),
|
||||
(12, Ok(Some(6))),
|
||||
]),
|
||||
finality_verification_context: HashMap::from([
|
||||
(9, Ok(TestFinalityVerificationContext { check_equivocations: true })),
|
||||
(10, Ok(TestFinalityVerificationContext { check_equivocations: true })),
|
||||
(11, Ok(TestFinalityVerificationContext { check_equivocations: false })),
|
||||
(12, Ok(TestFinalityVerificationContext { check_equivocations: true })),
|
||||
]),
|
||||
synced_headers_finality_info: HashMap::from([
|
||||
(
|
||||
10,
|
||||
Ok(vec![new_header_finality_info(2, None), new_header_finality_info(3, None)]),
|
||||
),
|
||||
(
|
||||
11,
|
||||
Ok(vec![
|
||||
new_header_finality_info(4, None),
|
||||
new_header_finality_info(5, Some(false)),
|
||||
]),
|
||||
),
|
||||
(12, Ok(vec![new_header_finality_info(6, None)])),
|
||||
(13, Ok(vec![new_header_finality_info(7, None)])),
|
||||
]),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
assert!(run::<TestEquivocationDetectionPipeline>(
|
||||
source_client,
|
||||
target_client,
|
||||
Duration::from_secs(0),
|
||||
MetricsParams { address: None, registry: Default::default() },
|
||||
exit_receiver.into_future().map(|(_, _)| ()),
|
||||
)
|
||||
.await
|
||||
.is_ok());
|
||||
assert_eq!(
|
||||
*reported_equivocations.lock().unwrap(),
|
||||
HashMap::from([
|
||||
(1, vec!["2-1", "3-1", "3-2"]),
|
||||
(3, vec!["4-1", "5-1"]),
|
||||
(6, vec!["7-1", "7-2"])
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn blocks_following_error_are_checked_correctly() {
|
||||
let best_finalized_headers = Mutex::new(VecDeque::from([Ok(10), Ok(11)]));
|
||||
let (exit_sender, exit_receiver) = futures::channel::mpsc::unbounded();
|
||||
|
||||
let source_client = TestSourceClient {
|
||||
finality_proofs: Arc::new(Mutex::new(vec![
|
||||
TestFinalityProof(2, vec!["2-1"]),
|
||||
TestFinalityProof(3, vec!["3-1"]),
|
||||
])),
|
||||
..Default::default()
|
||||
};
|
||||
let reported_equivocations = source_client.reported_equivocations.clone();
|
||||
let target_client = TestTargetClient {
|
||||
best_finalized_header_number: Arc::new(move || {
|
||||
best_finalized_header_number(&best_finalized_headers, &exit_sender)
|
||||
}),
|
||||
best_synced_header_hash: HashMap::from([(9, Ok(Some(1))), (10, Ok(Some(2)))]),
|
||||
finality_verification_context: HashMap::from([
|
||||
(9, Ok(TestFinalityVerificationContext { check_equivocations: true })),
|
||||
(10, Ok(TestFinalityVerificationContext { check_equivocations: true })),
|
||||
]),
|
||||
synced_headers_finality_info: HashMap::from([
|
||||
(10, Err(TestClientError::NonConnection)),
|
||||
(11, Ok(vec![new_header_finality_info(3, None)])),
|
||||
]),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
assert!(run::<TestEquivocationDetectionPipeline>(
|
||||
source_client,
|
||||
target_client,
|
||||
Duration::from_secs(0),
|
||||
MetricsParams { address: None, registry: Default::default() },
|
||||
exit_receiver.into_future().map(|(_, _)| ()),
|
||||
)
|
||||
.await
|
||||
.is_ok());
|
||||
assert_eq!(*reported_equivocations.lock().unwrap(), HashMap::from([(2, vec!["3-1"]),]));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
mod block_checker;
|
||||
mod equivocation_loop;
|
||||
mod mock;
|
||||
mod reporter;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use bp_header_pez_chain::{FinalityProof, FindEquivocations};
|
||||
use pez_finality_relay::{FinalityPipeline, SourceClientBase};
|
||||
use relay_utils::{relay_loop::Client as RelayClient, MaybeConnectionError, TransactionTracker};
|
||||
use std::{fmt::Debug, time::Duration};
|
||||
|
||||
pub use equivocation_loop::run;
|
||||
|
||||
#[cfg(not(test))]
|
||||
const RECONNECT_DELAY: Duration = relay_utils::relay_loop::RECONNECT_DELAY;
|
||||
#[cfg(test)]
|
||||
const RECONNECT_DELAY: Duration = mock::TEST_RECONNECT_DELAY;
|
||||
|
||||
pub trait EquivocationDetectionPipeline: FinalityPipeline {
|
||||
/// Block number of the target chain.
|
||||
type TargetNumber: relay_utils::BlockNumberBase;
|
||||
/// The context needed for validating finality proofs.
|
||||
type FinalityVerificationContext: Debug + Send;
|
||||
/// The type of the equivocation proof.
|
||||
type EquivocationProof: Clone + Debug + Send + Sync;
|
||||
/// The equivocations finder.
|
||||
type EquivocationsFinder: FindEquivocations<
|
||||
Self::FinalityProof,
|
||||
Self::FinalityVerificationContext,
|
||||
Self::EquivocationProof,
|
||||
>;
|
||||
}
|
||||
|
||||
type HeaderFinalityInfo<P> = bp_header_pez_chain::HeaderFinalityInfo<
|
||||
<P as FinalityPipeline>::FinalityProof,
|
||||
<P as EquivocationDetectionPipeline>::FinalityVerificationContext,
|
||||
>;
|
||||
|
||||
/// Source client used in equivocation detection loop.
|
||||
#[async_trait]
|
||||
pub trait SourceClient<P: EquivocationDetectionPipeline>: SourceClientBase<P> {
|
||||
/// Transaction tracker to track submitted transactions.
|
||||
type TransactionTracker: TransactionTracker;
|
||||
|
||||
/// Report equivocation.
|
||||
async fn report_equivocation(
|
||||
&self,
|
||||
at: P::Hash,
|
||||
equivocation: P::EquivocationProof,
|
||||
) -> Result<Self::TransactionTracker, Self::Error>;
|
||||
}
|
||||
|
||||
/// Target client used in equivocation detection loop.
|
||||
#[async_trait]
|
||||
pub trait TargetClient<P: EquivocationDetectionPipeline>: RelayClient {
|
||||
/// Get the best finalized header number.
|
||||
async fn best_finalized_header_number(&self) -> Result<P::TargetNumber, Self::Error>;
|
||||
|
||||
/// Get the hash of the best source header known by the target at the provided block number.
|
||||
async fn best_synced_header_hash(
|
||||
&self,
|
||||
at: P::TargetNumber,
|
||||
) -> Result<Option<P::Hash>, Self::Error>;
|
||||
|
||||
/// Get the data stored by the target at the specified block for validating source finality
|
||||
/// proofs.
|
||||
async fn finality_verification_context(
|
||||
&self,
|
||||
at: P::TargetNumber,
|
||||
) -> Result<P::FinalityVerificationContext, Self::Error>;
|
||||
|
||||
/// Get the finality info associated to the source headers synced with the target chain at the
|
||||
/// specified block.
|
||||
async fn synced_headers_finality_info(
|
||||
&self,
|
||||
at: P::TargetNumber,
|
||||
) -> Result<Vec<HeaderFinalityInfo<P>>, Self::Error>;
|
||||
}
|
||||
|
||||
/// The context needed for finding equivocations inside finality proofs and reporting them.
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct EquivocationReportingContext<P: EquivocationDetectionPipeline> {
|
||||
pub synced_header_hash: P::Hash,
|
||||
pub synced_verification_context: P::FinalityVerificationContext,
|
||||
}
|
||||
|
||||
impl<P: EquivocationDetectionPipeline> EquivocationReportingContext<P> {
|
||||
/// Try to get the `EquivocationReportingContext` used by the target chain
|
||||
/// at the provided block.
|
||||
pub async fn try_read_from_target<TC: TargetClient<P>>(
|
||||
target_client: &TC,
|
||||
at: P::TargetNumber,
|
||||
) -> Result<Option<Self>, TC::Error> {
|
||||
let maybe_best_synced_header_hash = target_client.best_synced_header_hash(at).await?;
|
||||
Ok(match maybe_best_synced_header_hash {
|
||||
Some(best_synced_header_hash) => Some(EquivocationReportingContext {
|
||||
synced_header_hash: best_synced_header_hash,
|
||||
synced_verification_context: target_client
|
||||
.finality_verification_context(at)
|
||||
.await?,
|
||||
}),
|
||||
None => None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Update with the new context introduced by the `HeaderFinalityInfo<P>` if any.
|
||||
pub fn update(&mut self, info: HeaderFinalityInfo<P>) {
|
||||
if let Some(new_verification_context) = info.new_verification_context {
|
||||
self.synced_header_hash = info.finality_proof.target_header_hash();
|
||||
self.synced_verification_context = new_verification_context;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_client_error<C: RelayClient>(client: &mut C, e: C::Error) {
|
||||
if e.is_connection_error() {
|
||||
client.reconnect_until_success(RECONNECT_DELAY).await;
|
||||
} else {
|
||||
async_std::task::sleep(RECONNECT_DELAY).await;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use crate::{EquivocationDetectionPipeline, HeaderFinalityInfo, SourceClient, TargetClient};
|
||||
use async_trait::async_trait;
|
||||
use bp_header_pez_chain::{FinalityProof, FindEquivocations};
|
||||
use pez_finality_relay::{FinalityPipeline, SourceClientBase};
|
||||
use futures::{Stream, StreamExt};
|
||||
use relay_utils::{
|
||||
relay_loop::Client as RelayClient, HeaderId, MaybeConnectionError, TrackedTransactionStatus,
|
||||
TransactionTracker,
|
||||
};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
pin::Pin,
|
||||
sync::{Arc, Mutex},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
pub type TestSourceHashAndNumber = u64;
|
||||
pub type TestTargetNumber = u64;
|
||||
pub type TestEquivocationProof = &'static str;
|
||||
|
||||
pub const TEST_RECONNECT_DELAY: Duration = Duration::from_secs(0);
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct TestFinalityProof(pub TestSourceHashAndNumber, pub Vec<TestEquivocationProof>);
|
||||
|
||||
impl FinalityProof<TestSourceHashAndNumber, TestSourceHashAndNumber> for TestFinalityProof {
|
||||
fn target_header_hash(&self) -> TestSourceHashAndNumber {
|
||||
self.0
|
||||
}
|
||||
|
||||
fn target_header_number(&self) -> TestSourceHashAndNumber {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct TestEquivocationDetectionPipeline;
|
||||
|
||||
impl FinalityPipeline for TestEquivocationDetectionPipeline {
|
||||
const SOURCE_NAME: &'static str = "TestSource";
|
||||
const TARGET_NAME: &'static str = "TestTarget";
|
||||
|
||||
type Hash = TestSourceHashAndNumber;
|
||||
type Number = TestSourceHashAndNumber;
|
||||
type FinalityProof = TestFinalityProof;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct TestFinalityVerificationContext {
|
||||
pub check_equivocations: bool,
|
||||
}
|
||||
|
||||
pub struct TestEquivocationsFinder;
|
||||
|
||||
impl FindEquivocations<TestFinalityProof, TestFinalityVerificationContext, TestEquivocationProof>
|
||||
for TestEquivocationsFinder
|
||||
{
|
||||
type Error = ();
|
||||
|
||||
fn find_equivocations(
|
||||
verification_context: &TestFinalityVerificationContext,
|
||||
synced_proof: &TestFinalityProof,
|
||||
source_proofs: &[TestFinalityProof],
|
||||
) -> Result<Vec<TestEquivocationProof>, Self::Error> {
|
||||
if verification_context.check_equivocations {
|
||||
// Get the equivocations from the source proofs, in order to make sure
|
||||
// that they are correctly provided.
|
||||
if let Some(proof) = source_proofs.iter().find(|proof| proof.0 == synced_proof.0) {
|
||||
return Ok(proof.1.clone());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(vec![])
|
||||
}
|
||||
}
|
||||
|
||||
impl EquivocationDetectionPipeline for TestEquivocationDetectionPipeline {
|
||||
type TargetNumber = TestTargetNumber;
|
||||
type FinalityVerificationContext = TestFinalityVerificationContext;
|
||||
type EquivocationProof = TestEquivocationProof;
|
||||
type EquivocationsFinder = TestEquivocationsFinder;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum TestClientError {
|
||||
Connection,
|
||||
NonConnection,
|
||||
}
|
||||
|
||||
impl MaybeConnectionError for TestClientError {
|
||||
fn is_connection_error(&self) -> bool {
|
||||
match self {
|
||||
TestClientError::Connection => true,
|
||||
TestClientError::NonConnection => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TestSourceClient {
|
||||
pub num_reconnects: u32,
|
||||
pub finality_proofs: Arc<Mutex<Vec<TestFinalityProof>>>,
|
||||
pub reported_equivocations:
|
||||
Arc<Mutex<HashMap<TestSourceHashAndNumber, Vec<TestEquivocationProof>>>>,
|
||||
}
|
||||
|
||||
impl Default for TestSourceClient {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
num_reconnects: 0,
|
||||
finality_proofs: Arc::new(Mutex::new(vec![])),
|
||||
reported_equivocations: Arc::new(Mutex::new(Default::default())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl RelayClient for TestSourceClient {
|
||||
type Error = TestClientError;
|
||||
|
||||
async fn reconnect(&mut self) -> Result<(), Self::Error> {
|
||||
self.num_reconnects += 1;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl SourceClientBase<TestEquivocationDetectionPipeline> for TestSourceClient {
|
||||
type FinalityProofsStream = Pin<Box<dyn Stream<Item = TestFinalityProof> + 'static + Send>>;
|
||||
|
||||
async fn finality_proofs(&self) -> Result<Self::FinalityProofsStream, Self::Error> {
|
||||
let finality_proofs = std::mem::take(&mut *self.finality_proofs.lock().unwrap());
|
||||
Ok(futures::stream::iter(finality_proofs).boxed())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TestTransactionTracker(
|
||||
pub TrackedTransactionStatus<HeaderId<TestSourceHashAndNumber, TestSourceHashAndNumber>>,
|
||||
);
|
||||
|
||||
impl Default for TestTransactionTracker {
|
||||
fn default() -> TestTransactionTracker {
|
||||
TestTransactionTracker(TrackedTransactionStatus::Finalized(Default::default()))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl TransactionTracker for TestTransactionTracker {
|
||||
type HeaderId = HeaderId<TestSourceHashAndNumber, TestSourceHashAndNumber>;
|
||||
|
||||
async fn wait(
|
||||
self,
|
||||
) -> TrackedTransactionStatus<HeaderId<TestSourceHashAndNumber, TestSourceHashAndNumber>> {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl SourceClient<TestEquivocationDetectionPipeline> for TestSourceClient {
|
||||
type TransactionTracker = TestTransactionTracker;
|
||||
|
||||
async fn report_equivocation(
|
||||
&self,
|
||||
at: TestSourceHashAndNumber,
|
||||
equivocation: TestEquivocationProof,
|
||||
) -> Result<Self::TransactionTracker, Self::Error> {
|
||||
self.reported_equivocations
|
||||
.lock()
|
||||
.unwrap()
|
||||
.entry(at)
|
||||
.or_default()
|
||||
.push(equivocation);
|
||||
|
||||
Ok(TestTransactionTracker::default())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TestTargetClient {
|
||||
pub num_reconnects: u32,
|
||||
pub best_finalized_header_number:
|
||||
Arc<dyn Fn() -> Result<TestTargetNumber, TestClientError> + Send + Sync>,
|
||||
pub best_synced_header_hash:
|
||||
HashMap<TestTargetNumber, Result<Option<TestSourceHashAndNumber>, TestClientError>>,
|
||||
pub finality_verification_context:
|
||||
HashMap<TestTargetNumber, Result<TestFinalityVerificationContext, TestClientError>>,
|
||||
pub synced_headers_finality_info: HashMap<
|
||||
TestTargetNumber,
|
||||
Result<Vec<HeaderFinalityInfo<TestEquivocationDetectionPipeline>>, TestClientError>,
|
||||
>,
|
||||
}
|
||||
|
||||
impl Default for TestTargetClient {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
num_reconnects: 0,
|
||||
best_finalized_header_number: Arc::new(|| Ok(0)),
|
||||
best_synced_header_hash: Default::default(),
|
||||
finality_verification_context: Default::default(),
|
||||
synced_headers_finality_info: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl RelayClient for TestTargetClient {
|
||||
type Error = TestClientError;
|
||||
|
||||
async fn reconnect(&mut self) -> Result<(), Self::Error> {
|
||||
self.num_reconnects += 1;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl TargetClient<TestEquivocationDetectionPipeline> for TestTargetClient {
|
||||
async fn best_finalized_header_number(&self) -> Result<TestTargetNumber, Self::Error> {
|
||||
(self.best_finalized_header_number)()
|
||||
}
|
||||
|
||||
async fn best_synced_header_hash(
|
||||
&self,
|
||||
at: TestTargetNumber,
|
||||
) -> Result<Option<TestSourceHashAndNumber>, Self::Error> {
|
||||
self.best_synced_header_hash
|
||||
.get(&at)
|
||||
.unwrap_or(&Err(TestClientError::NonConnection))
|
||||
.clone()
|
||||
}
|
||||
|
||||
async fn finality_verification_context(
|
||||
&self,
|
||||
at: TestTargetNumber,
|
||||
) -> Result<TestFinalityVerificationContext, Self::Error> {
|
||||
self.finality_verification_context
|
||||
.get(&at)
|
||||
.unwrap_or(&Err(TestClientError::NonConnection))
|
||||
.clone()
|
||||
}
|
||||
|
||||
async fn synced_headers_finality_info(
|
||||
&self,
|
||||
at: TestTargetNumber,
|
||||
) -> Result<Vec<HeaderFinalityInfo<TestEquivocationDetectionPipeline>>, Self::Error> {
|
||||
self.synced_headers_finality_info
|
||||
.get(&at)
|
||||
.unwrap_or(&Err(TestClientError::NonConnection))
|
||||
.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_header_finality_info(
|
||||
source_hdr: TestSourceHashAndNumber,
|
||||
check_following_equivocations: Option<bool>,
|
||||
) -> HeaderFinalityInfo<TestEquivocationDetectionPipeline> {
|
||||
HeaderFinalityInfo::<TestEquivocationDetectionPipeline> {
|
||||
finality_proof: TestFinalityProof(source_hdr, vec![]),
|
||||
new_verification_context: check_following_equivocations.map(
|
||||
|check_following_equivocations| TestFinalityVerificationContext {
|
||||
check_equivocations: check_following_equivocations,
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Bridges Common.
|
||||
|
||||
// Parity Bridges Common is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Bridges Common is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Helper struct used for submitting finality reports and tracking their status.
|
||||
|
||||
use crate::{EquivocationDetectionPipeline, SourceClient};
|
||||
|
||||
use futures::FutureExt;
|
||||
use relay_utils::{TrackedTransactionFuture, TrackedTransactionStatus, TransactionTracker};
|
||||
use std::{
|
||||
future::poll_fn,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
pub struct EquivocationsReporter<'a, P: EquivocationDetectionPipeline, SC: SourceClient<P>> {
|
||||
pending_reports: Vec<TrackedTransactionFuture<'a, SC::TransactionTracker>>,
|
||||
}
|
||||
|
||||
impl<'a, P: EquivocationDetectionPipeline, SC: SourceClient<P>> EquivocationsReporter<'a, P, SC> {
|
||||
pub fn new() -> Self {
|
||||
Self { pending_reports: vec![] }
|
||||
}
|
||||
|
||||
/// Submit a `report_equivocation()` transaction to the source chain.
|
||||
///
|
||||
/// We store the transaction tracker for future monitoring.
|
||||
pub async fn submit_report(
|
||||
&mut self,
|
||||
source_client: &SC,
|
||||
at: P::Hash,
|
||||
equivocation: P::EquivocationProof,
|
||||
) -> Result<(), SC::Error> {
|
||||
let pending_report = source_client.report_equivocation(at, equivocation).await?;
|
||||
self.pending_reports.push(pending_report.wait());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn do_process_pending_reports(&mut self, cx: &mut Context<'_>) -> Poll<()> {
|
||||
self.pending_reports.retain_mut(|pending_report| {
|
||||
match pending_report.poll_unpin(cx) {
|
||||
Poll::Ready(tx_status) => {
|
||||
match tx_status {
|
||||
TrackedTransactionStatus::Lost => {
|
||||
tracing::error!(target: "bridge", "Equivocation report tx was lost");
|
||||
},
|
||||
TrackedTransactionStatus::Finalized(id) => {
|
||||
tracing::error!(target: "bridge", ?id, "Equivocation report tx was finalized in source block");
|
||||
},
|
||||
}
|
||||
|
||||
// The future was processed. Drop it.
|
||||
false
|
||||
},
|
||||
Poll::Pending => {
|
||||
// The future is still pending. Retain it.
|
||||
true
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
Poll::Ready(())
|
||||
}
|
||||
|
||||
/// Iterate through all the pending `report_equivocation()` transactions
|
||||
/// and log the ones that finished.
|
||||
pub async fn process_pending_reports(&mut self) {
|
||||
poll_fn(|cx| self.do_process_pending_reports(cx)).await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mock::*;
|
||||
use relay_utils::HeaderId;
|
||||
use std::sync::Mutex;
|
||||
|
||||
#[async_std::test]
|
||||
async fn process_pending_reports_works() {
|
||||
let polled_reports = Mutex::new(vec![]);
|
||||
let finished_reports = Mutex::new(vec![]);
|
||||
|
||||
let mut reporter =
|
||||
EquivocationsReporter::<TestEquivocationDetectionPipeline, TestSourceClient> {
|
||||
pending_reports: vec![
|
||||
Box::pin(async {
|
||||
polled_reports.lock().unwrap().push(1);
|
||||
finished_reports.lock().unwrap().push(1);
|
||||
TrackedTransactionStatus::Finalized(HeaderId(1, 1))
|
||||
}),
|
||||
Box::pin(async {
|
||||
polled_reports.lock().unwrap().push(2);
|
||||
finished_reports.lock().unwrap().push(2);
|
||||
TrackedTransactionStatus::Finalized(HeaderId(2, 2))
|
||||
}),
|
||||
Box::pin(async {
|
||||
polled_reports.lock().unwrap().push(3);
|
||||
std::future::pending::<()>().await;
|
||||
finished_reports.lock().unwrap().push(3);
|
||||
TrackedTransactionStatus::Finalized(HeaderId(3, 3))
|
||||
}),
|
||||
Box::pin(async {
|
||||
polled_reports.lock().unwrap().push(4);
|
||||
finished_reports.lock().unwrap().push(4);
|
||||
TrackedTransactionStatus::Finalized(HeaderId(4, 4))
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
reporter.process_pending_reports().await;
|
||||
assert_eq!(*polled_reports.lock().unwrap(), vec![1, 2, 3, 4]);
|
||||
assert_eq!(*finished_reports.lock().unwrap(), vec![1, 2, 4]);
|
||||
assert_eq!(reporter.pending_reports.len(), 1);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user