diff --git a/finality-aleph/src/sync/mock/backend.rs b/finality-aleph/src/sync/mock/backend.rs new file mode 100644 index 0000000000..1c77472645 --- /dev/null +++ b/finality-aleph/src/sync/mock/backend.rs @@ -0,0 +1,268 @@ +use std::{ + collections::HashMap, + fmt::{Display, Error as FmtError, Formatter}, + sync::Arc, +}; + +use futures::channel::mpsc::{self, UnboundedSender}; +use parking_lot::Mutex; + +use crate::sync::{ + mock::{MockHeader, MockIdentifier, MockJustification, MockNotification}, + BlockIdentifier, BlockStatus, ChainStatus, ChainStatusNotifier, Finalizer, Header, + Justification as JustificationT, +}; + +#[derive(Debug)] +struct MockBlock { + header: MockHeader, + justification: Option, +} + +impl MockBlock { + fn new(header: MockHeader) -> Self { + Self { + header, + justification: None, + } + } + + fn header(&self) -> MockHeader { + self.header.clone() + } + + fn finalize(&mut self, justification: MockJustification) { + self.justification = Some(justification); + } +} + +struct BackendStorage { + blockchain: HashMap, + top_finalized: MockIdentifier, + best_block: MockIdentifier, + genesis_block: MockIdentifier, +} + +#[derive(Clone)] +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, + maybe_predecessor: MockIdentifier, +) -> bool { + while let Some(parent) = header.parent_id() { + if header.id().number() != parent.number() + 1 { + break; + } + if parent == maybe_predecessor { + return true; + } + + header = match storage.get(&parent) { + Some(block) => block.header(), + None => return false, + } + } + false +} + +impl Backend { + fn new(notification_sender: UnboundedSender) -> Self { + let header = MockHeader::random_parentless(0); + let id = header.id(); + + let block = MockBlock { + header: header.clone(), + justification: Some(MockJustification::for_header(header)), + }; + + let storage = Arc::new(Mutex::new(BackendStorage { + blockchain: HashMap::from([(id.clone(), block)]), + top_finalized: id.clone(), + best_block: id.clone(), + genesis_block: id, + })); + + Self { + inner: storage, + notification_sender, + } + } + + fn notify_imported(&self, id: MockIdentifier) { + self.notification_sender + .unbounded_send(MockNotification::BlockImported(id)) + .expect("notification receiver is open"); + } + + fn notify_finalized(&self, id: MockIdentifier) { + self.notification_sender + .unbounded_send(MockNotification::BlockFinalized(id)) + .expect("notification receiver is open"); + } + + pub fn import(&self, header: MockHeader) { + let mut storage = self.inner.lock(); + + let parent_id = match header.parent_id() { + Some(id) => id, + None => panic!("importing block without a parent: {:?}", header), + }; + + if storage.blockchain.contains_key(&header.id()) { + panic!("importing an already imported block: {:?}", header) + } + + if !storage.blockchain.contains_key(&parent_id) { + panic!("importing block without an imported parent: {:?}", header) + } + + if header.id().number() != parent_id.number() + 1 { + panic!("importing block without a correct parent: {:?}", header) + } + + if header.id().number() > storage.best_block.number() + && is_predecessor( + &storage.blockchain, + header.clone(), + storage.best_block.clone(), + ) + { + storage.best_block = header.id(); + } + + storage + .blockchain + .insert(header.id(), MockBlock::new(header.clone())); + + self.notify_imported(header.id()); + } +} + +#[derive(Debug)] +pub struct FinalizerError; + +impl Display for FinalizerError { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { + write!(f, "{:?}", self) + } +} + +impl Finalizer for Backend { + type Error = FinalizerError; + + fn finalize(&self, justification: MockJustification) -> Result<(), Self::Error> { + if !justification.is_correct { + panic!( + "finalizing block with an incorrect justification: {:?}", + justification + ); + } + + let mut storage = self.inner.lock(); + + let header = justification.header(); + let parent_id = match justification.header().parent_id() { + Some(id) => id, + None => panic!("finalizing block without a parent: {:?}", header), + }; + + let parent_block = match storage.blockchain.get(&parent_id) { + Some(block) => block, + None => panic!("finalizing block without an imported parent: {:?}", header), + }; + + if parent_block.justification.is_none() { + panic!("finalizing block without a finalized parent: {:?}", header); + } + + if parent_id != storage.top_finalized { + panic!( + "finalizing block whose parent is not top finalized: {:?}. Top is {:?}", + header, storage.top_finalized + ); + } + + let id = justification.header().id(); + let block = match storage.blockchain.get_mut(&id) { + Some(block) => block, + None => panic!("finalizing a not imported block: {:?}", header), + }; + + block.finalize(justification); + storage.top_finalized = 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( + &storage.blockchain, + storage + .blockchain + .get(&storage.best_block) + .unwrap() + .header(), + id.clone(), + ) { + storage.best_block = id.clone() + } + self.notify_finalized(id); + + Ok(()) + } +} + +#[derive(Debug)] +pub struct StatusError; + +impl Display for StatusError { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { + write!(f, "{:?}", self) + } +} + +impl ChainStatus for Backend { + type Error = StatusError; + + fn status_of(&self, id: MockIdentifier) -> Result, Self::Error> { + let storage = self.inner.lock(); + let block = match storage.blockchain.get(&id) { + Some(block) => block, + None => return Ok(BlockStatus::Unknown), + }; + + if let Some(justification) = block.justification.clone() { + Ok(BlockStatus::Justified(justification)) + } else { + Ok(BlockStatus::Present(block.header())) + } + } + + fn best_block(&self) -> Result { + let storage = self.inner.lock(); + let id = storage.best_block.clone(); + storage + .blockchain + .get(&id) + .map(|b| b.header()) + .ok_or(StatusError) + } + + fn top_finalized(&self) -> Result { + let storage = self.inner.lock(); + let id = storage.top_finalized.clone(); + storage + .blockchain + .get(&id) + .and_then(|b| b.justification.clone()) + .ok_or(StatusError) + } +} diff --git a/finality-aleph/src/sync/mock.rs b/finality-aleph/src/sync/mock/mod.rs similarity index 60% rename from finality-aleph/src/sync/mock.rs rename to finality-aleph/src/sync/mock/mod.rs index 2cdc630cf3..8802b26a34 100644 --- a/finality-aleph/src/sync/mock.rs +++ b/finality-aleph/src/sync/mock/mod.rs @@ -1,22 +1,38 @@ +use std::hash::Hash; + use codec::{Decode, Encode}; +use sp_core::H256; + +use crate::sync::{ + BlockIdentifier, BlockStatus, ChainStatusNotification, ChainStatusNotifier, Header, + Justification as JustificationT, Verifier, +}; + +mod backend; +mod status_notifier; +mod verifier; -use crate::sync::{BlockIdentifier, Header, Justification}; +type MockNumber = u32; +type MockHash = H256; + +pub use backend::Backend; +pub use verifier::MockVerifier; pub type MockPeerId = u32; #[derive(Clone, Hash, Debug, PartialEq, Eq, Encode, Decode)] pub struct MockIdentifier { - number: u32, - hash: u32, + number: MockNumber, + hash: MockHash, } impl MockIdentifier { - fn new(number: u32, hash: u32) -> Self { + fn new(number: MockNumber, hash: MockHash) -> Self { MockIdentifier { number, hash } } - pub fn new_random(number: u32) -> Self { - MockIdentifier::new(number, rand::random()) + pub fn new_random(number: MockNumber) -> Self { + MockIdentifier::new(number, MockHash::random()) } } @@ -37,7 +53,7 @@ impl MockHeader { MockHeader { id, parent } } - pub fn random_parentless(number: u32) -> Self { + pub fn random_parentless(number: MockNumber) -> Self { let id = MockIdentifier::new_random(number); MockHeader { id, parent: None } } @@ -84,15 +100,26 @@ impl Header for MockHeader { #[derive(Clone, Hash, Debug, PartialEq, Eq, Encode, Decode)] pub struct MockJustification { header: MockHeader, + is_correct: bool, } impl MockJustification { pub fn for_header(header: MockHeader) -> Self { - MockJustification { header } + Self { + header, + is_correct: true, + } + } + + pub fn for_header_incorrect(header: MockHeader) -> Self { + Self { + header, + is_correct: false, + } } } -impl Justification for MockJustification { +impl JustificationT for MockJustification { type Header = MockHeader; type Unverified = Self; @@ -104,3 +131,17 @@ impl Justification for MockJustification { self } } + +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/status_notifier.rs b/finality-aleph/src/sync/mock/status_notifier.rs new file mode 100644 index 0000000000..e915e1849e --- /dev/null +++ b/finality-aleph/src/sync/mock/status_notifier.rs @@ -0,0 +1,30 @@ +use std::fmt::{Display, Error as FmtError, Formatter}; + +use futures::channel::mpsc::UnboundedReceiver; + +use crate::sync::{ + mock::{MockIdentifier, MockNotification}, + ChainStatusNotifier, +}; + +#[derive(Debug)] +pub enum Error { + StreamClosed, +} + +impl Display for Error { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { + write!(f, "{:?}", self) + } +} + +#[async_trait::async_trait] +impl ChainStatusNotifier for UnboundedReceiver { + type Error = Error; + + async fn next(&mut self) -> Result { + ::next(self) + .await + .ok_or(Error::StreamClosed) + } +} diff --git a/finality-aleph/src/sync/mock/verifier.rs b/finality-aleph/src/sync/mock/verifier.rs new file mode 100644 index 0000000000..a8f95318ed --- /dev/null +++ b/finality-aleph/src/sync/mock/verifier.rs @@ -0,0 +1,31 @@ +use std::fmt::{Display, Error as FmtError, Formatter}; + +use crate::sync::{mock::MockJustification, Verifier}; + +pub struct MockVerifier; + +#[derive(Debug)] +pub enum Error { + IncorrectJustification, +} + +impl Display for Error { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { + write!(f, "{:?}", self) + } +} + +impl Verifier for MockVerifier { + type Error = Error; + + fn verify( + &mut self, + justification: MockJustification, + ) -> Result { + if justification.is_correct { + Ok(justification) + } else { + Err(Error::IncorrectJustification) + } + } +}