Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A0-1824: add sync required mocks #883

Merged
merged 1 commit into from
Jan 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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() {
lesniak43 marked this conversation as resolved.
Show resolved Hide resolved
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;
lesniak43 marked this conversation as resolved.
Show resolved Hide resolved

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;
lesniak43 marked this conversation as resolved.
Show resolved Hide resolved

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;
timorl marked this conversation as resolved.
Show resolved Hide resolved
type MockHash = H256;
timorl marked this conversation as resolved.
Show resolved Hide resolved

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