diff --git a/finality-aleph/src/crypto.rs b/finality-aleph/src/crypto.rs index 4d24141c7e..dd6a5d3b06 100644 --- a/finality-aleph/src/crypto.rs +++ b/finality-aleph/src/crypto.rs @@ -102,7 +102,7 @@ pub fn verify(authority: &AuthorityId, message: &[u8], signature: &Signature) -> /// Holds the public authority keys for a session allowing for verification of messages from that /// session. -#[derive(Clone)] +#[derive(PartialEq, Clone, Debug)] pub struct AuthorityVerifier { authorities: Vec, } diff --git a/finality-aleph/src/nodes/mod.rs b/finality-aleph/src/nodes/mod.rs index 91e063284d..b377ab4a97 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,10 +57,10 @@ impl SessionInfoProviderImpl { } #[async_trait::async_trait] -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); +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 .session_authorities .get(current_session) diff --git a/finality-aleph/src/party/impls.rs b/finality-aleph/src/party/impls.rs index 14000177aa..845f1b5715 100644 --- a/finality-aleph/src/party/impls.rs +++ b/finality-aleph/src/party/impls.rs @@ -1,10 +1,11 @@ use std::{marker::PhantomData, sync::Arc}; use sc_client_api::Backend; -use sp_runtime::traits::{Block as BlockT, NumberFor, SaturatedConversion}; +use sp_runtime::traits::{Block as BlockT, NumberFor}; use crate::{ party::traits::{Block, ChainState, SessionInfo}, + session::{first_block_of_session, last_block_of_session, session_id_from_block_num}, ClientForAleph, SessionId, SessionPeriod, }; @@ -44,14 +45,14 @@ impl SessionInfoImpl { impl SessionInfo for SessionInfoImpl { fn session_id_from_block_num(&self, n: NumberFor) -> SessionId { - SessionId(n.saturated_into::() / self.session_period.0) + session_id_from_block_num(n, self.session_period) } fn last_block_of_session(&self, session_id: SessionId) -> NumberFor { - ((session_id.0 + 1) * self.session_period.0 - 1).into() + last_block_of_session(session_id, self.session_period) } fn first_block_of_session(&self, session_id: SessionId) -> NumberFor { - (session_id.0 * self.session_period.0).into() + first_block_of_session(session_id, self.session_period) } } diff --git a/finality-aleph/src/party/mocks.rs b/finality-aleph/src/party/mocks.rs index 21977bc100..935dc150b1 100644 --- a/finality-aleph/src/party/mocks.rs +++ b/finality-aleph/src/party/mocks.rs @@ -14,7 +14,8 @@ use crate::{ manager::AuthorityTask, traits::{Block, ChainState, NodeSessionManager, SessionInfo, SyncState}, }, - AuthorityId, NodeIndex, SessionId, + session::{first_block_of_session, last_block_of_session, session_id_from_block_num}, + AuthorityId, NodeIndex, SessionId, SessionPeriod, }; type AMutex = Arc>; @@ -183,25 +184,27 @@ impl NodeSessionManager for Arc { } pub struct MockSessionInfo { - pub session_period: u32, + pub session_period: SessionPeriod, } impl MockSessionInfo { pub fn new(session_period: u32) -> Self { - Self { session_period } + Self { + session_period: SessionPeriod(session_period), + } } } impl SessionInfo for MockSessionInfo { fn session_id_from_block_num(&self, n: u32) -> SessionId { - SessionId(n / self.session_period) + session_id_from_block_num(n, self.session_period) } fn last_block_of_session(&self, session_id: SessionId) -> u32 { - (session_id.0 + 1) * self.session_period - 1 + last_block_of_session(session_id, self.session_period) } fn first_block_of_session(&self, session_id: SessionId) -> u32 { - session_id.0 * self.session_period + first_block_of_session(session_id, self.session_period) } } diff --git a/finality-aleph/src/session.rs b/finality-aleph/src/session.rs index 3e7cde9ad2..3330c7156e 100644 --- a/finality-aleph/src/session.rs +++ b/finality-aleph/src/session.rs @@ -1,5 +1,8 @@ use codec::{Decode, Encode}; -use sp_runtime::{traits::Block, SaturatedConversion}; +use sp_runtime::{ + traits::{AtLeast32BitUnsigned, Block}, + SaturatedConversion, +}; use crate::NumberFor; @@ -12,8 +15,8 @@ pub struct SessionBoundaries { impl SessionBoundaries { pub fn new(session_id: SessionId, period: SessionPeriod) -> Self { SessionBoundaries { - first_block: first_block_of_session::(session_id, period), - last_block: last_block_of_session::(session_id, period), + first_block: first_block_of_session(session_id, period), + last_block: last_block_of_session(session_id, period), } } @@ -26,22 +29,40 @@ impl SessionBoundaries { } } -pub fn first_block_of_session( +pub fn first_block_of_session( session_id: SessionId, period: SessionPeriod, -) -> NumberFor { +) -> N { (session_id.0 * period.0).into() } -pub fn last_block_of_session( +pub fn last_block_of_session( session_id: SessionId, period: SessionPeriod, -) -> NumberFor { +) -> N { ((session_id.0 + 1) * period.0 - 1).into() } -pub fn session_id_from_block_num(num: NumberFor, period: SessionPeriod) -> SessionId { - SessionId(num.saturated_into::() / period.0) +pub fn session_id_from_block_num( + num: N, + period: SessionPeriod, +) -> SessionId { + SessionId((num / period.0.into()).saturated_into()) +} + +#[cfg(test)] +pub mod testing { + use aleph_primitives::SessionAuthorityData; + use sp_runtime::testing::UintAuthorityId; + + pub fn authority_data(from: u64, to: u64) -> SessionAuthorityData { + SessionAuthorityData::new( + (from..to) + .map(|id| UintAuthorityId(id).to_public_key()) + .collect(), + None, + ) + } } #[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash, Ord, PartialOrd, Encode, Decode)] diff --git a/finality-aleph/src/session_map.rs b/finality-aleph/src/session_map.rs index b6182e3bfa..59f1093b24 100644 --- a/finality-aleph/src/session_map.rs +++ b/finality-aleph/src/session_map.rs @@ -313,7 +313,7 @@ where } async fn update_session(&mut self, session_id: SessionId, period: SessionPeriod) { - let first_block = first_block_of_session::(session_id, period); + let first_block = first_block_of_session(session_id, period); self.handle_first_block_of_session(first_block, session_id) .await; } @@ -321,7 +321,7 @@ where fn catch_up_boundaries(&self, period: SessionPeriod) -> (SessionId, SessionId) { let last_finalized = self.finality_notificator.last_finalized(); - let current_session = session_id_from_block_num::(last_finalized, period); + let current_session = session_id_from_block_num(last_finalized, period); let starting_session = SessionId(current_session.0.saturating_sub(PRUNING_THRESHOLD)); (starting_session, current_session) @@ -343,7 +343,7 @@ where let last_finalized = header.number(); trace!(target: "aleph-session-updater", "got FinalityNotification about #{:?}", last_finalized); - let session_id = session_id_from_block_num::(*last_finalized, period); + let session_id = session_id_from_block_num(*last_finalized, period); if last_updated >= session_id { continue; @@ -366,7 +366,6 @@ mod tests { use sc_block_builder::BlockBuilderProvider; use sc_utils::mpsc::tracing_unbounded; use sp_consensus::BlockOrigin; - use sp_runtime::testing::UintAuthorityId; use substrate_test_runtime_client::{ ClientBlockImportExt, DefaultTestClientBuilderExt, TestClient, TestClientBuilder, TestClientBuilderExt, @@ -374,7 +373,7 @@ mod tests { use tokio::sync::oneshot::error::TryRecvError; use super::*; - use crate::testing::mocks::TBlock; + use crate::{session::testing::authority_data, testing::mocks::TBlock}; struct MockProvider { pub session_map: HashMap, SessionAuthorityData>, @@ -432,15 +431,6 @@ mod tests { } } - fn authority_data(from: u64, to: u64) -> SessionAuthorityData { - SessionAuthorityData::new( - (from..to) - .map(|id| UintAuthorityId(id).to_public_key()) - .collect(), - None, - ) - } - fn n_new_blocks(client: &mut Arc, n: u64) -> Vec { (0..n) .map(|_| { diff --git a/finality-aleph/src/sync/mod.rs b/finality-aleph/src/sync/mod.rs index 23e66bd53e..207efc5d8f 100644 --- a/finality-aleph/src/sync/mod.rs +++ b/finality-aleph/src/sync/mod.rs @@ -7,6 +7,8 @@ mod substrate; mod task_queue; mod ticker; +pub use substrate::SessionVerifier; + const LOG_TARGET: &str = "aleph-block-sync"; /// The identifier of a block, the least amount of knowledge we can have about a block. @@ -50,7 +52,7 @@ pub trait Verifier { /// Verifies the raw justification and returns a full justification if successful, otherwise an /// error. - fn verify(&self, justification: J::Unverified) -> Result; + fn verify(&mut self, justification: J::Unverified) -> Result; } /// A facility for finalizing blocks using justifications. diff --git a/finality-aleph/src/sync/substrate/mod.rs b/finality-aleph/src/sync/substrate/mod.rs index 2a96b49ed9..cb5f1274cc 100644 --- a/finality-aleph/src/sync/substrate/mod.rs +++ b/finality-aleph/src/sync/substrate/mod.rs @@ -11,6 +11,9 @@ use crate::{ mod chain_status; mod finalizer; mod status_notifier; +mod verification; + +pub use verification::SessionVerifier; #[derive(Clone, Debug, PartialEq, Eq)] pub struct BlockId> { @@ -18,6 +21,7 @@ pub struct BlockId> { number: H::Number, } +/// An identifier uniquely specifying a block and its height. impl> Hash for BlockId { fn hash(&self, state: &mut H) where diff --git a/finality-aleph/src/sync/substrate/verification/cache.rs b/finality-aleph/src/sync/substrate/verification/cache.rs new file mode 100644 index 0000000000..545302b475 --- /dev/null +++ b/finality-aleph/src/sync/substrate/verification/cache.rs @@ -0,0 +1,358 @@ +use std::{ + collections::{hash_map::Entry, HashMap}, + fmt::{Display, Error as FmtError, Formatter}, +}; + +use aleph_primitives::BlockNumber; +use sp_runtime::SaturatedConversion; + +use crate::{ + session::{first_block_of_session, session_id_from_block_num, SessionId}, + session_map::AuthorityProvider, + sync::substrate::verification::{verifier::SessionVerifier, FinalizationInfo}, + SessionPeriod, +}; + +/// Ways in which a justification can fail verification. +#[derive(Debug, PartialEq, Eq)] +pub enum CacheError { + UnknownAuthorities(SessionId), + SessionTooOld(SessionId, SessionId), + SessionInFuture(SessionId, SessionId), +} + +impl Display for CacheError { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { + use CacheError::*; + match self { + SessionTooOld(session, lower_bound) => write!( + f, + "session {:?} is too old. Should be at least {:?}", + session, lower_bound + ), + SessionInFuture(session, upper_bound) => write!( + f, + "session {:?} without known authorities. Should be at most {:?}", + session, upper_bound + ), + UnknownAuthorities(session) => { + write!( + f, + "authorities for session {:?} not present on chain even though they should be", + session + ) + } + } + } +} + +/// Cache storing SessionVerifier structs for multiple sessions. Keeps up to `cache_size` verifiers of top sessions. +/// If the session is too new or ancient it will fail to return a SessionVerifier. +/// Highest session verifier this cache returns is for the session after the current finalization session. +/// Lowest session verifier this cache returns is for `top_returned_session` - `cache_size`. +pub struct VerifierCache +where + AP: AuthorityProvider, + FI: FinalizationInfo, +{ + sessions: HashMap, + session_period: SessionPeriod, + finalization_info: FI, + authority_provider: AP, + cache_size: usize, + /// Lowest currently available session. + lower_bound: SessionId, +} + +impl VerifierCache +where + AP: AuthorityProvider, + FI: FinalizationInfo, +{ + pub fn new( + session_period: SessionPeriod, + finalization_info: FI, + authority_provider: AP, + cache_size: usize, + ) -> Self { + Self { + sessions: HashMap::new(), + session_period, + finalization_info, + authority_provider, + cache_size, + lower_bound: SessionId(0), + } + } +} + +/// Download authorities for the session and return `SessionVerifier` for them. `session_id` should be the first session, +/// or the first block from the session number `session_id - 1` should be finalized. +fn download_session_verifier>( + authority_provider: &AP, + session_id: SessionId, + session_period: SessionPeriod, +) -> Option { + let maybe_authority_data = match session_id { + SessionId(0) => authority_provider.authority_data(0), + SessionId(id) => { + let prev_first = first_block_of_session(SessionId(id - 1), session_period); + authority_provider.next_authority_data(prev_first) + } + }; + + maybe_authority_data.map(|a| a.into()) +} + +impl VerifierCache +where + AP: AuthorityProvider, + FI: FinalizationInfo, +{ + /// Prune all sessions with a number smaller than `session_id` + fn prune(&mut self, session_id: SessionId) { + self.sessions.retain(|&id, _| id >= session_id); + self.lower_bound = session_id; + } + + /// Returns session verifier for block number if available. Updates cache if necessary. + pub fn get(&mut self, number: BlockNumber) -> Result<&SessionVerifier, CacheError> { + let session_id = session_id_from_block_num(number, self.session_period); + + if session_id < self.lower_bound { + return Err(CacheError::SessionTooOld(session_id, self.lower_bound)); + } + + // We are sure about authorities in all session that have first block from previous session finalized. + let upper_bound = SessionId( + session_id_from_block_num( + self.finalization_info.finalized_number(), + self.session_period, + ) + .0 + 1, + ); + if session_id > upper_bound { + return Err(CacheError::SessionInFuture(session_id, upper_bound)); + } + + if session_id.0 + >= self + .lower_bound + .0 + .saturating_add(self.cache_size.saturated_into()) + { + self.prune(SessionId( + session_id + .0 + .saturating_sub(self.cache_size.saturated_into()) + + 1, + )); + } + + let verifier = match self.sessions.entry(session_id) { + Entry::Occupied(occupied) => occupied.into_mut(), + Entry::Vacant(vacant) => { + let verifier = download_session_verifier( + &self.authority_provider, + session_id, + self.session_period, + ) + .ok_or(CacheError::UnknownAuthorities(session_id))?; + vacant.insert(verifier) + } + }; + + Ok(verifier) + } +} + +#[cfg(test)] +mod tests { + use std::{cell::Cell, collections::HashMap}; + + use aleph_primitives::SessionAuthorityData; + use sp_runtime::SaturatedConversion; + + use super::{ + AuthorityProvider, BlockNumber, CacheError, FinalizationInfo, SessionVerifier, + VerifierCache, + }; + use crate::{ + session::{session_id_from_block_num, testing::authority_data, SessionId}, + SessionPeriod, + }; + + const SESSION_PERIOD: u32 = 30; + const CACHE_SIZE: usize = 2; + + type TestVerifierCache<'a> = VerifierCache>; + + struct MockFinalizationInfo<'a> { + finalized_number: &'a Cell, + } + + impl<'a> FinalizationInfo for MockFinalizationInfo<'a> { + fn finalized_number(&self) -> BlockNumber { + self.finalized_number.get() + } + } + + struct MockAuthorityProvider { + session_map: HashMap, + session_period: SessionPeriod, + } + + fn authority_data_for_session(session_id: u64) -> SessionAuthorityData { + authority_data(session_id * 4, (session_id + 1) * 4) + } + + impl MockAuthorityProvider { + fn new(session_n: u64) -> Self { + let session_map = (0..session_n + 1) + .map(|s| (SessionId(s.saturated_into()), authority_data_for_session(s))) + .collect(); + + Self { + session_map, + session_period: SessionPeriod(SESSION_PERIOD), + } + } + } + + impl AuthorityProvider for MockAuthorityProvider { + fn authority_data(&self, block: BlockNumber) -> Option { + self.session_map + .get(&session_id_from_block_num(block, self.session_period)) + .cloned() + } + + fn next_authority_data(&self, block: BlockNumber) -> Option { + self.session_map + .get(&SessionId( + session_id_from_block_num(block, self.session_period).0 + 1, + )) + .cloned() + } + } + + fn setup_test(max_session_n: u64, finalized_number: &'_ Cell) -> TestVerifierCache<'_> { + let finalization_info = MockFinalizationInfo { finalized_number }; + let authority_provider = MockAuthorityProvider::new(max_session_n); + + VerifierCache::new( + SessionPeriod(SESSION_PERIOD), + finalization_info, + authority_provider, + CACHE_SIZE, + ) + } + + fn finalize_first_in_session(finalized_number: &Cell, session_id: u32) { + finalized_number.set(session_id * SESSION_PERIOD); + } + + fn session_verifier( + verifier: &mut TestVerifierCache, + session_id: u32, + ) -> Result { + verifier.get((session_id + 1) * SESSION_PERIOD - 1).cloned() + } + + fn check_session_verifier(verifier: &mut TestVerifierCache, session_id: u32) { + let session_verifier = + session_verifier(verifier, session_id).expect("Should return verifier. Got error"); + let expected_verifier: SessionVerifier = + authority_data_for_session(session_id as u64).into(); + assert_eq!(session_verifier, expected_verifier); + } + + #[test] + fn genesis_session() { + let finalized_number = Cell::new(0); + + let mut verifier = setup_test(0, &finalized_number); + + check_session_verifier(&mut verifier, 0); + } + + #[test] + fn normal_session() { + let finalized_number = Cell::new(0); + + let mut verifier = setup_test(3, &finalized_number); + + check_session_verifier(&mut verifier, 0); + check_session_verifier(&mut verifier, 1); + + finalize_first_in_session(&finalized_number, 1); + check_session_verifier(&mut verifier, 0); + check_session_verifier(&mut verifier, 1); + check_session_verifier(&mut verifier, 2); + + finalize_first_in_session(&finalized_number, 2); + check_session_verifier(&mut verifier, 1); + check_session_verifier(&mut verifier, 2); + check_session_verifier(&mut verifier, 3); + } + + #[test] + fn prunes_old_sessions() { + let finalized_number = Cell::new(0); + + let mut verifier = setup_test(3, &finalized_number); + + check_session_verifier(&mut verifier, 0); + check_session_verifier(&mut verifier, 1); + + finalize_first_in_session(&finalized_number, 1); + check_session_verifier(&mut verifier, 2); + + // Should no longer have verifier for session 0 + assert_eq!( + session_verifier(&mut verifier, 0), + Err(CacheError::SessionTooOld(SessionId(0), SessionId(1))) + ); + + finalize_first_in_session(&finalized_number, 2); + check_session_verifier(&mut verifier, 3); + + // Should no longer have verifier for session 1 + assert_eq!( + session_verifier(&mut verifier, 1), + Err(CacheError::SessionTooOld(SessionId(1), SessionId(2))) + ); + } + + #[test] + fn session_from_future() { + let finalized_number = Cell::new(0); + + let mut verifier = setup_test(3, &finalized_number); + + finalize_first_in_session(&finalized_number, 1); + + // Did not finalize first block in session 2 yet + assert_eq!( + session_verifier(&mut verifier, 3), + Err(CacheError::SessionInFuture(SessionId(3), SessionId(2))) + ); + } + + #[test] + fn authority_provider_error() { + let finalized_number = Cell::new(0); + let mut verifier = setup_test(0, &finalized_number); + + assert_eq!( + session_verifier(&mut verifier, 1), + Err(CacheError::UnknownAuthorities(SessionId(1))) + ); + + finalize_first_in_session(&finalized_number, 1); + + assert_eq!( + session_verifier(&mut verifier, 2), + Err(CacheError::UnknownAuthorities(SessionId(2))) + ); + } +} diff --git a/finality-aleph/src/sync/substrate/verification/mod.rs b/finality-aleph/src/sync/substrate/verification/mod.rs new file mode 100644 index 0000000000..eefb6af68d --- /dev/null +++ b/finality-aleph/src/sync/substrate/verification/mod.rs @@ -0,0 +1,114 @@ +use std::{ + fmt::{Display, Error as FmtError, Formatter}, + marker::PhantomData, + sync::Arc, +}; + +use aleph_primitives::BlockNumber; +use codec::Encode; +use sc_client_api::HeaderBackend; +use sp_runtime::traits::{Block as BlockT, Header as SubstrateHeader}; + +use crate::{ + session_map::AuthorityProvider, + sync::{ + substrate::{ + verification::{ + cache::{CacheError, VerifierCache}, + verifier::SessionVerificationError, + }, + Justification, + }, + Verifier, + }, +}; + +mod cache; +mod verifier; + +pub use verifier::SessionVerifier; + +/// Supplies finalized number. Will be unified together with other traits we used in A0-1839. +pub trait FinalizationInfo { + fn finalized_number(&self) -> BlockNumber; +} + +/// Substrate specific implementation of `FinalizationInfo` +pub struct SubstrateFinalizationInfo +where + BE: HeaderBackend, + B: BlockT, + B::Header: SubstrateHeader, +{ + client: Arc, + _phantom: PhantomData, +} + +impl SubstrateFinalizationInfo +where + BE: HeaderBackend, + B: BlockT, + B::Header: SubstrateHeader, +{ + pub fn new(client: Arc) -> Self { + Self { + client, + _phantom: PhantomData, + } + } +} + +impl FinalizationInfo for SubstrateFinalizationInfo +where + BE: HeaderBackend, + B: BlockT, + B::Header: SubstrateHeader, +{ + fn finalized_number(&self) -> BlockNumber { + self.client.info().finalized_number + } +} + +#[derive(Debug, PartialEq, Eq)] +pub enum VerificationError { + Verification(SessionVerificationError), + Cache(CacheError), +} + +impl From for VerificationError { + fn from(e: SessionVerificationError) -> Self { + VerificationError::Verification(e) + } +} + +impl From for VerificationError { + fn from(e: CacheError) -> Self { + VerificationError::Cache(e) + } +} + +impl Display for VerificationError { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { + use VerificationError::*; + match self { + Verification(e) => write!(f, "{}", e), + Cache(e) => write!(f, "{}", e), + } + } +} + +impl Verifier> for VerifierCache +where + H: SubstrateHeader, + AP: AuthorityProvider, + FS: FinalizationInfo, +{ + type Error = VerificationError; + + fn verify(&mut self, justification: Justification) -> Result, Self::Error> { + let header = &justification.header; + let verifier = self.get(*header.number())?; + verifier.verify_bytes(&justification.raw_justification, header.hash().encode())?; + Ok(justification) + } +} diff --git a/finality-aleph/src/sync/substrate/verification/verifier.rs b/finality-aleph/src/sync/substrate/verification/verifier.rs new file mode 100644 index 0000000000..004ef9b536 --- /dev/null +++ b/finality-aleph/src/sync/substrate/verification/verifier.rs @@ -0,0 +1,90 @@ +use std::fmt::{Display, Error as FmtError, Formatter}; + +use aleph_primitives::SessionAuthorityData; +use codec::Encode; +use log::warn; +use sp_runtime::{traits::Block as BlockT, RuntimeAppPublic}; + +use crate::{ + crypto::AuthorityVerifier, + justification::{AlephJustification, Verifier as LegacyVerifier}, + AuthorityId, +}; + +/// A justification verifier within a single session. +#[derive(Clone, PartialEq, Debug)] +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. +#[derive(Debug, PartialEq, Eq)] +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 { + /// Verifies the correctness of a justification for supplied bytes. + 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 + } + } + } +} diff --git a/finality-aleph/src/testing/mocks/session_info.rs b/finality-aleph/src/testing/mocks/session_info.rs index 97e9c7a5a5..9e86190521 100644 --- a/finality-aleph/src/testing/mocks/session_info.rs +++ b/finality-aleph/src/testing/mocks/session_info.rs @@ -34,13 +34,10 @@ impl SessionInfoProviderImpl { #[async_trait::async_trait] impl SessionInfoProvider for SessionInfoProviderImpl { async fn for_block_num(&self, number: TNumber) -> SessionInfo { - let current_session = session_id_from_block_num::(number, self.session_period); + let current_session = session_id_from_block_num(number, self.session_period); SessionInfo { current_session, - last_block_height: last_block_of_session::( - current_session, - self.session_period, - ), + last_block_height: last_block_of_session(current_session, self.session_period), verifier: match &*self.acceptance_policy.lock().unwrap() { AcceptancePolicy::Unavailable => None, _ => Some(VerifierWrapper {