Skip to content

Commit

Permalink
Abstract over the various data types needed by the consensus engine (#8)
Browse files Browse the repository at this point in the history
Closes: #11

This gist of this PR is that it adds a `Consensus` trait which defines the various datatypes that the consensus engine operates over, as well as a trait per such datatype that defines the requirement for said datatype.

```rust
pub trait Consensus
where
    Self: Sized,
{
    type Address: Address;
    type Height: Height;
    type Proposal: Proposal<Self>;
    type PublicKey: PublicKey;
    type Validator: Validator<Self>;
    type ValidatorSet: ValidatorSet<Self>;
    type Value: Value;
    type Vote: Vote<Self>;
```

```rust
pub trait Height
where
    Self: Clone + Debug + PartialEq + Eq + PartialOrd + Ord,
{
}
```

etc.

In test code, we define our own simple versions of these datatypes as well as a `TestConsensus` struct, for which we implement the `Consensus` trait.


```rust
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Address(u64);

impl Address {
    pub const fn new(value: u64) -> Self {
        Self(value)
    }
}

impl malachite_common::Address for Address {}
```

```rust
pub struct TestConsensus;

impl Consensus for TestConsensus {
    type Address = Address;
    type Height = Height;
    type Proposal = Proposal;
    type PublicKey = PublicKey;
    type ValidatorSet = ValidatorSet;
    type Validator = Validator;
    type Value = Value;
    type Vote = Vote;
}
```

---

* Attempt to abstract over the various data types needed by the consensus engine

* Fix tests

* Fix lints names

* Move tests and associated data types into their own crate (#10)

* Move tests and associated data types into their own crate

* Add small threshold to codecov

* Small cleanup

* Move tests into `tests` folder

* Adjust base when computing coverage in the presence of removed code

See: https://docs.codecov.com/docs/commit-status#removed_code_behavior

* Document the `Round` type and rename `Round::None` to `Round::Nil`

* More doc comments

* Remove `PublicKey::hash` requirement

* Few more comments

* Add propose+precommit timeout test
  • Loading branch information
romac authored Oct 25, 2023
1 parent ccc12e6 commit 3fea113
Show file tree
Hide file tree
Showing 34 changed files with 1,823 additions and 884 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# macOS Finder and Windows Thumbs.db files
.DS_Store
Thumbs.db

# Generated by Cargo
# will have compiled files and executables
debug/
Expand Down
1 change: 1 addition & 0 deletions Code/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ members = [
"consensus",
"round",
"vote",
"test",
]
49 changes: 49 additions & 0 deletions Code/common/src/consensus.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use crate::{
Address, Height, Proposal, PublicKey, Round, Validator, ValidatorSet, Value, ValueId, Vote,
};

/// This trait allows to abstract over the various datatypes
/// that are used in the consensus engine.
pub trait Consensus
where
Self: Sized,
{
type Address: Address;
type Height: Height;
type Proposal: Proposal<Self>;
type PublicKey: PublicKey;
type Validator: Validator<Self>;
type ValidatorSet: ValidatorSet<Self>;
type Value: Value;
type Vote: Vote<Self>;

// FIXME: Remove this and thread it through where necessary
const DUMMY_ADDRESS: Self::Address;

// FIXME: Remove altogether
const DUMMY_VALUE: Self::Value;

/// Build a new proposal for the given value at the given height, round and POL round.
fn new_proposal(
height: Self::Height,
round: Round,
value: Self::Value,
pol_round: Round,
) -> Self::Proposal;

/// Build a new prevote vote by the validator with the given address,
/// for the value identified by the given value id, at the given round.
fn new_prevote(
round: Round,
value_id: Option<ValueId<Self>>,
address: Self::Address,
) -> Self::Vote;

/// Build a new precommit vote by the validator with the given address,
/// for the value identified by the given value id, at the given round.
fn new_precommit(
round: Round,
value_id: Option<ValueId<Self>>,
address: Self::Address,
) -> Self::Vote;
}
25 changes: 12 additions & 13 deletions Code/common/src/height.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
// TODO: Abstract over Height
use core::fmt::Debug;

/// A blockchain height
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct Height(u64);

impl Height {
pub fn new(height: u64) -> Self {
Self(height)
}

pub fn as_u64(&self) -> u64 {
self.0
}
// TODO: Keep the trait or just add the bounds to Consensus::Height?
/// Defines the requirements for a height type.
///
/// A height denotes the number of blocks (values) created since the chain began.
///
/// A height of 0 represents a chain which has not yet produced a block.
pub trait Height
where
// TODO: Require Copy as well?
Self: Clone + Debug + PartialEq + Eq + PartialOrd + Ord,
{
}
25 changes: 15 additions & 10 deletions Code/common/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
//! Common data types and abstractions
//! Common data types and abstractions for the consensus engine.
#![forbid(unsafe_code)]
#![deny(unused_crate_dependencies, trivial_casts, trivial_numeric_casts)]
#![warn(
// missing_docs,
broken_intra_doc_links,
private_intra_doc_links,
rustdoc::broken_intra_doc_links,
rustdoc::private_intra_doc_links,
variant_size_differences
)]
#![cfg_attr(not(test), deny(clippy::unwrap_used, clippy::panic))]

mod consensus;
mod height;
mod proposal;
mod round;
Expand All @@ -18,10 +19,14 @@ mod validator_set;
mod value;
mod vote;

pub use height::*;
pub use proposal::*;
pub use round::*;
pub use timeout::*;
pub use validator_set::*;
pub use value::*;
pub use vote::*;
/// Type alias to make it easier to refer the `ValueId` type of a given `Consensus` engine.
pub type ValueId<C> = <<C as Consensus>::Value as Value>::Id;

pub use consensus::Consensus;
pub use height::Height;
pub use proposal::Proposal;
pub use round::Round;
pub use timeout::{Timeout, TimeoutStep};
pub use validator_set::{Address, PublicKey, Validator, ValidatorSet, VotingPower};
pub use value::Value;
pub use vote::{Vote, VoteType};
36 changes: 18 additions & 18 deletions Code/common/src/proposal.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
use crate::{Height, Round, Value};
use core::fmt::Debug;

/// A proposal for a value in a round
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Proposal {
pub height: Height,
pub round: Round,
pub value: Value,
pub pol_round: Round,
}
use crate::{Consensus, Round};

/// Defines the requirements for a proposal type.
pub trait Proposal<C: Consensus>
where
Self: Clone + Debug + PartialEq + Eq,
{
/// The height for which the proposal is for.
fn height(&self) -> C::Height;

/// The round for which the proposal is for.
fn round(&self) -> Round;

/// The value that is proposed.
fn value(&self) -> &C::Value;

impl Proposal {
pub fn new(height: Height, round: Round, value: Value, pol_round: Round) -> Self {
Self {
height,
round,
value,
pol_round,
}
}
/// The POL round for which the proposal is for.
fn pol_round(&self) -> Round;
}
51 changes: 35 additions & 16 deletions Code/common/src/round.rs
Original file line number Diff line number Diff line change
@@ -1,49 +1,68 @@
/// A round number, ie. a natural number
/// A round number.
///
/// Can be either:
/// - `Round::Nil` (ie. `-1`)
/// - `Round::Some(r)` where `r >= 0`
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Round {
/// No round
None,
/// No round, ie. `-1`
Nil,

/// Some round
/// Some round `r` where `r >= 0`
Some(i64),
}

impl Round {
/// The initial, zero round.
pub const INITIAL: Round = Round::new(0);

/// Create a new round.
///
/// If `round < 0`, then `Round::Nil` is returned.
/// Otherwise, `Round::Some(round)` is returned.
pub const fn new(round: i64) -> Self {
if round < 0 {
Self::None
Self::Nil
} else {
Self::Some(round)
}
}

/// Convert the round to an `i64`.
///
/// `Round::Nil` is converted to `-1`.
/// `Round::Some(r)` is converted to `r`.
pub fn as_i64(&self) -> i64 {
match self {
Round::None => -1,
Round::Nil => -1,
Round::Some(r) => *r,
}
}

/// Wether the round is defined, ie. `Round::Some(r)` where `r >= 0`.
pub fn is_defined(&self) -> bool {
matches!(self, Round::Some(r) if *r >= 0)
}

/// Wether the round is `Round::Nil`.
pub fn is_nil(&self) -> bool {
matches!(self, Round::None)
matches!(self, Round::Nil)
}

/// Whether the round is valid, ie. either nil or defined.
pub fn is_valid(&self) -> bool {
match self {
Round::None => true,
Round::Some(r) => *r >= 0,
}
// FIXME: This is always true, given that the constructor upholds
// the invariant that `Round::Some(r)` is only created for `r >= 0`.
self.is_nil() || self.is_defined()
}

/// Increment the round.
///
/// If the round is nil, then the initial zero round is returned.
/// Otherwise, the round is incremented by one.
pub fn increment(&self) -> Round {
match self {
Round::None => Round::new(0),
Round::Nil => Round::new(0),
Round::Some(r) => Round::new(r + 1),
}
}
Expand All @@ -68,20 +87,20 @@ mod tests {
#[test]
fn test_round() {
// Test Round::new()
assert_eq!(Round::new(-42), Round::None);
assert_eq!(Round::new(-1), Round::None);
assert_eq!(Round::new(-42), Round::Nil);
assert_eq!(Round::new(-1), Round::Nil);
assert_eq!(Round::new(0), Round::Some(0));
assert_eq!(Round::new(1), Round::Some(1));
assert_eq!(Round::new(2), Round::Some(2));

// Test Round::as_i64()
assert_eq!(Round::None.as_i64(), -1);
assert_eq!(Round::Nil.as_i64(), -1);
assert_eq!(Round::Some(0).as_i64(), 0);
assert_eq!(Round::Some(1).as_i64(), 1);
assert_eq!(Round::Some(2).as_i64(), 2);

// Test Round::is_defined()
assert!(!Round::None.is_defined());
assert!(!Round::Nil.is_defined());
assert!(Round::Some(0).is_defined());
assert!(Round::Some(1).is_defined());
assert!(Round::Some(2).is_defined());
Expand Down
22 changes: 21 additions & 1 deletion Code/common/src/timeout.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,34 @@
use crate::Round;

/// The round step for which the timeout is for.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum TimeoutStep {
Propose,
Prevote,
Precommit,
}

#[derive(Clone, Debug, PartialEq, Eq)]
/// A timeout for a round step.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Timeout {
pub round: Round,
pub step: TimeoutStep,
}

impl Timeout {
pub fn new(round: Round, step: TimeoutStep) -> Self {
Self { round, step }
}

pub fn propose(round: Round) -> Self {
Self::new(round, TimeoutStep::Propose)
}

pub fn prevote(round: Round) -> Self {
Self::new(round, TimeoutStep::Prevote)
}

pub fn precommit(round: Round) -> Self {
Self::new(round, TimeoutStep::Precommit)
}
}
Loading

0 comments on commit 3fea113

Please sign in to comment.