Skip to content

Commit

Permalink
A0-1824: add sync required mocks (#883)
Browse files Browse the repository at this point in the history
  • Loading branch information
maciejnems authored Jan 25, 2023
1 parent c15c121 commit 5983043
Show file tree
Hide file tree
Showing 4 changed files with 379 additions and 9 deletions.
268 changes: 268 additions & 0 deletions finality-aleph/src/sync/mock/backend.rs
Original file line number Diff line number Diff line change
@@ -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<MockJustification>,
}

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<MockIdentifier, MockBlock>,
top_finalized: MockIdentifier,
best_block: MockIdentifier,
genesis_block: MockIdentifier,
}

#[derive(Clone)]
pub struct Backend {
inner: Arc<Mutex<BackendStorage>>,
notification_sender: UnboundedSender<MockNotification>,
}

pub fn setup() -> (Backend, impl ChainStatusNotifier<MockIdentifier>) {
let (notification_sender, notification_receiver) = mpsc::unbounded();

(Backend::new(notification_sender), notification_receiver)
}

fn is_predecessor(
storage: &HashMap<MockIdentifier, MockBlock>,
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<MockNotification>) -> 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<MockJustification> 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<MockJustification> for Backend {
type Error = StatusError;

fn status_of(&self, id: MockIdentifier) -> Result<BlockStatus<MockJustification>, 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<MockHeader, Self::Error> {
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<MockJustification, Self::Error> {
let storage = self.inner.lock();
let id = storage.top_finalized.clone();
storage
.blockchain
.get(&id)
.and_then(|b| b.justification.clone())
.ok_or(StatusError)
}
}
Original file line number Diff line number Diff line change
@@ -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())
}
}

Expand All @@ -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 }
}
Expand Down Expand Up @@ -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;

Expand All @@ -104,3 +131,17 @@ impl Justification for MockJustification {
self
}
}

type MockNotification = ChainStatusNotification<MockIdentifier>;
type MockBlockStatus = BlockStatus<MockJustification>;

pub fn setup() -> (
Backend,
impl Verifier<MockJustification>,
impl ChainStatusNotifier<MockIdentifier>,
) {
let (backend, notifier) = backend::setup();
let verifier = MockVerifier;

(backend, verifier, notifier)
}
Loading

0 comments on commit 5983043

Please sign in to comment.