From 9c63be544927fe7c2e06d97562a43c3762645bec Mon Sep 17 00:00:00 2001 From: timorl Date: Fri, 30 Dec 2022 16:02:01 +0100 Subject: [PATCH 1/3] Add justifications and validator implementations --- finality-aleph/src/nodes/mod.rs | 63 +------ finality-aleph/src/session.rs | 11 +- finality-aleph/src/sync/mod.rs | 5 +- .../src/sync/substrate/justification.rs | 171 ++++++++++++++++++ .../sync/{substrate.rs => substrate/mod.rs} | 6 + 5 files changed, 195 insertions(+), 61 deletions(-) create mode 100644 finality-aleph/src/sync/substrate/justification.rs rename finality-aleph/src/sync/{substrate.rs => substrate/mod.rs} (85%) diff --git a/finality-aleph/src/nodes/mod.rs b/finality-aleph/src/nodes/mod.rs index 91e063284d..9532d3f28d 100644 --- a/finality-aleph/src/nodes/mod.rs +++ b/finality-aleph/src/nodes/mod.rs @@ -3,30 +3,23 @@ mod validator_node; use std::{future::Future, sync::Arc}; -use aleph_primitives::{AuthorityId, SessionAuthorityData}; -use codec::Encode; -use log::warn; pub use nonvalidator_node::run_nonvalidator_node; use sc_client_api::Backend; use sc_network::NetworkService; use sc_network_common::ExHashT; -use sp_runtime::{ - traits::{Block, Header, NumberFor}, - RuntimeAppPublic, -}; +use sp_runtime::traits::{Block, Header, NumberFor}; pub use validator_node::run_validator_node; use crate::{ - crypto::AuthorityVerifier, finalization::AlephFinalizer, justification::{ - AlephJustification, JustificationHandler, JustificationRequestSchedulerImpl, SessionInfo, - SessionInfoProvider, Verifier, + JustificationHandler, JustificationRequestSchedulerImpl, SessionInfo, SessionInfoProvider, }, last_block_of_session, mpsc, mpsc::UnboundedSender, session_id_from_block_num, session_map::ReadOnlySessionMap, + sync::SessionVerifier, BlockchainBackend, JustificationNotification, Metrics, MillisecsPerBlock, SessionPeriod, }; @@ -38,52 +31,6 @@ pub mod testing { /// Max amount of tries we can not update a finalized block number before we will clear requests queue const MAX_ATTEMPTS: u32 = 5; -struct JustificationVerifier { - authority_verifier: AuthorityVerifier, - emergency_signer: Option, -} - -impl From for JustificationVerifier { - fn from(authority_data: SessionAuthorityData) -> Self { - JustificationVerifier { - authority_verifier: AuthorityVerifier::new(authority_data.authorities().to_vec()), - emergency_signer: authority_data.emergency_finalizer().clone(), - } - } -} - -impl Verifier for JustificationVerifier { - fn verify(&self, justification: &AlephJustification, hash: B::Hash) -> bool { - use AlephJustification::*; - let encoded_hash = hash.encode(); - match justification { - CommitteeMultisignature(multisignature) => match self - .authority_verifier - .is_complete(&encoded_hash, multisignature) - { - true => true, - false => { - warn!(target: "aleph-justification", "Bad multisignature for block hash #{:?} {:?}", hash, multisignature); - false - } - }, - EmergencySignature(signature) => match &self.emergency_signer { - Some(emergency_signer) => match emergency_signer.verify(&encoded_hash, signature) { - true => true, - false => { - warn!(target: "aleph-justification", "Bad emergency signature for block hash #{:?} {:?}", hash, signature); - false - } - }, - None => { - warn!(target: "aleph-justification", "Received emergency signature for block with hash #{:?}, which has no emergency signer defined.", hash); - false - } - }, - } - } -} - struct JustificationParams { pub network: Arc>, pub client: Arc, @@ -110,8 +57,8 @@ impl SessionInfoProviderImpl { } #[async_trait::async_trait] -impl SessionInfoProvider for SessionInfoProviderImpl { - async fn for_block_num(&self, number: NumberFor) -> SessionInfo { +impl SessionInfoProvider for SessionInfoProviderImpl { + async fn for_block_num(&self, number: NumberFor) -> SessionInfo { let current_session = session_id_from_block_num::(number, self.session_period); let last_block_height = last_block_of_session::(current_session, self.session_period); let verifier = self diff --git a/finality-aleph/src/session.rs b/finality-aleph/src/session.rs index 3e7cde9ad2..699f595dcd 100644 --- a/finality-aleph/src/session.rs +++ b/finality-aleph/src/session.rs @@ -1,5 +1,5 @@ use codec::{Decode, Encode}; -use sp_runtime::{traits::Block, SaturatedConversion}; +use sp_runtime::traits::{Block, UniqueSaturatedInto}; use crate::NumberFor; @@ -40,8 +40,15 @@ pub fn last_block_of_session( ((session_id.0 + 1) * period.0 - 1).into() } +pub fn session_id_from_num>( + num: N, + period: SessionPeriod, +) -> SessionId { + SessionId(num.unique_saturated_into() / period.0) +} + pub fn session_id_from_block_num(num: NumberFor, period: SessionPeriod) -> SessionId { - SessionId(num.saturated_into::() / period.0) + session_id_from_num(num, period) } #[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash, Ord, PartialOrd, Encode, Decode)] diff --git a/finality-aleph/src/sync/mod.rs b/finality-aleph/src/sync/mod.rs index 9907ec1ca9..fcb452fa2f 100644 --- a/finality-aleph/src/sync/mod.rs +++ b/finality-aleph/src/sync/mod.rs @@ -6,6 +6,8 @@ use std::{ mod substrate; mod ticker; +pub use substrate::SessionVerifier; + /// The identifier of a block, the least amount of knowledge we can have about a block. pub trait BlockIdentifier: Clone + Hash + Debug + Eq { /// The block number, useful when reasoning about hopeless forks. @@ -42,12 +44,13 @@ pub trait Justification: Clone { } /// A verifier of justifications. +#[async_trait::async_trait] pub trait Verifier { type Error: Display; /// Verifies the raw justification and returns a full justification if successful, otherwise an /// error. - fn verify(&self, justification: J::Unverified) -> Result; + async fn verify(&self, justification: J::Unverified) -> Result; } /// A facility for finalizing blocks using justifications. diff --git a/finality-aleph/src/sync/substrate/justification.rs b/finality-aleph/src/sync/substrate/justification.rs new file mode 100644 index 0000000000..5208d74a02 --- /dev/null +++ b/finality-aleph/src/sync/substrate/justification.rs @@ -0,0 +1,171 @@ +use std::fmt::{Display, Error as FmtError, Formatter}; + +use aleph_primitives::{BlockNumber, SessionAuthorityData}; +use codec::Encode; +use log::warn; +use sp_runtime::{ + traits::{Block, Header as SubstrateHeader}, + RuntimeAppPublic, +}; + +use crate::{ + crypto::AuthorityVerifier, + justification::{AlephJustification, Verifier as LegacyVerifier}, + session::session_id_from_num, + session_map::ReadOnlySessionMap, + sync::{Justification as JustificationT, Verifier}, + AuthorityId, SessionPeriod, +}; + +/// A justification, including the related header. +#[derive(Clone)] +pub struct Justification> { + header: H, + raw_justification: AlephJustification, +} + +impl> JustificationT for Justification { + type Header = H; + type Unverified = Self; + + fn header(&self) -> &Self::Header { + &self.header + } + + fn into_unverified(self) -> Self::Unverified { + self + } +} + +/// A justification verifier within a single session. +pub struct SessionVerifier { + authority_verifier: AuthorityVerifier, + emergency_signer: Option, +} + +impl From for SessionVerifier { + fn from(authority_data: SessionAuthorityData) -> Self { + SessionVerifier { + authority_verifier: AuthorityVerifier::new(authority_data.authorities().to_vec()), + emergency_signer: authority_data.emergency_finalizer().clone(), + } + } +} + +/// Ways in which a justification can be wrong. +pub enum SessionVerificationError { + BadMultisignature, + BadEmergencySignature, + NoEmergencySigner, +} + +impl Display for SessionVerificationError { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { + use SessionVerificationError::*; + match self { + BadMultisignature => write!(f, "bad multisignature"), + BadEmergencySignature => write!(f, "bad emergency signature"), + NoEmergencySigner => write!(f, "no emergency signer defined"), + } + } +} + +impl SessionVerifier { + pub fn verify_bytes( + &self, + justification: &AlephJustification, + bytes: Vec, + ) -> Result<(), SessionVerificationError> { + use AlephJustification::*; + use SessionVerificationError::*; + match justification { + CommitteeMultisignature(multisignature) => { + match self.authority_verifier.is_complete(&bytes, multisignature) { + true => Ok(()), + false => Err(BadMultisignature), + } + } + EmergencySignature(signature) => match self + .emergency_signer + .as_ref() + .ok_or(NoEmergencySigner)? + .verify(&bytes, signature) + { + true => Ok(()), + false => Err(BadEmergencySignature), + }, + } + } +} + +// This shouldn't be necessary after we remove the legacy justification sync. Then we can also +// rewrite the implementation above and make it simpler. +impl LegacyVerifier for SessionVerifier { + fn verify(&self, justification: &AlephJustification, hash: B::Hash) -> bool { + match self.verify_bytes(justification, hash.encode()) { + Ok(()) => true, + Err(e) => { + warn!(target: "aleph-justification", "Bad justification for block {:?}: {}", hash, e); + false + } + } + } +} + +/// Ways in which a justification can fail verification. +pub enum VerificationError { + UnknownSession, + Session(SessionVerificationError), +} + +impl From for VerificationError { + fn from(e: SessionVerificationError) -> Self { + VerificationError::Session(e) + } +} + +impl Display for VerificationError { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { + use VerificationError::*; + match self { + UnknownSession => write!(f, "justification from unknown session"), + Session(e) => write!(f, "{}", e), + } + } +} + +/// A verifier working for all sessions, although if the session is too new or ancient it will fail +/// verification -- this is by design. +pub struct FullVerifier { + sessions: ReadOnlySessionMap, + period: SessionPeriod, +} + +impl FullVerifier { + async fn session_verifier>( + &self, + header: &H, + ) -> Result { + let session_id = session_id_from_num(*header.number(), self.period); + self.sessions + .get(session_id) + .await + .map(|authority_data| authority_data.into()) + .ok_or(VerificationError::UnknownSession) + } +} + +#[async_trait::async_trait] +impl> Verifier> for FullVerifier { + type Error = VerificationError; + + async fn verify( + &self, + justification: Justification, + ) -> Result, Self::Error> { + let header = &justification.header; + let verifier = self.session_verifier(header).await?; + verifier.verify_bytes(&justification.raw_justification, header.hash().encode())?; + Ok(justification) + } +} diff --git a/finality-aleph/src/sync/substrate.rs b/finality-aleph/src/sync/substrate/mod.rs similarity index 85% rename from finality-aleph/src/sync/substrate.rs rename to finality-aleph/src/sync/substrate/mod.rs index 5516f80ba2..451cb04d31 100644 --- a/finality-aleph/src/sync/substrate.rs +++ b/finality-aleph/src/sync/substrate/mod.rs @@ -5,6 +5,12 @@ use sp_runtime::traits::{CheckedSub, Header as SubstrateHeader, One}; use crate::sync::{BlockIdentifier, Header}; +mod justification; + +// Probably can be removed after removal of legacy justification sync. +pub use justification::SessionVerifier; + +/// An identifier uniquely specifying a block and its height. #[derive(Clone, Debug, PartialEq, Eq)] pub struct BlockId> { hash: H::Hash, From 2e82cdd713e4d24769dc25b088fc5cefce0068cd Mon Sep 17 00:00:00 2001 From: timorl Date: Mon, 2 Jan 2023 14:13:48 +0100 Subject: [PATCH 2/3] Sync rwlock in session map --- finality-aleph/src/nodes/mod.rs | 1 - finality-aleph/src/party/mod.rs | 12 +- finality-aleph/src/session_map.rs | 111 ++++++------------ finality-aleph/src/sync/mod.rs | 2 +- .../src/sync/substrate/justification.rs | 10 +- 5 files changed, 46 insertions(+), 90 deletions(-) diff --git a/finality-aleph/src/nodes/mod.rs b/finality-aleph/src/nodes/mod.rs index 9532d3f28d..e4873c176f 100644 --- a/finality-aleph/src/nodes/mod.rs +++ b/finality-aleph/src/nodes/mod.rs @@ -64,7 +64,6 @@ impl SessionInfoProvider for SessionInfoProviderIm let verifier = self .session_authorities .get(current_session) - .await .map(|authority_data| authority_data.into()); SessionInfo { diff --git a/finality-aleph/src/party/mod.rs b/finality-aleph/src/party/mod.rs index 50c9c78e3f..77e172042c 100644 --- a/finality-aleph/src/party/mod.rs +++ b/finality-aleph/src/party/mod.rs @@ -111,7 +111,6 @@ where .session_authorities .subscribe_to_insertion(session_id) .await - .await { Err(e) => panic!( "Error while receiving the notification about current session {:?}", @@ -162,8 +161,7 @@ where let next_session_id = SessionId(session_id.0 + 1); let mut start_next_session_network = Some( self.session_authorities - .subscribe_to_insertion(next_session_id) - .await, + .subscribe_to_insertion(next_session_id), ); loop { tokio::select! { @@ -181,7 +179,7 @@ where match notification.await { Err(e) => { warn!(target: "aleph-party", "Error with subscription {:?}", e); - start_next_session_network = Some(self.session_authorities.subscribe_to_insertion(next_session_id).await); + start_next_session_network = Some(self.session_authorities.subscribe_to_insertion(next_session_id)); None }, Ok(next_session_authority_data) => { @@ -424,8 +422,7 @@ mod tests { if let Some((session, authorities)) = session_authorities { self.controller .shared_session_map - .update(session, SessionAuthorityData::new(authorities, None)) - .await; + .update(session, SessionAuthorityData::new(authorities, None)); } if let Some(id) = id { @@ -486,8 +483,7 @@ mod tests { if let Some((session, authorities)) = session_authorities { self.controller .shared_session_map - .update(session, SessionAuthorityData::new(authorities, None)) - .await; + .update(session, SessionAuthorityData::new(authorities, None)); } if let Some(id) = id { diff --git a/finality-aleph/src/session_map.rs b/finality-aleph/src/session_map.rs index b6182e3bfa..7cdfe630fd 100644 --- a/finality-aleph/src/session_map.rs +++ b/finality-aleph/src/session_map.rs @@ -3,6 +3,7 @@ use std::{collections::HashMap, marker::PhantomData, sync::Arc}; use aleph_primitives::{AlephSessionApi, SessionAuthorityData}; use futures::StreamExt; use log::{debug, error, trace}; +use parking_lot::RwLock; use sc_client_api::{Backend, FinalityNotification}; use sc_utils::mpsc::TracingUnboundedReceiver; use sp_runtime::{ @@ -10,10 +11,7 @@ use sp_runtime::{ traits::{Block, Header, NumberFor}, SaturatedConversion, }; -use tokio::sync::{ - oneshot::{Receiver as OneShotReceiver, Sender as OneShotSender}, - RwLock, -}; +use tokio::sync::oneshot::{Receiver as OneShotReceiver, Sender as OneShotSender}; use crate::{ first_block_of_session, session_id_from_block_num, ClientForAleph, SessionId, SessionPeriod, @@ -167,12 +165,12 @@ impl SharedSessionMap { Self(Arc::new(RwLock::new((HashMap::new(), HashMap::new())))) } - pub async fn update( + pub fn update( &mut self, id: SessionId, authority_data: SessionAuthorityData, ) -> Option { - let mut guard = self.0.write().await; + let mut guard = self.0.write(); // notify all subscribers about insertion and remove them from subscription if let Some(senders) = guard.1.remove(&id) { @@ -186,8 +184,8 @@ impl SharedSessionMap { guard.0.insert(id, authority_data) } - async fn prune_below(&mut self, id: SessionId) { - let mut guard = self.0.write().await; + fn prune_below(&mut self, id: SessionId) { + let mut guard = self.0.write(); guard.0.retain(|&s, _| s >= id); guard.1.retain(|&s, _| s >= id); @@ -201,19 +199,16 @@ impl SharedSessionMap { } impl ReadOnlySessionMap { - pub async fn get(&self, id: SessionId) -> Option { - self.inner.read().await.0.get(&id).cloned() + pub fn get(&self, id: SessionId) -> Option { + self.inner.read().0.get(&id).cloned() } /// returns an end of the oneshot channel that fires a message if either authority data is already /// known for the session with id = `id` or when the data is inserted for this session. - pub async fn subscribe_to_insertion( - &self, - id: SessionId, - ) -> OneShotReceiver { + pub fn subscribe_to_insertion(&self, id: SessionId) -> OneShotReceiver { let (sender, receiver) = tokio::sync::oneshot::channel(); - let mut guard = self.inner.write().await; + let mut guard = self.inner.write(); if let Some(authority_data) = guard.0.get(&id) { // if the value is already present notify immediately @@ -282,40 +277,34 @@ where } /// puts authority data for the next session into the session map - async fn handle_first_block_of_session(&mut self, num: NumberFor, session_id: SessionId) { + fn handle_first_block_of_session(&mut self, num: NumberFor, session_id: SessionId) { debug!(target: "aleph-session-updater", "Handling first block #{:?} of session {:?}", num, session_id.0); let next_session = SessionId(session_id.0 + 1); let authority_provider = &self.authority_provider; - self.session_map - .update( - next_session, - get_authority_data_for_session::<_, B>(authority_provider, next_session, num), - ) - .await; + self.session_map.update( + next_session, + get_authority_data_for_session::<_, B>(authority_provider, next_session, num), + ); // if this is the first session we also need to include starting authority data into the map if session_id.0 == 0 { let authority_provider = &self.authority_provider; - self.session_map - .update( - session_id, - get_authority_data_for_session::<_, B>(authority_provider, session_id, num), - ) - .await; + self.session_map.update( + session_id, + get_authority_data_for_session::<_, B>(authority_provider, session_id, num), + ); } if session_id.0 >= PRUNING_THRESHOLD && session_id.0 % PRUNING_THRESHOLD == 0 { debug!(target: "aleph-session-updater", "Pruning session map below session #{:?}", session_id.0 - PRUNING_THRESHOLD); self.session_map - .prune_below(SessionId(session_id.0 - PRUNING_THRESHOLD)) - .await; + .prune_below(SessionId(session_id.0 - PRUNING_THRESHOLD)); } } - async fn update_session(&mut self, session_id: SessionId, period: SessionPeriod) { + fn update_session(&mut self, session_id: SessionId, period: SessionPeriod) { let first_block = first_block_of_session::(session_id, period); - self.handle_first_block_of_session(first_block, session_id) - .await; + self.handle_first_block_of_session(first_block, session_id); } fn catch_up_boundaries(&self, period: SessionPeriod) -> (SessionId, SessionId) { @@ -334,7 +323,7 @@ where // lets catch up for session in starting_session.0..=current_session.0 { - self.update_session(SessionId(session), period).await; + self.update_session(SessionId(session), period); } let mut last_updated = current_session; @@ -350,7 +339,7 @@ where } for session in (last_updated.0 + 1)..=session_id.0 { - self.update_session(SessionId(session), period).await; + self.update_session(SessionId(session), period); } last_updated = session_id; @@ -504,22 +493,10 @@ mod tests { // wait a bit Delay::new(Duration::from_millis(50)).await; - assert_eq!( - session_map.get(SessionId(0)).await, - Some(authority_data(0, 4)) - ); - assert_eq!( - session_map.get(SessionId(1)).await, - Some(authority_data(4, 8)) - ); - assert_eq!( - session_map.get(SessionId(2)).await, - Some(authority_data(8, 12)) - ); - assert_eq!( - session_map.get(SessionId(3)).await, - Some(authority_data(12, 16)) - ); + assert_eq!(session_map.get(SessionId(0)), Some(authority_data(0, 4))); + assert_eq!(session_map.get(SessionId(1)), Some(authority_data(4, 8))); + assert_eq!(session_map.get(SessionId(2)), Some(authority_data(8, 12))); + assert_eq!(session_map.get(SessionId(3)), Some(authority_data(12, 16))); } #[tokio::test(flavor = "multi_thread")] @@ -549,22 +526,10 @@ mod tests { // wait a bit Delay::new(Duration::from_millis(50)).await; - assert_eq!( - session_map.get(SessionId(0)).await, - Some(authority_data(0, 4)) - ); - assert_eq!( - session_map.get(SessionId(1)).await, - Some(authority_data(4, 8)) - ); - assert_eq!( - session_map.get(SessionId(2)).await, - Some(authority_data(8, 12)) - ); - assert_eq!( - session_map.get(SessionId(3)).await, - Some(authority_data(12, 16)) - ); + assert_eq!(session_map.get(SessionId(0)), Some(authority_data(0, 4))); + assert_eq!(session_map.get(SessionId(1)), Some(authority_data(4, 8))); + assert_eq!(session_map.get(SessionId(2)), Some(authority_data(8, 12))); + assert_eq!(session_map.get(SessionId(3)), Some(authority_data(12, 16))); } #[tokio::test(flavor = "multi_thread")] @@ -598,7 +563,7 @@ mod tests { } for i in 0..=20 - PRUNING_THRESHOLD { assert_eq!( - session_map.get(SessionId(i)).await, + session_map.get(SessionId(i)), None, "Session {:?} should be pruned", i @@ -606,7 +571,7 @@ mod tests { } for i in 21 - PRUNING_THRESHOLD..=20 { assert_eq!( - session_map.get(SessionId(i)).await, + session_map.get(SessionId(i)), Some(authority_data(4 * i as u64, 4 * (i + 1) as u64)), "Session {:?} should not be pruned", i @@ -620,9 +585,9 @@ mod tests { let readonly = shared.read_only(); let session = SessionId(0); - shared.update(session, authority_data(0, 2)).await; + shared.update(session, authority_data(0, 2)); - let mut receiver = readonly.subscribe_to_insertion(session).await; + let mut receiver = readonly.subscribe_to_insertion(session); // we should have this immediately assert_eq!(Ok(authority_data(0, 2)), receiver.try_recv()); @@ -633,11 +598,11 @@ mod tests { let mut shared = SharedSessionMap::new(); let readonly = shared.read_only(); let session = SessionId(0); - let mut receiver = readonly.subscribe_to_insertion(session).await; + let mut receiver = readonly.subscribe_to_insertion(session); // does not yet have any value assert_eq!(Err(TryRecvError::Empty), receiver.try_recv()); - shared.update(session, authority_data(0, 2)).await; + shared.update(session, authority_data(0, 2)); assert_eq!(Ok(authority_data(0, 2)), receiver.await); } } diff --git a/finality-aleph/src/sync/mod.rs b/finality-aleph/src/sync/mod.rs index 5f4b80d9c7..cd31664065 100644 --- a/finality-aleph/src/sync/mod.rs +++ b/finality-aleph/src/sync/mod.rs @@ -50,7 +50,7 @@ pub trait Verifier { /// Verifies the raw justification and returns a full justification if successful, otherwise an /// error. - async fn verify(&self, justification: J::Unverified) -> Result; + fn verify(&self, justification: J::Unverified) -> Result; } /// A facility for finalizing blocks using justifications. diff --git a/finality-aleph/src/sync/substrate/justification.rs b/finality-aleph/src/sync/substrate/justification.rs index 5208d74a02..c7d8285b68 100644 --- a/finality-aleph/src/sync/substrate/justification.rs +++ b/finality-aleph/src/sync/substrate/justification.rs @@ -142,14 +142,13 @@ pub struct FullVerifier { } impl FullVerifier { - async fn session_verifier>( + fn session_verifier>( &self, header: &H, ) -> Result { let session_id = session_id_from_num(*header.number(), self.period); self.sessions .get(session_id) - .await .map(|authority_data| authority_data.into()) .ok_or(VerificationError::UnknownSession) } @@ -159,12 +158,9 @@ impl FullVerifier { impl> Verifier> for FullVerifier { type Error = VerificationError; - async fn verify( - &self, - justification: Justification, - ) -> Result, Self::Error> { + fn verify(&self, justification: Justification) -> Result, Self::Error> { let header = &justification.header; - let verifier = self.session_verifier(header).await?; + let verifier = self.session_verifier(header)?; verifier.verify_bytes(&justification.raw_justification, header.hash().encode())?; Ok(justification) } From 3b7ff800f19effad23258396b6be6c4d40ece793 Mon Sep 17 00:00:00 2001 From: timorl Date: Mon, 2 Jan 2023 14:16:53 +0100 Subject: [PATCH 3/3] Missed a spot --- finality-aleph/src/sync/substrate/justification.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/finality-aleph/src/sync/substrate/justification.rs b/finality-aleph/src/sync/substrate/justification.rs index c7d8285b68..ecc182a35e 100644 --- a/finality-aleph/src/sync/substrate/justification.rs +++ b/finality-aleph/src/sync/substrate/justification.rs @@ -154,7 +154,6 @@ impl FullVerifier { } } -#[async_trait::async_trait] impl> Verifier> for FullVerifier { type Error = VerificationError;