diff --git a/finality-aleph/src/sync/data.rs b/finality-aleph/src/sync/data.rs index ccf5b263dd..3d6e3eee1b 100644 --- a/finality-aleph/src/sync/data.rs +++ b/finality-aleph/src/sync/data.rs @@ -1,10 +1,14 @@ -use std::mem::size_of; +use std::{collections::HashSet, marker::PhantomData, mem::size_of}; use aleph_primitives::MAX_BLOCK_SIZE; use codec::{Decode, Encode, Error as CodecError, Input as CodecInput}; use log::warn; -use crate::{sync::Justification, Version}; +use crate::{ + network::GossipNetwork, + sync::{BlockIdFor, Justification, LOG_TARGET}, + Version, +}; /// The representation of the database state to be sent to other nodes. /// In the first version this only contains the top justification. @@ -13,6 +17,16 @@ pub struct State { top_justification: J::Unverified, } +impl State { + pub fn new(top_justification: J::Unverified) -> Self { + State { top_justification } + } + + pub fn top_justification(&self) -> J::Unverified { + self.top_justification.clone() + } +} + /// Data to be sent over the network. #[derive(Clone, Debug, Encode, Decode)] pub enum NetworkData { @@ -22,6 +36,8 @@ pub enum NetworkData { StateBroadcast(State), /// A series of justifications, sent to a node that is clearly behind. Justifications(Vec, State), + /// An explicit request for data, potentially a lot of it. + Request(BlockIdFor, State), } /// Version wrapper around the network data. @@ -44,6 +60,7 @@ fn encode_with_version(version: Version, payload: &[u8]) -> Vec { if size > MAX_SYNC_MESSAGE_SIZE { warn!( + target: LOG_TARGET, "Versioned sync message v{:?} too big during Encode. Size is {:?}. Should be {:?} at max.", version, payload.len(), @@ -100,3 +117,53 @@ impl Decode for VersionedNetworkData { } } } + +/// Wrap around a network to avoid thinking about versioning. +pub struct VersionWrapper>> { + inner: N, + _phantom: PhantomData, +} + +impl>> VersionWrapper { + /// Wrap the inner network. + pub fn new(inner: N) -> Self { + VersionWrapper { + inner, + _phantom: PhantomData, + } + } +} + +#[async_trait::async_trait] +impl>> GossipNetwork> + for VersionWrapper +{ + type Error = N::Error; + type PeerId = N::PeerId; + + fn send_to(&mut self, data: NetworkData, peer_id: Self::PeerId) -> Result<(), Self::Error> { + self.inner.send_to(VersionedNetworkData::V1(data), peer_id) + } + + fn send_to_random( + &mut self, + data: NetworkData, + peer_ids: HashSet, + ) -> Result<(), Self::Error> { + self.inner + .send_to_random(VersionedNetworkData::V1(data), peer_ids) + } + + fn broadcast(&mut self, data: NetworkData) -> Result<(), Self::Error> { + self.inner.broadcast(VersionedNetworkData::V1(data)) + } + + async fn next(&mut self) -> Result<(NetworkData, Self::PeerId), Self::Error> { + loop { + match self.inner.next().await? { + (VersionedNetworkData::Other(version, _), _) => warn!(target: LOG_TARGET, "Received sync data of unsupported version {:?}, this node might be running outdated software.", version), + (VersionedNetworkData::V1(data), peer_id) => return Ok((data, peer_id)), + } + } + } +} diff --git a/finality-aleph/src/sync/forest/mod.rs b/finality-aleph/src/sync/forest/mod.rs index 1f457fbe1b..c0648debca 100644 --- a/finality-aleph/src/sync/forest/mod.rs +++ b/finality-aleph/src/sync/forest/mod.rs @@ -3,13 +3,12 @@ use std::collections::{ HashMap, HashSet, }; -use crate::sync::{BlockIdentifier, Header, Justification, PeerId}; +use crate::sync::{BlockIdFor, BlockIdentifier, Header, Justification, PeerId}; mod vertex; -use vertex::{JustificationAddResult, Vertex}; - -type BlockIdFor = <::Header as Header>::Identifier; +pub use vertex::JustificationAddResult; +use vertex::Vertex; pub struct JustificationWithParent { pub justification: J, diff --git a/finality-aleph/src/sync/forest/vertex.rs b/finality-aleph/src/sync/forest/vertex.rs index ce4efa3b5d..7d26d66bdc 100644 --- a/finality-aleph/src/sync/forest/vertex.rs +++ b/finality-aleph/src/sync/forest/vertex.rs @@ -1,6 +1,6 @@ use std::collections::HashSet; -use crate::sync::{forest::BlockIdFor, Justification, PeerId}; +use crate::sync::{BlockIdFor, Justification, PeerId}; #[derive(Clone, Debug, Copy, PartialEq, Eq)] enum HeaderImportance { diff --git a/finality-aleph/src/sync/handler.rs b/finality-aleph/src/sync/handler.rs new file mode 100644 index 0000000000..7641d84098 --- /dev/null +++ b/finality-aleph/src/sync/handler.rs @@ -0,0 +1,486 @@ +use crate::{ + session::{last_block_of_session, session_id_from_block_num, SessionId, SessionPeriod}, + sync::{ + data::{NetworkData, State}, + forest::{Error as ForestError, Forest, JustificationAddResult}, + BlockIdFor, BlockIdentifier, ChainStatus, Finalizer, Header, Justification, PeerId, + Verifier, + }, +}; + +/// How many justifications we will send at most in response to a broadcast. +const MAX_SMALL_JUSTIFICATION_BATCH: u32 = 10; +// Silly workaround to make range matching actually work... +const MAX_SMALL_JUSTIFICATION_BATCH_PLUS_ONE: u32 = MAX_SMALL_JUSTIFICATION_BATCH + 1; +/// How many justifications we will send at most in response to an explicit query. +const MAX_JUSTIFICATION_BATCH: u32 = 100; + +/// Handler for data incoming from the network. +pub struct Handler, V: Verifier, F: Finalizer> +{ + chain_status: CS, + verifier: V, + finalizer: F, + forest: Forest, + period: SessionPeriod, +} + +/// What actions can the handler recommend as a reaction to some data. +#[derive(Clone, Debug)] +pub enum SyncActions { + /// A response for the peer that sent us the data. + Response(NetworkData), + /// A task that should be performed periodically. At the moment these are only requests for blocks, + /// so it always contains the id of the block. + Task(BlockIdFor), + /// Do nothing. + Noop, +} + +impl SyncActions { + fn noop() -> Self { + SyncActions::Noop + } + + fn response(response: NetworkData) -> Self { + SyncActions::Response(response) + } + + fn task(id: BlockIdFor) -> Self { + SyncActions::Task(id) + } +} + +/// What can go wrong when handling a piece of data. +#[derive(Clone, Debug)] +pub enum Error, V: Verifier, F: Finalizer> { + Verifier(V::Error), + ChainStatus(CS::Error), + Finalizer(F::Error), + Forest(ForestError), + NoParent, + MissingJustification, +} + +impl, V: Verifier, F: Finalizer> From + for Error +{ + fn from(e: ForestError) -> Self { + Error::Forest(e) + } +} + +impl, V: Verifier, F: Finalizer> + Handler +{ + /// New handler with the provided chain interfaces. + pub fn new( + chain_status: CS, + verifier: V, + finalizer: F, + period: SessionPeriod, + ) -> Result> { + let top_finalized = chain_status + .top_finalized() + .map_err(Error::ChainStatus)? + .header() + .id(); + let forest = Forest::new(top_finalized); + Ok(Handler { + chain_status, + verifier, + finalizer, + forest, + period, + }) + } + + fn large_justification_batch_from( + &self, + id: BlockIdFor, + ) -> Result, Error> { + use Error::*; + let mut result = Vec::with_capacity(MAX_SMALL_JUSTIFICATION_BATCH as usize); + let mut number = id.number() + 1; + let top_finalized_number = self + .chain_status + .top_finalized() + .map_err(ChainStatus)? + .header() + .id() + .number(); + while result.len() < MAX_JUSTIFICATION_BATCH as usize && number <= top_finalized_number { + number = match self + .chain_status + .finalized_at(number) + .map_err(ChainStatus)? + { + Some(justification) => { + result.push(justification.into_unverified()); + number + 1 + } + // We might skip all blocks of a session if we are missing a justification, but + // this will happen only for sessions where we don't have all the justifications. + // The last block of a session was always guaranteed to contain a justification, so + // we only share that one. It can be missing only if the last block is above the + // top finalized, in which case this will break the loop. + None => last_block_of_session( + session_id_from_block_num(number, self.period), + self.period, + ), + } + } + Ok(NetworkData::Justifications(result, self.state()?)) + } + + fn small_justification_batch_from( + &self, + id: BlockIdFor, + ) -> Result, Error> { + let mut result = Vec::with_capacity(MAX_SMALL_JUSTIFICATION_BATCH as usize); + let mut number = id.number() + 1; + while result.len() < MAX_SMALL_JUSTIFICATION_BATCH as usize { + match self + .chain_status + .finalized_at(number) + .map_err(Error::ChainStatus)? + { + Some(justification) => result.push(justification.into_unverified()), + None => break, + } + number += 1; + } + Ok(NetworkData::Justifications(result, self.state()?)) + } + + fn top_understandable_for( + &self, + id: BlockIdFor, + ) -> Result, Error> { + use Error::*; + let block_session = session_id_from_block_num(id.number(), self.period); + let understandable_session = SessionId(block_session.0 + 1); + let last_understandable_block_number = + last_block_of_session(understandable_session, self.period); + match self + .chain_status + .finalized_at(last_understandable_block_number) + .map_err(ChainStatus)? + { + Some(justification) => Ok(NetworkData::Justifications( + vec![justification.into_unverified()], + self.state()?, + )), + None => { + let justification = self.chain_status.top_finalized().map_err(ChainStatus)?; + match justification.header().id().number() <= last_understandable_block_number { + true => Ok(NetworkData::Justifications( + vec![justification.into_unverified()], + self.state()?, + )), + false => Err(Error::MissingJustification), + } + } + } + } + + fn try_finalize(&mut self) -> Result<(), Error> { + while let Some(justification) = self.forest.try_finalize() { + self.finalizer + .finalize(justification) + .map_err(Error::Finalizer)?; + } + Ok(()) + } + + fn handle_verified_justification( + &mut self, + justification: J, + peer: I, + ) -> Result, Error> { + use JustificationAddResult::*; + let id = justification.header().id(); + match self + .forest + .update_justification(justification, Some(peer))? + { + Noop => Ok(SyncActions::noop()), + Required => Ok(SyncActions::task(id)), + Finalizable => { + self.try_finalize()?; + Ok(SyncActions::noop()) + } + } + } + + /// Inform the handler that a block has been imported. + pub fn block_imported(&mut self, header: J::Header) -> Result<(), Error> { + match self.forest.update_body(&header)? { + true => self.try_finalize(), + false => Ok(()), + } + } + + /// Handle a request for potentially substantial amounts of data. + /// + /// Currently ignores the requested id, it will only become important once we can request + /// blocks. + pub fn handle_request( + &mut self, + _requested_id: BlockIdFor, + state: State, + ) -> Result, Error> { + let remote_top_id = self + .verifier + .verify(state.top_justification()) + .map_err(Error::Verifier)? + .header() + .id(); + Ok(SyncActions::response( + self.large_justification_batch_from(remote_top_id)?, + )) + } + + /// Handle a single justification. + pub fn handle_justification( + &mut self, + justification: J::Unverified, + peer: I, + ) -> Result, Error> { + let justification = self + .verifier + .verify(justification) + .map_err(Error::Verifier)?; + self.handle_verified_justification(justification, peer) + } + + /// Handle a state broadcast returning the actions we should take in response. + pub fn handle_state( + &mut self, + state: State, + peer: I, + ) -> Result, Error> { + use Error::*; + let remote_top = self + .verifier + .verify(state.top_justification()) + .map_err(Verifier)?; + let local_top = self.chain_status.top_finalized().map_err(ChainStatus)?; + match local_top + .header() + .id() + .number() + .checked_sub(remote_top.header().id().number()) + { + // If we are just one behind then normal broadcasts should remedy the situation. + Some(0..=1) => Ok(SyncActions::noop()), + Some(2..=MAX_SMALL_JUSTIFICATION_BATCH) => Ok(SyncActions::response( + self.small_justification_batch_from(remote_top.header().id())?, + )), + Some(MAX_SMALL_JUSTIFICATION_BATCH_PLUS_ONE..) => Ok(SyncActions::response( + self.top_understandable_for(remote_top.header().id())?, + )), + None => self.handle_verified_justification(remote_top, peer), + } + } + + /// The current state of our database. + pub fn state(&self) -> Result, Error> { + let top_justification = self + .chain_status + .top_finalized() + .map_err(Error::ChainStatus)? + .into_unverified(); + Ok(State::new(top_justification)) + } +} + +#[cfg(test)] +mod tests { + use super::{Handler, SyncActions}; + use crate::{ + sync::{ + data::NetworkData, + mock::{Backend, MockHeader, MockJustification, MockPeerId, MockVerifier}, + ChainStatus, Header, Justification, + }, + SessionPeriod, + }; + + type MockHandler = Handler; + + const SESSION_PERIOD: usize = 20; + + fn setup() -> (MockHandler, Backend, impl Send) { + let (backend, _keep) = Backend::setup(); + let verifier = MockVerifier {}; + let handler = Handler::new( + backend.clone(), + verifier, + backend.clone(), + SessionPeriod(20), + ) + .expect("mock backend works"); + (handler, backend, _keep) + } + + fn import_branch(backend: &Backend, branch_length: usize) -> Vec { + let result: Vec<_> = backend + .best_block() + .expect("mock backend works") + .random_branch() + .take(branch_length) + .collect(); + for header in &result { + backend.import(header.clone()); + } + result + } + + #[test] + fn finalizes_imported_and_justified() { + let (mut handler, backend, _keep) = setup(); + let header = import_branch(&backend, 1)[0].clone(); + handler + .block_imported(header.clone()) + .expect("importing in order"); + let justification = MockJustification::for_header(header); + let peer = rand::random(); + assert!(matches!( + handler + .handle_justification(justification.clone().into_unverified(), peer) + .expect("correct justification"), + SyncActions::Noop + )); + assert_eq!( + backend.top_finalized().expect("mock backend works"), + justification + ); + } + + #[test] + fn finalizes_justified_and_imported() { + let (mut handler, backend, _keep) = setup(); + let header = import_branch(&backend, 1)[0].clone(); + let justification = MockJustification::for_header(header.clone()); + let peer = rand::random(); + match handler + .handle_justification(justification.clone().into_unverified(), peer) + .expect("correct justification") + { + SyncActions::Task(id) => assert_eq!(id, header.id()), + other_action => panic!("expected a task, got {:?}", other_action), + } + handler.block_imported(header).expect("importing in order"); + assert_eq!( + backend.top_finalized().expect("mock backend works"), + justification + ); + } + + #[test] + fn handles_state_with_small_difference() { + let (mut handler, backend, _keep) = setup(); + let initial_state = handler.state().expect("state works"); + let peer = rand::random(); + for justification in import_branch(&backend, 5) + .into_iter() + .map(MockJustification::for_header) + { + handler + .block_imported(justification.header().clone()) + .expect("importing in order"); + handler + .handle_justification(justification.clone().into_unverified(), peer) + .expect("correct justification"); + } + match handler + .handle_state(initial_state, peer) + .expect("correct justification") + { + SyncActions::Response(NetworkData::Justifications(justifications, _)) => { + assert_eq!(justifications.len(), 5) + } + other_action => panic!( + "expected a response with justifications, got {:?}", + other_action + ), + } + } + + #[test] + fn handles_state_with_large_difference() { + let (mut handler, backend, _keep) = setup(); + let initial_state = handler.state().expect("state works"); + let peer = rand::random(); + let justifications: Vec<_> = import_branch(&backend, 500) + .into_iter() + .map(MockJustification::for_header) + .collect(); + for justification in &justifications { + handler + .block_imported(justification.header().clone()) + .expect("importing in order"); + handler + .handle_justification(justification.clone().into_unverified(), peer) + .expect("correct justification"); + } + match handler + .handle_state(initial_state, peer) + .expect("correct justification") + { + SyncActions::Response(NetworkData::Justifications(sent_justifications, _)) => { + assert_eq!(sent_justifications.len(), 1); + assert_eq!( + sent_justifications[0].header().id(), + justifications[SESSION_PERIOD * 2 - 2].header().id() + ); + } + other_action => panic!( + "expected a response with justifications, got {:?}", + other_action + ), + } + } + + #[test] + fn handles_request() { + let (mut handler, backend, _keep) = setup(); + let initial_state = handler.state().expect("state works"); + let peer = rand::random(); + let justifications: Vec<_> = import_branch(&backend, 500) + .into_iter() + .map(MockJustification::for_header) + .collect(); + for justification in &justifications { + handler + .block_imported(justification.header().clone()) + .expect("importing in order"); + handler + .handle_justification(justification.clone().into_unverified(), peer) + .expect("correct justification"); + } + // currently ignored, so picking a random one + let requested_id = justifications[43].header().id(); + match handler + .handle_request(requested_id, initial_state) + .expect("correct request") + { + SyncActions::Response(NetworkData::Justifications(sent_justifications, _)) => { + assert_eq!(sent_justifications.len(), 100); + for (sent_justification, justification) in + sent_justifications.iter().zip(justifications) + { + assert_eq!( + sent_justification.header().id(), + justification.header().id() + ); + } + } + other_action => panic!( + "expected a response with justifications, got {:?}", + other_action + ), + } + } +} diff --git a/finality-aleph/src/sync/mock/backend.rs b/finality-aleph/src/sync/mock/backend.rs index 1c77472645..65325c607c 100644 --- a/finality-aleph/src/sync/mock/backend.rs +++ b/finality-aleph/src/sync/mock/backend.rs @@ -13,7 +13,7 @@ use crate::sync::{ Justification as JustificationT, }; -#[derive(Debug)] +#[derive(Clone, Debug)] struct MockBlock { header: MockHeader, justification: Option, @@ -36,25 +36,20 @@ impl MockBlock { } } +#[derive(Clone, Debug)] struct BackendStorage { blockchain: HashMap, - top_finalized: MockIdentifier, + finalized: Vec, best_block: MockIdentifier, genesis_block: MockIdentifier, } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Backend { inner: Arc>, notification_sender: UnboundedSender, } -pub fn setup() -> (Backend, impl ChainStatusNotifier) { - let (notification_sender, notification_receiver) = mpsc::unbounded(); - - (Backend::new(notification_sender), notification_receiver) -} - fn is_predecessor( storage: &HashMap, mut header: MockHeader, @@ -77,6 +72,12 @@ fn is_predecessor( } impl Backend { + pub fn setup() -> (Self, impl ChainStatusNotifier) { + let (notification_sender, notification_receiver) = mpsc::unbounded(); + + (Backend::new(notification_sender), notification_receiver) + } + fn new(notification_sender: UnboundedSender) -> Self { let header = MockHeader::random_parentless(0); let id = header.id(); @@ -88,7 +89,7 @@ impl Backend { let storage = Arc::new(Mutex::new(BackendStorage { blockchain: HashMap::from([(id.clone(), block)]), - top_finalized: id.clone(), + finalized: vec![id.clone()], best_block: id.clone(), genesis_block: id, })); @@ -186,10 +187,11 @@ impl Finalizer for Backend { panic!("finalizing block without a finalized parent: {:?}", header); } - if parent_id != storage.top_finalized { + if &parent_id != storage.finalized.last().expect("there is a top finalized") { panic!( "finalizing block whose parent is not top finalized: {:?}. Top is {:?}", - header, storage.top_finalized + header, + storage.finalized.last().expect("there is a top finalized") ); } @@ -200,7 +202,7 @@ impl Finalizer for Backend { }; block.finalize(justification); - storage.top_finalized = id.clone(); + storage.finalized.push(id.clone()); // In case finalization changes best block, we set best block, to top finalized. // Whenever a new import happens, best block will update anyway. if !is_predecessor( @@ -246,6 +248,19 @@ impl ChainStatus for Backend { } } + fn finalized_at(&self, number: u32) -> Result, Self::Error> { + let storage = self.inner.lock(); + let id = match storage.finalized.get(number as usize) { + Some(id) => id, + None => return Ok(None), + }; + storage + .blockchain + .get(id) + .ok_or(StatusError) + .map(|b| b.justification.clone()) + } + fn best_block(&self) -> Result { let storage = self.inner.lock(); let id = storage.best_block.clone(); @@ -258,7 +273,11 @@ impl ChainStatus for Backend { fn top_finalized(&self) -> Result { let storage = self.inner.lock(); - let id = storage.top_finalized.clone(); + let id = storage + .finalized + .last() + .expect("there is a top finalized") + .clone(); storage .blockchain .get(&id) diff --git a/finality-aleph/src/sync/mock/mod.rs b/finality-aleph/src/sync/mock/mod.rs index 8802b26a34..f7bd4a1cbb 100644 --- a/finality-aleph/src/sync/mock/mod.rs +++ b/finality-aleph/src/sync/mock/mod.rs @@ -4,8 +4,7 @@ use codec::{Decode, Encode}; use sp_core::H256; use crate::sync::{ - BlockIdentifier, BlockStatus, ChainStatusNotification, ChainStatusNotifier, Header, - Justification as JustificationT, Verifier, + BlockIdentifier, BlockStatus, ChainStatusNotification, Header, Justification as JustificationT, }; mod backend; @@ -134,14 +133,3 @@ impl JustificationT for MockJustification { type MockNotification = ChainStatusNotification; type MockBlockStatus = BlockStatus; - -pub fn setup() -> ( - Backend, - impl Verifier, - impl ChainStatusNotifier, -) { - let (backend, notifier) = backend::setup(); - let verifier = MockVerifier; - - (backend, verifier, notifier) -} diff --git a/finality-aleph/src/sync/mock/verifier.rs b/finality-aleph/src/sync/mock/verifier.rs index a8f95318ed..b4089e260a 100644 --- a/finality-aleph/src/sync/mock/verifier.rs +++ b/finality-aleph/src/sync/mock/verifier.rs @@ -2,6 +2,7 @@ use std::fmt::{Display, Error as FmtError, Formatter}; use crate::sync::{mock::MockJustification, Verifier}; +#[derive(Debug)] pub struct MockVerifier; #[derive(Debug)] diff --git a/finality-aleph/src/sync/mod.rs b/finality-aleph/src/sync/mod.rs index 611a510de2..b33c990685 100644 --- a/finality-aleph/src/sync/mod.rs +++ b/finality-aleph/src/sync/mod.rs @@ -7,6 +7,7 @@ use codec::Codec; mod data; mod forest; +mod handler; #[cfg(test)] mod mock; mod substrate; @@ -23,7 +24,7 @@ pub trait PeerId: Clone + Hash + Eq {} impl PeerId for T {} /// The identifier of a block, the least amount of knowledge we can have about a block. -pub trait BlockIdentifier: Clone + Hash + Debug + Eq { +pub trait BlockIdentifier: Clone + Hash + Debug + Eq + Codec + Send + Sync + 'static { /// The block number, useful when reasoning about hopeless forks. fn number(&self) -> u32; } @@ -35,7 +36,7 @@ pub trait Requester { } /// The header of a block, containing information about the parent relation. -pub trait Header: Clone { +pub trait Header: Clone + Codec + Send + Sync + 'static { type Identifier: BlockIdentifier; /// The identifier of this block. @@ -46,9 +47,9 @@ pub trait Header: Clone { } /// The verified justification of a block, including a header. -pub trait Justification: Clone { +pub trait Justification: Clone + Send + Sync + 'static { type Header: Header; - type Unverified: Clone + Codec + Debug; + type Unverified: Clone + Codec + Debug + Send + Sync + 'static; /// The header of the block. fn header(&self) -> &Self::Header; @@ -57,6 +58,8 @@ pub trait Justification: Clone { fn into_unverified(self) -> Self::Unverified; } +type BlockIdFor = <::Header as Header>::Identifier; + /// A verifier of justifications. pub trait Verifier { type Error: Display; @@ -76,6 +79,7 @@ pub trait Finalizer { } /// A notification about the chain status changing. +#[derive(Clone, Debug)] pub enum ChainStatusNotification { /// A block has been imported. BlockImported(BI), @@ -112,6 +116,10 @@ pub trait ChainStatus { id: ::Identifier, ) -> Result, Self::Error>; + /// The justification at this block number, if we have it. Should return None if the + /// request is above the top finalized. + fn finalized_at(&self, number: u32) -> Result, Self::Error>; + /// The header of the best block. fn best_block(&self) -> Result; diff --git a/finality-aleph/src/sync/substrate/chain_status.rs b/finality-aleph/src/sync/substrate/chain_status.rs index 74958dba6f..66b7245ef5 100644 --- a/finality-aleph/src/sync/substrate/chain_status.rs +++ b/finality-aleph/src/sync/substrate/chain_status.rs @@ -13,7 +13,10 @@ use sp_runtime::{ use crate::{ justification::backwards_compatible_decode, - sync::{substrate::Justification, BlockStatus, ChainStatus, Header, LOG_TARGET}, + sync::{ + substrate::{BlockId, Justification}, + BlockStatus, ChainStatus, Header, LOG_TARGET, + }, AlephJustification, }; @@ -73,6 +76,10 @@ where B: BlockT, B::Header: SubstrateHeader, { + fn hash_for_number(&self, number: BlockNumber) -> Result, ClientError> { + self.client.hash(number) + } + fn header(&self, hash: B::Hash) -> Result, ClientError> { let id = SubstrateBlockId::::Hash(hash); self.client.header(id) @@ -119,6 +126,20 @@ where { type Error = Error; + fn finalized_at( + &self, + number: BlockNumber, + ) -> Result>, Self::Error> { + let id = match self.hash_for_number(number)? { + Some(hash) => BlockId { hash, number }, + None => return Ok(None), + }; + match self.status_of(id)? { + BlockStatus::Justified(justification) => Ok(Some(justification)), + _ => Ok(None), + } + } + fn status_of( &self, id: ::Identifier,